Комментарии 138
На сегодня я знаю только одну ORM, которая действительно умеет в SQL. Остальное может только в `Hello, World!`.
Однако в примерах простейший SQL, который действительно лучше генерить с помощью ORM.
На сегодня я знаю только одну ORM, которая действительно умеет в SQL.
Посмотрите как она ищет места в SQL-запросе куда нужно добавить долларовые ($1) аргументы - она просто делает поиск вместо синтаксического разбора
Только не всегда ORM — это оптимальное решение. Т.к. в таком случае приходится оперировать объектами, что во многих случаях не нужно.
Здесь, возможно, нужно иметь некоторый опыт в разработке на Delphi, чтобы понять. Крупные проекты работают совсем иначе, чем «программы» на других языках. Так, когда вообще не нужно описывать структуру таблиц, но иметь возможность добавлять, изменять и удалять данные и даже если структура таблицы изменяется.
Многие крупные проекты в делфи работают без бэкенда, напрямую используя подключение к БД. А ORM, и вообще CRUD и прочее появились с веб-проектами.
Имея прямое подключение к БД имеется прямой доступ к таблицам и данным. В делфи имеются огромные возможности для работы с БД в дизайнтайме (в режиме дизайна проекта). Для того, чтобы написать крупный проект с БД можно обойтись без вообще написания кода. Достаточно положить на форму нужные таблицы из списка компонент. Выбрать подключение, создать либо компонент для получения данных из таблицы (напрямую), либо компонент, в котором мы пишем запрос (любой сложности). Указать куда отображать содержимое. Если имеется прямое подключение к таблице, мы можем манипулировать ею как хотим (CRUD). Если мы используем свой запрос, то если он не сложный, то мы тоже имеем сразу возможность манипулировать данными, если запрос сложный, то требуется, либо описать запросы редактирования, либо редактировать вручную. За исключением последнего, мы вообще ещё не прикасались к редактору кода.
Естественно ни кто не мешает создавать приложение так, как создаются они в других языках и средах. Использовать ORM, или сразу REST фреймворк.
Спасибо автору!
Появилось сильное желание потрогать/вспомнить как там Delphi поживает.
Последняя версия Delphi - Delphi 7. А после уже не delphi, а выкидыш. Почему, когда в версии 10.3.1 пути прописаны и по ним все находит, а в 10.3.3 уже не находит. Вот путь прописан, вон юнит лежит по этому пути! Сука, не находит! Хочется вырвать разрабам руки, а не деньги платить за этот выкидыш...
Я на 5-й до сих пор пишу. Есть смысл на 7-ю перейти?
Тут я даже не знаю что и сказать, я конечно очень рад тому что в 2003 году Delphi 7 была достаточно хороша, что ее досихпор считают "правильной" и используют в продакшене. Но тем не менее язык развивается, и хотя бы из-за Advanced Records, Helpers, и юникодных строк имеет смысл посмотреть на новые версии.
Последняя версия Delphi — Delphi 7.
VCL не юникодная, generics нет, экзешники умеет только 32-битные генерировать, перед запуском не забудь сохранить проект иначе может повиснуть и ты лишишься части сделанной работы. Ну да, идеал и вершина развития.
Нечто подобное делал когда-то я, только на python
Мой проект подключался к разным схемам в качестве Read-Only пользователя, и при этом не имел собственной БД. Эти схемы содержали с большего одни и те же данные (в смысле решали одну и ту же задачу), просто разложены они были порой весьма причудливо. Запросы в том проекте у меня были довольно мозговзрывающие -- я пытался выжать максимум из БД. Даже хинты были в ходу -- Oracle неправильно оценивал cardinality и строил дикий план
Так вот, мой вариант принципиально отличается только тем, что весь SQL запихивать в один файл было смерти подобно. Соответственно, имя функции у меня являлось именем файла
И API у меня был почти идентичным. Разве что наверху был ещё один слой абстракции, но сути это не меняет
Организация 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, то это гораздо проще сделать в случае одного файла.
Выигрыш от многофайловости в возможности правки запросов без перекомпиляции программы. Хотя это можно сделать и поместив запросы в один файл, но с большими запросами будет неудобно. Я в таком случае разбивал большие запросы на функциональные части, давал им имена, а в рантайме подменял имена на запросы
Но мне, как программисту, когда-то давно активно работавшему с Delphi, его использование кажется странным.
Потому что есть пара других, более стандартных вариантов.
1. Если есть возможность использовать форму (TForm), хотя бы — невидимую, то на нее можно в Design Time накидать компонентов, подобных изначальному TQuery (для SQLite, если я не ошибась, это должно быть TFDQuery), связанных с нужным подключением (TFDConnection), и через Object Inspector (или как он там сейчас называется) установить все нужные тексты запросов в свойстве SQLQuery. А потом — использовать для вызова каждого запроса предназначенный для него компонент.
2. Если уж формы с компонентами использовать нельзя, то для хранения строк в ресурсах имеет смысл использовать специально предназначенный для этого тип STRINGTABLE: в нем строки изначально сопоставляются с целыми числами — номерами, так что в программе для получения нужных строк можно использовать системные средства (первый пример, который мне попался в поиске), а не свой велосипед.
Предложенное вами решение — крайне неудобно, ИМХО
В форме компоненты запросов можно и визуально сгруппировать в соответствии с их назначением.
Компонентам можно дать значимые имена: эти имена — такие же идентификаторы, как и любые другие имена переменных, полей, свойств и т.д. В Object Inspector раньше был (и, надеюсь, и сейчас его не выпилили) список компонентов, в котором можно было выбрать компонент по его имени.
А для STRINGTABLE можно вместо числовых индексов использовать идентификаторы, если их предварительно определить через #define в том же файле ресурсов.
То есть, удобство использования альтернативных вариантов можно повысить.
Впрочем, я не настаиваю. Мне удобнее — так. Но на вкус и цвет… :)
Прямо в точку, насчёт редактирования! При большом числе компонентов, часто проще отредактировать форму открыв dfm в notepade и найдя нужное место обычным поиском.
Редактировать dfm руками имеет смысл только в случае критической ошибки, ИМХО.
Так же как и dproj
А в чем хакерствр? Dfm такой же plain text как html, xml или json.
Одним словам — производитель не предусматривал такой сценарий.
Безусловно, и dpr и dproj и dfm можно править руками. Но лучше делать это только в крайнем случае. Я не раз получал совершенно неожиданные и непредсказуемые разультаты от вполне безобидных правок этих файлов.
И спустя какое-то время получил странное поведение проекта в design-time. Не создавалась главная форма при открытии проекта. При этом — никаких ошибок.
Но вот только ссылки свойств компонент на других формах на компоненты главной формы при сохранении стали затираться. Например — ссылка на StyleBook.
И я только через несколько месяцев понял, что виноват безобидный комментарий в файле dpr
Выглядит как ошибка в Delphi, это бы описать и отправить разработчикам. А так выходить изотерика какая-то: есть странное поведение причины которого не ясны.
С файлом 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.
И это далеко не самый сложный пример
Если убрать комментарии — форма создается.
Delphi 10.3 Upd. 3
Это крайне сомнительно. Что показывает отладчик? До строчки доходит?
Эм. А в чем проблема открыть форму в проекте?
Например — ссылка на компонент StyleBook. Он как правило, один на приложение и размещается на главной форме. А остальные формы на него ссылаются.
Таким образом, редактируемая форма теряет стиль оформления. И это происходит тихо и незаметно, безо всяких ошибок )). Хорошо, если тестировщик заметит до того, как оно уйдет в продакшн
Меня больше всег раздражают не ошибки в IDE, а неполнота документации. Вот это — реальная беда. Я в последние пру лет активно использую в проектах Binding. Удобно. Часто вообще код писать не нужно при работе с БД — только SQL-запросы. Но он крайне плохо документирован. И на форумах про него днем с огнем ничего не найдешь… Обидно
В 99% случаях не нужно лезть через окно — есть дверь. Просто я о ней не знаю :)
Ибо основной проект начинался ещё на Delphi первой версии, где TDataModule не было, а во всяких вспомогательных, обычно предназначенных для работы без GUI, все классы доступа к БД (там часто использовалась даже не BDE, а компоненты, специфичные для Interbase) создавались в рантайме, для них контейнер для Design Time был без надобности.
А из контекста следовало, скорее, что ее там и так не требовалось: Delphi чаще всего используется для десктопной разработки GUI-приложений, для а GUI — он обычно уже однопоточный (редко кто заморачивается отдельными циклами обработки сообщений для разных окон).
К примеру, в конкретно моём приложении есть приодический опрос сервера на наличие неких изменений. И по необходимости — загрузка этих изменений. Не в главном потоке же этот цикл делать!
В принципе — ничто не мешает и в главном потоке: TTimer — на форму, опрос об изменениях — в OnTimer, загрузка изменений — тут надо смотреть конкрено, где это делать (возможный вариант — через PostMessage для своего типа сообщения в его обработчике: для локальных БД такая загрузка обычно происходит быстро, незаметно для пользователя, но если нет, то придется гузить по частям, да). Особенно — если этот поток единственный: в Delphi1 только так и приходилось делать, потому что кооперативная многозадачность (называемая ныне модным словом «асинхронность») там единственным вариантом. А веб-разработчики и сейчас так мучаются.
Но в целом вы меня убедили, что для конретной архитектуры вашего конкретного приложения вариант с компонентами в TForm/TDataModule далек от оптимального по вполне объективным причинам. Собственно, я такую возможность допускал еще и в начальном ответе.
Но вот почему вы не использовали ресурс типа STRINGTABLE (второй вариант в первоначальном ответе) и стандартные методы загрузки ресурсов вместо самопального парсинга — мне это по-прежнему непонятно.
Рационального объяснения этому я пока что не увидел.
PS Вместо того, чтобы писать несколько ответов на один комментарий, лучше, как я считаю, редактировать первоначальный (судя по времени комментариев, у вас на это было достаточно времени). Даже если отвечающий не увидит ваши правки сразу, то он увидит их после спосылки своего ответа, и будет иметь возможность отредактировать уже собственный комментарий.
Я до этого пользовался TFDScript для обновления БД и сами скрипты хранил в ресурсах. В таблицах строк их для этого компонента никак хранить не получится.
Тут сделал по образу и подобию.
Да и в коце-концов — чем удобнее хранение скриптов в STRINGTABLE? Я не нашел ни одного преимущества, только неудобства.
Добавить файл в ресурсы — одна строка. Извлечь оттуда — ну 10 строк (GetStringResource). Всё. И я получаю файл в том формате, в каком его наиболее удобно парсить а не мучаюсь с форматом STRINGTABLE. Вот просто получается реально меньше кода и проще. Вот и все…
У меня вопросов по этому поводу больше нет.
Возможно, я бы и сам поступил точно так же, хотя у лично меня довольно много опыта редактирования сверхдлинных строк: связки команд Powershell — они зачастую бывают весьма длинными, хотя потребоваться могут и раз в год, и реже.
Но это — чисто мой опыт, у других его может и не быть.
Да и вариант разбития длинного запроса на несколько строк с последовательными идентификаторами — он тоже возможен.
Но в целом считаю ваш выбор для ваших условий вполне разумным, а статью — полезной в качестве еще одного варианта в копилку возможных решений.
А если вообще — см. комментарий выше.
Запрос удачный но выполняется долго?
По-вашему — разработчики Android от нечего делать запрещают морозить GUI всеми доступными способами?
Будьте проще — не делайте неудачных запросов.
А также избегайте ошибок в своих программах. И воще, мышки, становитесь ёжиками :D
Да и в Android везде используется SQLite и обязательно все запросы к БД идут в отдельных потоках или корутинах.
Так что случай — что ни на есть наш. SQLite после некторых танцев с бубном вполне неплохо живет в потоках.
Невизуальные компоненты можно без проблем использовать вне основного потока. Да и визуальные тоже можно, если действия не приведут к перерисовке.
Тем не менее, для SQLite периодически пишутся всякие костыли по этой теме. Из недавнего CG/SQL: пишете на языке похожем на T-SQL и потом это дело компилится в C-код, который можно подключить.
А потом, статья ведь не о выборе СУБД совсем :)
Кстати, чем firebird будет лучше mysql/postgresssql? Хочу с paradox наконец-то слезть, но не могу определиться. Надо чтобы и код можно было легко переделать. Пишу на Delphi 5.
Для Delphi 5 есть хорошие нативные компоненты FIBPlus для доступа к firebird, вообще вся экосистема древней дельфи (в Fast Report 2.x есть компоненты firebird и т.п.) хорошо дружит c firebird.
Плюс FB 2.5 в лёгкой миграции вверх/вниз. Можно использовать Embedded — скопировал/раззиповал программу, запустил, работает. Правда, если в программе ошибка и память портится — можно убить файл БД. Заменой же в программе одной настройки можно уже переключиться на отдельно работающий локальный сервер БД (программа умерла и хрен с ней, файл БД испортить ни один глюк в программе не может) или даже сетевой.
В случае SQLite рост вверх (многопользовательская программа в сети) возможен только через 3-звенку (например mORMot). Это не обязательно хуже, но заметно иначе.
В то же время FB3/4 попёр «в сторону Оракла», быстрее работать с огромными БД, но с заметно усложнившимся администрированием (как поддерживать пользователей класса «ничего не работает», если просто создать системных пользователей FB3 — уже квест ?).
P.S. FIB+ судя по всему умер. Из опенсорсных ещё есть условно живой UIB (хорош по своему, но не TDataSet и даже не во всём похож) и IBX2 (но это уже FPC/Lazarus), и кажется Zeus.
А еще есть написанный на самой Delphi NexusDB, но платный.
Хотя… если D5, то его уже никто не поддерживает, даже JediVCL :-D
На вс. сл. — вопрос не стоит — почему именно борщ и почему именно лук. тут лишь рассказывается как резать этот самый лук в этот самый борщ! ))
Класс для использования сохраненных процедур выглядит полезно. Можно хранить в ресурсах, можно в отдельном файле.
Но особого прогресса для науки я не увидел. Советы для начинающих?
Приведенный код а) не рабочий и б) впечатляюще безграмотный. Но если вы мне обьясните, почему я должен тратить на него свое время — могу указать на наиболее вопиющие дыры.
Разница в том, что она может содержать переменные и более чем один запрос.
Но я потому и назвал статью "Альтернатива хранимым процедурам".
И кто вам пообещал прогреес для науки? Это просто маленький лайфхак ))))
Многим хотелось бы иметь хранимые процедуры в SQLite.
Но не с целью процедуры отдельно от своего кода, а для автоматического выполнения кода при выполнении запросов select/update/insert/delete.
Посмотрите, если будет время, тему «stored procedures for SqLite» на Stackoverflow.
Поэтому ожидалось, что Вы придумали лучшее решение этой проблемы.
А у Вас оказалось решение: где хранить SQL запросы в клиентском приложении.
Насколько я понимаю, автоматически выполнять при «update/insert/delete» — можно триггеры. А триггеры и хранимые процедуры — не одно и то же.
Я знаю, чем хранимая процедура отличается от одиночного SQL-запроса, поверьте. И совершенно согласен с определением в Википедии )). Возможно, название статье дал не самое удачное, согласен.
Решить эту проблему полноценно нельзя в приципе, поскольку в SQLite просто нет хранимых процедур от рождения.
Но я часто использую хранимые процедуры именно просто как хранилище SQL-кода. Просто для того, чтобы отделять SQL-код от Delphi-кода. И именно эту функциональность реализует данный класс. Но никак не полноценные хранимые процедуры, конечно.
И всё-таки интересно — есть ли лучший способ отделить SQL-скрипты от кода? Пока тут прозвучало только одно предложение, но, ИМХО, довольно бредовое ))
Например, у меня чуть ли не базовая функция в каждом проекте — LOWER_CASE для кириллицы.
Я страшно материл Embarcadero, когда увидел КАК они привязали длл.
Чтобы использовать внешнюю длл, нужно отредактировать и перекомпилировать некоторые юниты самого Delphi. Или я чего-то не догоняю или кому-то по рукам надавать хочется )))
Правда хранить SQL (логику) отдельно от остальной логики — странное решение. Понять, что делает функция, без тела SQL запроса будет неудобно…
Я уж не говорю о том, что тексты SQL-запросов в Делфи будут похожи просто на лапшу ))
Автор статьи не понимает, что вы от него хотите.
Всё это есть и работает даже со штатной библиотекой. Я, вроде как, выше это писал вам уже даже.
В Delphi подключается и dll и so. Первое парой строк, второе через интерфейс
Не нужно ничего перекомпиливать. Достаточно положить библиотеку рядом с ехе.
Это вы ошибаетесь. Не нужно путать проблему динамической и статической связки и указание конкретной библиотеки (dll) для использования.
Win32 — sqlite3_x86.obj
Буду вам благодарен, если объясните — как отказаться от встравивания sqlite3.obj в exe. Возможно я туплю, но так и не понял, как это делается в Delphi 10.3 без редактирования исходников.
Для Sqlite можно использовать AES-256 сквозное шифрование. Что не позволит даже структуру таблиц узнать без мастер-ключа.
Правда запросы можно и не только в ресурсах поправить, но и в коде, через дизассемблер.
Хранимка же это посути та же программа только работающая на стороне сервере, т.е вы просто кидаете на сервер что вам надо сделать с данными и дальше оно там отрабатывает, при этом нет никакого обмена данными с клиентом, ожидания выполнения того или иного запроса, программа отрабатывает на сервере и вы только получаете результат выполнения.
Я переводил проект с 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
'''
Delphi и SQLite. Альтернатива хранимым процедурам