Pull to refresh

Comments 138

UFO just landed and posted this here

На сегодня я знаю только одну ORM, которая действительно умеет в SQL. Остальное может только в `Hello, World!`.

Однако в примерах простейший SQL, который действительно лучше генерить с помощью ORM.

UFO just landed and posted this here

На этот вопрос я уже ответил в приведённой выше ссылке. Прошу обратить внимание, что в центре статьи вовсе не производительность query builder, а широкие возможности в генерации SQL, включая макросы и интроспекцию

На сегодня я знаю только одну ORM, которая действительно умеет в SQL.

Посмотрите как она ищет места в SQL-запросе куда нужно добавить долларовые ($1) аргументы - она просто делает поиск вместо синтаксического разбора

Есть как минимум 3 ORM более известных для Delphi. И более десятка ORM, написанных случайными разработчиками и выложенных на гите.

Только не всегда ORM — это оптимальное решение. Т.к. в таком случае приходится оперировать объектами, что во многих случаях не нужно.

Здесь, возможно, нужно иметь некоторый опыт в разработке на Delphi, чтобы понять. Крупные проекты работают совсем иначе, чем «программы» на других языках. Так, когда вообще не нужно описывать структуру таблиц, но иметь возможность добавлять, изменять и удалять данные и даже если структура таблицы изменяется.
UFO just landed and posted this here

Многие крупные проекты в делфи работают без бэкенда, напрямую используя подключение к БД. А ORM, и вообще CRUD и прочее появились с веб-проектами.

Имея прямое подключение к БД имеется прямой доступ к таблицам и данным. В делфи имеются огромные возможности для работы с БД в дизайнтайме (в режиме дизайна проекта). Для того, чтобы написать крупный проект с БД можно обойтись без вообще написания кода. Достаточно положить на форму нужные таблицы из списка компонент. Выбрать подключение, создать либо компонент для получения данных из таблицы (напрямую), либо компонент, в котором мы пишем запрос (любой сложности). Указать куда отображать содержимое. Если имеется прямое подключение к таблице, мы можем манипулировать ею как хотим (CRUD). Если мы используем свой запрос, то если он не сложный, то мы тоже имеем сразу возможность манипулировать данными, если запрос сложный, то требуется, либо описать запросы редактирования, либо редактировать вручную. За исключением последнего, мы вообще ещё не прикасались к редактору кода.

Естественно ни кто не мешает создавать приложение так, как создаются они в других языках и средах. Использовать ORM, или сразу REST фреймворк.

ORM без GC та ещё боль.

Дельфисты привыкшие к подчистке за собой. Это даёт свои плюшки. Да и это можно решить разными средствами по типу TComponent и удалением лишь корневого элемента.

Спасибо автору!

Появилось сильное желание потрогать/вспомнить как там Delphi поживает.

Да поживает вполне. Слухи о его смерти сильно преувеличены уже много лет :)

Последняя версия Delphi - Delphi 7. А после уже не delphi, а выкидыш. Почему, когда в версии 10.3.1 пути прописаны и по ним все находит, а в 10.3.3 уже не находит. Вот путь прописан, вон юнит лежит по этому пути! Сука, не находит! Хочется вырвать разрабам руки, а не деньги платить за этот выкидыш...

Я на 5-й до сих пор пишу. Есть смысл на 7-ю перейти?

Вы слишком строги к разработчикам IDE :) Да, Делфи не идеальна. Но идеальных IDE я пока и не видел. Поначалу восхищался вот Android studio. Пото спали розовые очки и начал потихоньку материть её )))
У вас какой-то странный, единичный случай. И вообще ни как не относится к самому языку. Среда разработки (10.3.х) во многом превосходит Delphi 7, как и сам язык превосходит ту версию, которая использовалась в Delphi 7.

Тут я даже не знаю что и сказать, я конечно очень рад тому что в 2003 году Delphi 7 была достаточно хороша, что ее досихпор считают "правильной" и используют в продакшене. Но тем не менее язык развивается, и хотя бы из-за Advanced Records, Helpers, и юникодных строк имеет смысл посмотреть на новые версии.

В delphi 5 строки юникодные, в а delphi 7 dfm-файлы (шаблоны форм) — юникодные.
Я думаю всем нам понятно о чем я написал. Не о наличии UnicodeString/WideString как типа, а о дефолтной и полной поддержке в RTL, VCL и т.д.
Последняя версия Delphi — Delphi 7.

VCL не юникодная, generics нет, экзешники умеет только 32-битные генерировать, перед запуском не забудь сохранить проект иначе может повиснуть и ты лишишься части сделанной работы. Ну да, идеал и вершина развития.

Нечто подобное делал когда-то я, только на python

Мой проект подключался к разным схемам в качестве Read-Only пользователя, и при этом не имел собственной БД. Эти схемы содержали с большего одни и те же данные (в смысле решали одну и ту же задачу), просто разложены они были порой весьма причудливо. Запросы в том проекте у меня были довольно мозговзрывающие -- я пытался выжать максимум из БД. Даже хинты были в ходу -- Oracle неправильно оценивал cardinality и строил дикий план

Так вот, мой вариант принципиально отличается только тем, что весь SQL запихивать в один файл было смерти подобно. Соответственно, имя функции у меня являлось именем файла

И API у меня был почти идентичным. Разве что наверху был ещё один слой абстракции, но сути это не меняет

Да, была мысль разбить это дело на файлы как-то. Но отказался. Сам запрос отлаживается в SQLite Expert. И в проект помещается уже рабочий, единожды. Процедуры легко находятся по имени поиском. Никакого выигрыша от «многофайловости» не будет в работе. Ну разве только если действительно, процдур будет космическое количество и они будут огромны.
Выигрыш от многофайловости только в сценариях командной работы, когда нужно быстренько посмотреть git/svn blame/log

При длине одного запроса в 100 строк (были и длиннее, были и совсем коротенькие) и их количестве более 30 выигрыш от многофайловости далеко не только в сценариях командной работы

Организация SQL запросов в виде отдельных файлов, позволяет хранить в них кроме самого запроса, еще и часть дополнительной информации. Например:

Файл: CustomerByPostCode.sql

<code>

[Desc]

Запрос для выбора данных таблицы Customers (покупатели) по почтовому коду из адреса покупателя.

[SQL]

SELECT * FROM Customers WHERE PostCode = %P1

[Example]

SELECT * FROM Customers WHERE PostCode = 100

[Test]

P1=100

</code>

Здесь, секция [Desc] содержит комментарий-описание самого запроса. Секция [SQL] собственно сам запрос для вызова в программе. В принципе, можно написать парзер, который из отдельных файлов будет генерировать ресурс-файл на этапе pre-build проекта, если уж так хочется иметь один файл с запросами. Секция [Example] - пример запуска запроса с конкретными параметрами, при открытии запроса в SQL-редакторе можно выделить код в этой секции и быстро запустить, посмотрев, как работает запрос. Секция [Test] для описания параметров запуска при тестировании запроса, например unit-test системами. Использование секций вместо комментариев к запросу, позволяет очень просто работать с файлом с помощью TStringList. И, как уже сказали, смотреть историю изменения кода в системе контроля версий в связке с системой отслеживания изменений гораздо проще в случае одного файла на запрос. Например, если хочется посмотреть в рамках каких задач менялся запрос CustomerByPostCode, то это гораздо проще сделать в случае одного файла.

Ну на SQLite обычно такие мощные запросы не используются. Тут уже нормальная СУБД нужна

Выигрыш от многофайловости в возможности правки запросов без перекомпиляции программы. Хотя это можно сделать и поместив запросы в один файл, но с большими запросами будет неудобно. Я в таком случае разбивал большие запросы на функциональные части, давал им имена, а в рантайме подменял имена на запросы

Ну, конечно, этот вариант работает, а потому — небезынтересен.
Но мне, как программисту, когда-то давно активно работавшему с Delphi, его использование кажется странным.
Потому что есть пара других, более стандартных вариантов.
1. Если есть возможность использовать форму (TForm), хотя бы — невидимую, то на нее можно в Design Time накидать компонентов, подобных изначальному TQuery (для SQLite, если я не ошибась, это должно быть TFDQuery), связанных с нужным подключением (TFDConnection), и через Object Inspector (или как он там сейчас называется) установить все нужные тексты запросов в свойстве SQLQuery. А потом — использовать для вызова каждого запроса предназначенный для него компонент.
2. Если уж формы с компонентами использовать нельзя, то для хранения строк в ресурсах имеет смысл использовать специально предназначенный для этого тип STRINGTABLE: в нем строки изначально сопоставляются с целыми числами — номерами, так что в программе для получения нужных строк можно использовать системные средства (первый пример, который мне попался в поиске), а не свой велосипед.
Ну и чем лучше форма, содержащая десятки а то и сотни компонент? Найти и отредактировать в ней нужный запрос — тот еще квест ))))))
Предложенное вами решение — крайне неудобно, ИМХО
Вопрос удобства — он, конечно, глубоко персональный, но…
В форме компоненты запросов можно и визуально сгруппировать в соответствии с их назначением.
Компонентам можно дать значимые имена: эти имена — такие же идентификаторы, как и любые другие имена переменных, полей, свойств и т.д. В Object Inspector раньше был (и, надеюсь, и сейчас его не выпилили) список компонентов, в котором можно было выбрать компонент по его имени.
А для STRINGTABLE можно вместо числовых индексов использовать идентификаторы, если их предварительно определить через #define в том же файле ресурсов.
То есть, удобство использования альтернативных вариантов можно повысить.
Я использую и компоненты (TFDQuery например) тоже в проектах. Но когда количество запросов измеряется десятками — это уже совсем не комильфо, поверьте. Чтобы отредактировать текст запроса, я должен найти компонент на форме и дважды щелкнуть по нему. Это гораздо менее удобно. Я уж не говорю о том, что всё это довольно громоздкие объекты, которые жрут память, ресурсы… Так я использую один объект. А в вашем варианте их буде создано одновременно несолько десятков.
Впрочем, я не настаиваю. Мне удобнее — так. Но на вкус и цвет… :)

Прямо в точку, насчёт редактирования! При большом числе компонентов, часто проще отредактировать форму открыв dfm в notepade и найдя нужное место обычным поиском.

файлы dfm не предназначены для непосредственного редактирования. Я предпочитаю использовать штатные способы хакерским.
Редактировать dfm руками имеет смысл только в случае критической ошибки, ИМХО.
Так же как и dproj

А в чем хакерствр? Dfm такой же plain text как html, xml или json.

Этот файл создает и изменяет сама IDE. Если его некорректно отредактировать ручками, получите кучу странных ошибок и форма «сломается».
Одним словам — производитель не предусматривал такой сценарий.
Безусловно, и dpr и dproj и dfm можно править руками. Но лучше делать это только в крайнем случае. Я не раз получал совершенно неожиданные и непредсказуемые разультаты от вполне безобидных правок этих файлов.
Вот например, я вставил комментарий в dpr. Просто комментарий между " Application.Initialize;" и «Application.Run;».
И спустя какое-то время получил странное поведение проекта в design-time. Не создавалась главная форма при открытии проекта. При этом — никаких ошибок.
Но вот только ссылки свойств компонент на других формах на компоненты главной формы при сохранении стали затираться. Например — ссылка на StyleBook.
И я только через несколько месяцев понял, что виноват безобидный комментарий в файле dpr

Выглядит как ошибка в Delphi, это бы описать и отправить разработчикам. А так выходить изотерика какая-то: есть странное поведение причины которого не ясны.

Ну почему же? Файл dpr — служебный. IDE сама его создает и правит.
С файлом dproj — еще веселее )))
Если туда что-то добавлять НЕ в конец файла, вообще крыша у Delphi может поехать и файл перколбасит так, что не узнать.

Файл dpr - это основной файл и ни как не "служебный". Его можно и нужно редактировать и изменять. Ваша ошибка может быть легко объяснена (я уверен), если вы скинете код с повторяемой ошибкой.

begin
{$IFDEF DEBUG}
  // Для отображения утечек памяти, если они есть
  ReportMemoryLeaksOnShutdown := True;
{$ENDIF}
  Application.Initialize;
  // Сначала модули данных
  Application.CreateForm(TDatabaseModule, DatabaseModule);
  Application.CreateForm(TMenuUpdaterModule, MenuUpdaterModule);
  Application.CreateForm(TDBSettingsModule, DBSettingsModule);
  // Только потом - основную форму!
  Application.CreateForm(TfrmMainForm, frmMainForm);
  Application.Run;

end.

И? Это минимум, который есть у меня почти в каждом проекте. А бывает сложнее.
{$R *.res}
const AppMutexConst = 'HYPERHACK_UNIQ_F1F2F3';
var RestartTry: Integer = 5;
begin
{$IFNDEF DEBUG}
if IsDebuggerPresentInt then
asm
call exitprocess
end;
{$ENDIF}
CreateHomeDir;
{$IFDEF MSWINDOWS}
if OpenMutex(MUTEX_ALL_ACCESS, False, AppMutexConst) <> 0 then
begin
repeat
Dec(RestartTry);
Sleep(3000);
until (OpenMutex(MUTEX_ALL_ACCESS, False, AppMutexConst) = 0) or (RestartTry > 0);
end;
if RestartTry <= 0 then
Halt
else
CreateMutex(nil, False, AppMutexConst);
{$ENDIF}
{$IFDEF DEBUG}
ReportMemoryLeaksOnShutdown := True;
{$ENDIF}
Application.Initialize;
Application.CreateForm(TFormMain, FormMain);
Application.Run;
end.

И это далеко не самый сложный пример

Замечательный пример. Но я говорил о том, что нельзя вставлять комментарии между Application.Initialize; и Application.Run; — между строчками комад создания форм.
Ваш пример в этом смысле не показателен ))
Ошибки как таковой нет. Просто при загрузке проекта в design-time не создается frmMainForm.
Если убрать комментарии — форма создается.
Delphi 10.3 Upd. 3

Это крайне сомнительно. Что показывает отладчик? До строчки доходит?

Какой отладчик? В DESIGN-TIME
Вы бы перечитали комментарии выше. Я там обо всем написал
Проблемы нет. Но есть неудобство. Если ты забыл открыть главную форму ручками и начал редактировать другую форму, то на этой форме слетают все ссылки на главную.
Например — ссылка на компонент StyleBook. Он как правило, один на приложение и размещается на главной форме. А остальные формы на него ссылаются.
Таким образом, редактируемая форма теряет стиль оформления. И это происходит тихо и незаметно, безо всяких ошибок )). Хорошо, если тестировщик заметит до того, как оно уйдет в продакшн
В Delphi конечно ошибок немеряно. Но другие IDE грешат тем же, увы.
Меня больше всег раздражают не ошибки в IDE, а неполнота документации. Вот это — реальная беда. Я в последние пру лет активно использую в проектах Binding. Удобно. Часто вообще код писать не нужно при работе с БД — только SQL-запросы. Но он крайне плохо документирован. И на форумах про него днем с огнем ничего не найдешь… Обидно
Опять же — это только моё мнение. Я не настаиваю.
В 99% случаях не нужно лезть через окно — есть дверь. Просто я о ней не знаю :)
А в большом приложении процедур и запросов — сотни. И тут уже использовать сотни визуальных компонент… На мой взгляд — это извращение. Извините
Вообще, для этого есть такая штука как TDataModule, где можно размещать только невизуальные компоненты. Ну и видеть их списком в инспекторе объектов.
Не смешно. Размещать в TDataModule сотни компонентов — по одному на запрос? Ну-ну…

Замечание не к вашей статье, а к комментарию выше. В котором предлагают использовать не видимую форму.

Ну, наверное, писал про «невидимую форму» большой профессионал, чо )))
Ну да, позабыл про TDataModule, каюсь. Хотя и знал. Потому что не пользовался практически.
Ибо основной проект начинался ещё на Delphi первой версии, где TDataModule не было, а во всяких вспомогательных, обычно предназначенных для работы без GUI, все классы доступа к БД (там часто использовалась даже не BDE, а компоненты, специфичные для Interbase) создавались в рантайме, для них контейнер для Design Time был без надобности.
И самое-то главное: Компонеты на форме/модуле — это НЕ потокобезопасно. С ними можно работать только в однопоточном приложении.
Да ну? А если TDataModule создавать в потоке?
Безусловно. Но создавать TDataModule с сотней компонент, допустим, на каждый входящий запрос… Мне кажется — это совсем уж перебор. Процессоры конечно нынче мощные и памяти в компах много, но зачем же так? )))
Всё хорошо в меру. Но я не настаиваю — это моё субъективное мнение.
Так вы и не написали, что вам требовалась потокобеезопасность.
А из контекста следовало, скорее, что ее там и так не требовалось: Delphi чаще всего используется для десктопной разработки GUI-приложений, для а GUI — он обычно уже однопоточный (редко кто заморачивается отдельными циклами обработки сообщений для разных окон).
с GUI работать можно только в главном потоке. Но всё остальное лучше делать в дополнительных.
Десктопное GUI-приложение <> однопоточное приложение
К примеру, в конкретно моём приложении есть приодический опрос сервера на наличие неких изменений. И по необходимости — загрузка этих изменений. Не в главном потоке же этот цикл делать!
К примеру, в конкретно моём приложении есть приодический опрос сервера на наличие неких изменений. И по необходимости — загрузка этих изменений. Не в главном потоке же этот цикл делать!

В принципе — ничто не мешает и в главном потоке: TTimer — на форму, опрос об изменениях — в OnTimer, загрузка изменений — тут надо смотреть конкрено, где это делать (возможный вариант — через PostMessage для своего типа сообщения в его обработчике: для локальных БД такая загрузка обычно происходит быстро, незаметно для пользователя, но если нет, то придется гузить по частям, да). Особенно — если этот поток единственный: в Delphi1 только так и приходилось делать, потому что кооперативная многозадачность (называемая ныне модным словом «асинхронность») там единственным вариантом. А веб-разработчики и сейчас так мучаются.

Но в целом вы меня убедили, что для конретной архитектуры вашего конкретного приложения вариант с компонентами в TForm/TDataModule далек от оптимального по вполне объективным причинам. Собственно, я такую возможность допускал еще и в начальном ответе.
Но вот почему вы не использовали ресурс типа STRINGTABLE (второй вариант в первоначальном ответе) и стандартные методы загрузки ресурсов вместо самопального парсинга — мне это по-прежнему непонятно.
Рационального объяснения этому я пока что не увидел.
PS Вместо того, чтобы писать несколько ответов на один комментарий, лучше, как я считаю, редактировать первоначальный (судя по времени комментариев, у вас на это было достаточно времени). Даже если отвечающий не увидит ваши правки сразу, то он увидит их после спосылки своего ответа, и будет иметь возможность отредактировать уже собственный комментарий.
Я со STRINGTABLE почти не сталкивался. Если не ошибаюсь, его основное назначение — многоязычный интерфейс. И это — таблица строк с цифровыми идетификаторами. Большие SQL-запросы вытягивать в одну строку?!
Я до этого пользовался TFDScript для обновления БД и сами скрипты хранил в ресурсах. В таблицах строк их для этого компонента никак хранить не получится.
Тут сделал по образу и подобию.
Да и в коце-концов — чем удобнее хранение скриптов в STRINGTABLE? Я не нашел ни одного преимущества, только неудобства.
Добавить файл в ресурсы — одна строка. Извлечь оттуда — ну 10 строк (GetStringResource). Всё. И я получаю файл в том формате, в каком его наиболее удобно парсить а не мучаюсь с форматом STRINGTABLE. Вот просто получается реально меньше кода и проще. Вот и все…
Хорошо. Ваши мотивы выбора вполне понятны и для вашей конкретной ситуации весьма разумны.
У меня вопросов по этому поводу больше нет.
Возможно, я бы и сам поступил точно так же, хотя у лично меня довольно много опыта редактирования сверхдлинных строк: связки команд Powershell — они зачастую бывают весьма длинными, хотя потребоваться могут и раз в год, и реже.
Но это — чисто мой опыт, у других его может и не быть.
Да и вариант разбития длинного запроса на несколько строк с последовательными идентификаторами — он тоже возможен.
Но в целом считаю ваш выбор для ваших условий вполне разумным, а статью — полезной в качестве еще одного варианта в копилку возможных решений.
Если приложение активно работает с удаленным сервером и с БД, делать это в GUI потоке — моветон нынче. В Android, например, это вообще строжайше запрещено
Это — заведомо не ваш случай: SQLite — встраиваемая БД.
Когда неудачный запрос вешает поток с GUI это некрасиво выглядит, даже на sqlite.
Когда виснет GUI — это всегда некрасиво )) Тётеньки со светлым цветом волос начинают давить на все кнопки и орать в техподдержку «этавашапрограмма не работает!» :)
Будьте проще — не делайте неудачных запросов.
А если вообще — см. комментарий выше.
Здрасте, приехали! А если инет пропал, удаленный сервер перегружен? )))
Запрос удачный но выполняется долго?
По-вашему — разработчики Android от нечего делать запрещают морозить GUI всеми доступными способами?
Мы тут точно SQLite обсуждаем? Или я уже потреял из виду направление ваших мыслей?
Будьте проще — не делайте неудачных запросов.


А также избегайте ошибок в своих программах. И воще, мышки, становитесь ёжиками :D
Это почему же не наш? ))) Очень даже наш! Встраиваемая — значит локальная. Значит с БД всегда работает только один клиент — наше приложение.
Да и в Android везде используется SQLite и обязательно все запросы к БД идут в отдельных потоках или корутинах.
Так что случай — что ни на есть наш. SQLite после некторых танцев с бубном вполне неплохо живет в потоках.
Всё просто. Я увидел слово «удаленный» и вспомнил антоним к нему — «локальный», и увидел что SQLite именно этим антонимом описывается. Отсюда и комментарий.

Невизуальные компоненты можно без проблем использовать вне основного потока. Да и визуальные тоже можно, если действия не приведут к перерисовке.

Если вам нужны хранимки, то почему бы просто не взять Firebird? Тем более, что используете Delphi.

Тем не менее, для SQLite периодически пишутся всякие костыли по этой теме. Из недавнего CG/SQL: пишете на языке похожем на T-SQL и потом это дело компилится в C-код, который можно подключить.
Выбор СУБД не обсуждается в моём случае. Он предопределен заказчиком. И у него имеются веские аргументы.
А потом, статья ведь не о выборе СУБД совсем :)

Кстати, чем firebird будет лучше mysql/postgresssql? Хочу с paradox наконец-то слезть, но не могу определиться. Надо чтобы и код можно было легко переделать. Пишу на Delphi 5.

Сейчас все 3 упомянутые СУБД активно развиваются, все по-своему хороши.
Для Delphi 5 есть хорошие нативные компоненты FIBPlus для доступа к firebird, вообще вся экосистема древней дельфи (в Fast Report 2.x есть компоненты firebird и т.п.) хорошо дружит c firebird.
У FB есть Firebird Embedded, но есть и отдельно исполняемый EXE.

Плюс FB 2.5 в лёгкой миграции вверх/вниз. Можно использовать Embedded — скопировал/раззиповал программу, запустил, работает. Правда, если в программе ошибка и память портится — можно убить файл БД. Заменой же в программе одной настройки можно уже переключиться на отдельно работающий локальный сервер БД (программа умерла и хрен с ней, файл БД испортить ни один глюк в программе не может) или даже сетевой.

В случае SQLite рост вверх (многопользовательская программа в сети) возможен только через 3-звенку (например mORMot). Это не обязательно хуже, но заметно иначе.

В то же время FB3/4 попёр «в сторону Оракла», быстрее работать с огромными БД, но с заметно усложнившимся администрированием (как поддерживать пользователей класса «ничего не работает», если просто создать системных пользователей FB3 — уже квест ?).

P.S. FIB+ судя по всему умер. Из опенсорсных ещё есть условно живой UIB (хорош по своему, но не TDataSet и даже не во всём похож) и IBX2 (но это уже FPC/Lazarus), и кажется Zeus.

А еще есть написанный на самой Delphi NexusDB, но платный.

Хотя… если D5, то его уже никто не поддерживает, даже JediVCL :-D
P.S. FIB+ судя по всему умер
Какая разница, сейчас развивается или нет (в контексте обсуждения «пишу на delphi 5»). Если бы даже сейчас не умер, D5 всё равно бы не поддерживал.
Друзья! Многие комментарии напоминили мне ивестную историю «Как правильно резать лук в борщ?» )))
На вс. сл. — вопрос не стоит — почему именно борщ и почему именно лук. тут лишь рассказывается как резать этот самый лук в этот самый борщ! ))

как резать этот самый лук в этот самый борщ

С помощью велосипеда, конечно же ;)

Автор статьи перепутал хранимые процедуры и хранилище своего Sql кода.
Класс для использования сохраненных процедур выглядит полезно. Можно хранить в ресурсах, можно в отдельном файле.
Но особого прогресса для науки я не увидел. Советы для начинающих?
Вредные советы, я бы сказал.
Аргументируйте, умник
Статья о том, как сохранить текст в ресурсах, а потом распарсить его сампопальным парсером. Выше уже писали, скрипты в компонентах и так хранятся в ресурсах, уже именованые и готовые к употреблению, без всяких велосипедов. Возможно эта концепция вам не нравится, но она понятны любому дельфи-программисту, в отличии от вашего велосипеда.
Приведенный код а) не рабочий и б) впечатляюще безграмотный. Но если вы мне обьясните, почему я должен тратить на него свое время — могу указать на наиболее вопиющие дыры.
Знаете, мне не интересно ваше мнение, даже если вы мегапрофи. Вы впечатляюще невоспитаны.
И да, я не заставлял ваше величество тратить ваше бесценное время на мою недостойную статью )))
Я вам по секрету скажу, что хранимая процедура — это и есть набор SQL-инструкций.
Разница в том, что она может содержать переменные и более чем один запрос.
Но я потому и назвал статью "Альтернатива хранимым процедурам".
И кто вам пообещал прогреес для науки? Это просто маленький лайфхак ))))
Проблема в том, что Ваше понимание термина «хранимые процедуры» отличается от общепринятого, например в Википедии.
Многим хотелось бы иметь хранимые процедуры в SQLite.
Но не с целью процедуры отдельно от своего кода, а для автоматического выполнения кода при выполнении запросов select/update/insert/delete.
Посмотрите, если будет время, тему «stored procedures for SqLite» на Stackoverflow.
Поэтому ожидалось, что Вы придумали лучшее решение этой проблемы.
А у Вас оказалось решение: где хранить SQL запросы в клиентском приложении.
Я не излагал в статье своё понимание данного термина, кстати.
Насколько я понимаю, автоматически выполнять при «update/insert/delete» — можно триггеры. А триггеры и хранимые процедуры — не одно и то же.
Я знаю, чем хранимая процедура отличается от одиночного SQL-запроса, поверьте. И совершенно согласен с определением в Википедии )). Возможно, название статье дал не самое удачное, согласен.
Решить эту проблему полноценно нельзя в приципе, поскольку в SQLite просто нет хранимых процедур от рождения.
Но я часто использую хранимые процедуры именно просто как хранилище SQL-кода. Просто для того, чтобы отделять SQL-код от Delphi-кода. И именно эту функциональность реализует данный класс. Но никак не полноценные хранимые процедуры, конечно.
И всё-таки интересно — есть ли лучший способ отделить SQL-скрипты от кода? Пока тут прозвучало только одно предложение, но, ИМХО, довольно бредовое ))
Не увидел в тексте ни одной хранимой процедуры…
Потому что их нет в SQlite. В этом и суть. Автор использует просто наименование запроса и передает параметры. Что похоже на хранимые процедуры. Не все возможности хранимых процедур, но всё же.
В Delphi это доступно из коробки. Но это вообще ни как не связано с хранимыми процедурами. Это способ вызова внутри запроса внешнего кода. В Delphi это решается парой кликов мыши, вводом названия функции, написанием нужного кода внутри функции. Функция сама будет подключена провайдером БД (FireDAC) и будет доступна для вызова из запросов.
Например, у меня чуть ли не базовая функция в каждом проекте — LOWER_CASE для кириллицы.
Если подскажете, как использовать расширение для SQLite в Delphi — буду безмерно благодарен
Не спец по Delphi, но там вроде бы .dll/.so — подключить как обычно. Нет?
К сожалению, SQLite3.dll жестко встроена в делфи. Это удобно, поскольку не нужно заботиться о том, чтобы включать длл в пакет инсталляции. Но дико бесит, когда вам нужно использовать альтернативную или просто более новую версию длл.
Я страшно материл Embarcadero, когда увидел КАК они привязали длл.
Чтобы использовать внешнюю длл, нужно отредактировать и перекомпилировать некоторые юниты самого Delphi. Или я чего-то не догоняю или кому-то по рукам надавать хочется )))
Неужели нет sqlite3_create_function? Или sqlite3_enable_load_extension/sqlite3_load_extension?
Нет никакого низкоуровневого доступа к длл. Я использую фреймоврк FireDAC. Он позволяет вообще абстрагироваться от типа СУБД
Упс! Это уже интересно. Спасибо. Не знал о них. Но они вроде не решают вопрос раздельного хранения SQL и Delphi-кода?
Вы можете использовать оба способа ;)
Правда хранить SQL (логику) отдельно от остальной логики — странное решение. Понять, что делает функция, без тела SQL запроса будет неудобно…
Это совсем не странное решение. Мухи отдельно котлеты отдельно. Инкапсуляция.
Я уж не говорю о том, что тексты SQL-запросов в Делфи будут похожи просто на лапшу ))

Поменять sql запрос без правки кода в Delphi все равно не получится, мухи от котлет все равно сильно зависят ;)

Но и sql запросы вперемешку с кодом выглядят неряшливо, да.

Правильно было бы вводить новый тип данных и учить ide его обрабатывать.

Они выглядят не просто неряшливо. Они выглядят ужасающе

Автор статьи не понимает, что вы от него хотите.
Всё это есть и работает даже со штатной библиотекой. Я, вроде как, выше это писал вам уже даже.

В Delphi подключается и dll и so. Первое парой строк, второе через интерфейс

Ну вы меня совсем за идиота держите )))
Вам скинули название функции «sqlite3_create_function», что реализовано в TFDSQLiteFunction. По этому я и сказал, что вы не знаете.

Не нужно ничего перекомпиливать. Достаточно положить библиотеку рядом с ехе.

Ошибаетесь, однако
Нужно отредактировать FireDAC.inc, удалить строку с определением FireDAC_SQLITE_STATIC

Это вы ошибаетесь. Не нужно путать проблему динамической и статической связки и указание конкретной библиотеки (dll) для использования.

хм… Вы правы. Попутал ))
Static linking — the following client libraries are statically linked into the application:
Win32 — sqlite3_x86.obj
Речь идет не о статической или динамической загрузке длл. А о линковке sqlite3.obj в *.exe на этапе сборки (встраивании) либо и использовании внешней длл.
Буду вам благодарен, если объясните — как отказаться от встравивания sqlite3.obj в exe. Возможно я туплю, но так и не понял, как это делается в Delphi 10.3 без редактирования исходников.
Любой SQL-запрос вполне можно считать урезанной хранимой процедурой, если он где-то хранится )))
автор, не боитесь, что пользователь редактором ресурсов исправит файл с SQL запросами как захочет?
Чего бояться, если база SQLite лежит локально, доступ в неё не подразумевает никакой авторизации. Пользователь любым SQLite-браузером может залезть в любую таблицу и исправить что захочет.

Для Sqlite можно использовать AES-256 сквозное шифрование. Что не позволит даже структуру таблиц узнать без мастер-ключа.

Правда запросы можно и не только в ресурсах поправить, но и в коде, через дизассемблер.

Можно поставить breakpoint на sqlite3_key_v2 и увидеть ключ в отладчике. Так что это всё баловство…

Ну, точку останова можно поставить и в место, где конструируется запрос (без чтения из ресурса) и заменить строку на нужную. Так что стандартный вариант так же не спасает ни от чего.

Именно так. Поэтому и бессмысленно задавать вопрос «автор, не боитесь, что...»
Да просто человеку, видимо, хотелось очень прокомментировать, вот он и придумал ситацию со страшным хакером )))
Безусловно, там где нужна привтность и конфиденциальность — такое не прокатит. Но я не клинт-банк разрабатываю ))
Хранимые процедуры конкретно в SQLite не так чтобы и очень необходимы, просто из-за того что этот движок локальный. По факту функционал хранимки Вы реализуете кодом в дельфи, вы просто выгребаете данные в программу и дальше начинаете работать с ними.
Хранимка же это посути та же программа только работающая на стороне сервере, т.е вы просто кидаете на сервер что вам надо сделать с данными и дальше оно там отрабатывает, при этом нет никакого обмена данными с клиентом, ожидания выполнения того или иного запроса, программа отрабатывает на сервере и вы только получаете результат выполнения.
Всё верно. В названии я упомянул хранимые процедуры неудачно.
Я переводил проект с MSSQL Express на SQLite (!) и пришлось переносить код хранимых процедур в обычные запросы. В процессе этой работы и написал данный класс. Оттого он у меня и ассоциируется с процедурами. Но это конечно неверно.
Это один из способов вынести код SQL-запросов в отдельное хранилище. Но никак не полноценные хранимые процедуры. Вы правы.

В названии я упомянул хранимые процедуры неудачно.

Никто не мешает изменить заголовок, да и сам текст статьи, если требуется. На данный момент заголовок сильно кликбейтный.

А почему не хранить запросы в таблице?

Оставлю свои 5 копеек. Спасибо за статью, решение элегантное и избавляет от необходимости "лепить" текст скрипта в pas-файле, особенно, если скрипты большие, на страницу или больше. Легко редактировать текст скриптов, легко отлаживать, легко копировать из редактора. Одни плюсы. Всех "специалистов", которые рассказывают, что не надо такие скрипты и sqlite не для этого, шлите лесом.

Теперь (с выходом Delphi 12) будет возможность писать скрипты в inc файлах. Это можно и до 12 версии, но теперь можно будет писать без кучи конкатенаций строк.

В 12 версии появилась поддержка многострочных строк.

var Str := '''
  SELECT * FROM table
  WHERE id = 2
  ORDER BY name
  ''';

Ну и собственно, можно будет писать вот такое

var SQL := {$INCLUDE UserList.sql};

//содржимое файла UserList.sql
'''
SELECT * FROM table
WHERE id = 2
ORDER BY name
'''

Sign up to leave a comment.

Articles