Матрёшка: работа с базами данных

Модуль SQL Матрёшки предоставляет простой и независимый от базы данных интерфейс работы с реляционными базами данных. В настоящее время поддерживаются базы данных Oracle, PostgreSQL и SQLite3, а так же планируется поддержка MySQL и InterBase/FireBird. При разработке API особое внимание уделялось обеспечению простоты использования без серьёзного вреда функциональности и производительности. Ещё одной важной особенностью является независимость API от конкретных типов данных, за счёт использования контейнеров значений произвольных типов (см. статью Матрёшка: контейнеры значений произвольных типов), т.е. API позволяет выполнять обмен не только стандартными данными, но и специфичными для приложения и базы данных (например, географическими объектами) эффективным образом и с соблюдением типизации и контролем типов данных во время исполнения.

API состоит всего из двух тэговых типов данных — SQL.Databases.SQL_Database и SQL.Queries.SQL_Query. Первый представляет собой абстракцию базы данных и позволяет управлять соединением с сервером базы данных. Второй есть абстракция SQL запроса, позволяющая подготовить запрос, назначить значения параметрам, выполнить запрос и получить результаты.

Подключение драйверов сервера базы данных

Поскольку модуль обеспечиваает взаимодействие с различными базами данных, первым рассматриваемым вопросом будет подключения драйверов сервера базы данных. Для того, что бы драйвер безы данных был подключён к приложению необходимо где‐то в коде приложения подключить пакет фабрики этого драйвера, например, для Oracle, PostgreSQL и SQLite3 это будет соответственно:


          

          
with Matreshka.Internals.SQL_Drivers.Oracle.Factory;
with Matreshka.Internals.SQL_Drivers.PostgreSQL.Factory;
with Matreshka.Internals.SQL_Drivers.SQLite3.Factory;

Можно подключить ровно столько модулей драйверов сколько это требуется для приложения. Они никоим образом не мешают друг другу и могут использоваться одновременно.

Соединение с сервером базы данных

Для управления соединением с сервером базы данных необходимо необходимо создать и инициализировать объект типа SQL.Databases.SQL_Database. Для этого в пакете SQL.Databases есть подпрограмма Create, принимающая два строковых аргумента: имя модуля драйвера базы данных и параметры драйвера. Строка параметров сильно зависит от конкретного сервера, поэтому за полной информацией лучше обратиться к документации на конкретный модуль драйвера базы данных. Ниже приведены примеры задания имени драйвера и параметров для каждой из трёх рассматриваемых баз данных:


          

          
   DB_Driver : constant League.Strings.Universal_String
     := League.Strings.To_Universal_String ("OCI");
   DB_Options : constant League.Strings.Universal_String
     := League.Strings.To_Universal_String ("scott/tiger@db");
   DB_Driver : constant League.Strings.Universal_String
     := League.Strings.To_Universal_String ("POSTGRESQL");
   DB_Options : constant League.Strings.Universal_String
     := League.Strings.To_Universal_String
         ("user='me' password='my' dbname='db'");
   DB_Driver : constant League.Strings.Universal_String
     := League.Strings.To_Universal_String ("SQLITE3");
   DB_Options : constant League.Strings.Universal_String
     := League.Strings.To_Universal_String ("test.db");

На этом все зависимые от базы данных функции закончились, поэтому все примеры далее будут одинаковыми для всех баз данных (конечно же если явно не указано обратное).

Итак, настало время инициализировать объект SQL.Databases.SQL_Database и установить соединение с базой данных:


          

          
declare
   D : aliased SQL.Databases.SQL_Database
     := SQL.Databases.Create (DB_Driver, DB_Options);

begin
   D.Open;

Создание и выполнение простого запроса

Теперь пришло время создать и инициализировать объект для выполнения запросов и получения данных. Для этого достаточно выполнить подпрограмму Create у объекта‐базы данных:


          

          
declare
   Q : SQL.Queries.SQL_Query := D.Query;

begin

Созать объект для выполения запростов можно только если соединение с базой данных было установлено.

Рассмотрение выполнения SQL запросв начнём мы с самого простого — запроса без параметров и возвращаемых значений. В качестве демонстрации такового создадим в базе данных таблицу данных:


          

          
   Q.Prepare
    (+"CREATE TABLE point (x INTEGER, y CHARACTER VARYING, z FLOAT)");
   Q.Execute;

Подпрограмма Prepare выполняет подготовку запроса к выполнению. Единожды подготовленный запрос может использоваться многократно, при этом часть операций по его подготовке уже не будет выполняться повторно, что может существенно улучшить производительность в высоконагруженных приложениях.

Подпрограмма Execute непосредственно выполняет подготовленный запрос.

Запросы с параметрами

Как правило не все параметры запроса известны в момент сборки приложения. В виду того, что SQL запрос представляет из себя текстовую строку очень часто приложение «собирает» конкретный запрос с помощью конкатенации строк запроса и данных. А в результате получается уязвимо к атакам типа SQL внедрения. Для предотвращения самой возможности подобных уязвимостей Матрёшка предоставляет возможность выполнения параметризированных запросов. Суть заключается в том, что в составе строки запроса никогда не указываются какие‐либо значения, а вместо них используются именованные параметры. После подготовки параметризованного запроса задаются значения необходимых параметров и только после этого производится выполнение подготовленного запроса. В целях повышения эффективности можно сбросить значения параметров и задать новые, не выполняя повторной подготовки запроса. В качестве примера вставим строку в созданную таблицу данных:


          

          
   Q.Prepare (+"INSERT INTO point (x, y, z) VALUES (:x, :y, :z)");
   Q.Bind_Value (+":z", League.Holders.Floats.To_Holder (4.5));
   Q.Bind_Value (+":y", League.Holders.To_Holder (+"xyz"));
   Q.Bind_Value (+":x", League.Holders.Integers.To_Holder (5));
   Q.Execute;

Для передачи значений параметра используются контейнеры со значением произвольного типа. Так, в качестве параметра x передаётся значение целого типа, параметра y — строка, а параметра z — вещественное значение. Очередность задания параметров не имеет значения. Все неинициализированные параметры имеют по умолчанию значение null.

Запрос с результатом

Конечно же чаще всего используются запросы на выборку данных из базы. Подготовка и выполнение таковых ничем не отличаются от рассмотренного выше, поэтому здесь сконцентрируемся на извлечении полученных результатов. Объект SQL_Query предоставляет для этого две функции — Next и Value. Функция Next загружает данные следующей строки результата запроса и возвращает True если таковая успешно загружена. В противном случае возвращается False. Функция Value возвращает значение указанного поля текущей строки результата запроса. Например, выведем содержимое созданной нами таблицы данных:


          

          
   Q.Prepare (+"SELECT x, y, z FROM point");
   Q.Execute;

   while Q.Next loop
      Ada.Wide_Wide_Text_IO.Put_Line
       (Integer'Wide_Wide_Image
         (League.Holders.Integers.Element (Q.Value (1)))
          & ":"
          & League.Holders.Element (Q.Value (2)).To_Wide_Wide_String
          & ":"
          & Float'Wide_Wide_Image
             (League.Holders.Floats.Element (Q.Value (3))));
   end loop;

Аналогично случаю с параметрами запроса, значения возвращаются в виде контейнера значения произвольного типа. Если поле запроса содержит значение null, то функция контейнера Is_Empty вернёт True.

Управление транзакциями

Управление транзакциями осуществляется с помощью операций объекта SQL_Database. Подпрограмма Transaction открывает явную транзакцию. Подпрограмма Rollback откатывает все сделанные изменения. А подпрограмма Commit фиксирует сделанные изменения, конечно же если нет причин выполнить откат транзакции. Закрытие соединения с базой данных

Для того, что бы закрыть соединение с базой данных необходимо вызвать подпрограмму Close у объекта SQL_Database:


          

          
   D.Close;

Заключение

Конечно же фактический API предлагает несколько дополнительных подпрограмм для упрощения решения частных задач; тем не менее, описанного более чем достаточно для успешного использования модуля SQL.


Автор: Вадим Годунко
Дата: 28.07.2012