Регистрация | Войти
Lisp — программируемый язык программирования

Postmodern

Перевод на русский язык

Postmodern это библиотека Common Lisp для взаимодействия с базами данных PostgreSQL. Возможности:

  • Эффективное взаимодействие с сервером баз данных без необходимости внешних библиотек.
  • Поддержка UTF-8 в реализациях Lisp'а поддерживающих юникод
  • Синтаксис для смешивания и SQL и Lisp кода
  • Удобная поддержка "prepared statements" и хранимых процедур
  • Метакласс для простого DAO (database-access objects)

Наибольшие различия между этой библиотекой и CLSQL/CommonSQL в том, что Postmodern не переносим между различными реализациями SQL (библиотека включает в себя нестандартные Postgres функции), а также в расширениях, например, lispy SQL и DAO имеют совершенно другой стиль. Эта библиотека была написана, потому что CLSQL, на мой взгляд, не очень хорош, однако вы можете думать по-другому.

Зависимости

Библиотека зависит от usocket (за исключением SBCL и ACL, где используется встроенная библиотека сокетов), md5, closer-mop, bordeaux-threads если вы хотите иметь потокобезопасный пул соединений и CL+SSL необходим для SSL соединений.

Postmodern разделен на 4 различных пакета, некоторые из них могут быть использованы по отдельности. Simple-date является базовой реализацией объектов "Дата" и "Время" и используется для сохранения и получения связанных со временем SQL типов. CL-postgres библиотека нижнего уровня для соединения с БД postgresql через сокет. S-SQL используется для компиляции s-выражений в строки SQL кода, экранируя любые Lisp выражения внутри, и делающая это по мере возможности во время компиляции. Наконец, Postmodern это библиотека, которая собирает все вместе в один удобный интерфейс.

Лицензия

Postmodern опубликован под zlib-style лицензией. Это означает, что вы можете использовать код в любых случаях, за исключением публикации выдавая библиотеку, как свою собственность и публикации модифицированной версии без пометки о модификации.

Скачивание и инсталляция

Добавлю от себя, что пакет доступен из quicklisp репозитария.

(ql:quickload "postmodern")

Последний релиз Postmodern может быть скачать по адресу http://marijnhaverbeke.nl/postmodern/postmodern.tgz, или проинсталлирован с помощью asdf-install.

Git репозитарий с самыми последними изменениями может быть скачан по адресу:

git clone http://marijnhaverbeke.nl/git/postmodern

Вы также можете просмотреть репозитарий на github.

Файл http://marijnhaverbeke.nl/postmodern/postmodern-latest.tgz всегда содержит head текущего репозитария FIXME.

Быстростарт

Этот быстростарт даст вам почувствовать стиль работы с Postmodern. Остальная информация о работе библиотеки находится в справочном руководстве.

Если вы уже установили ее, загрузка и использование символов системы может выглядеть так:

(asdf:oos 'asdf:load-op :postmodern)

(use-package :postmodern)

Если у вас запущен PostgreSQL сервер на локальной машине, с БД 'testdb', именем пользователя 'foucault' и паролем 'surveiller' вы можете создать соединение так:

(connect-toplevel "testdb" "foucault" "surveiller" "localhost")

Данная строка установит соединение, которое будет использоваться любым кодом, за исключением кода обернутого формой with-connection, которая принимает такие же аргументы, но соединение устанавливает только для обернутого кода.

Для начала небольшой тест:

(query "select 22, 'Folie et déraison', 4.5")
;; => ((22 "Folie et déraison" 9/2))

который должен сработать. query основной способ послать запрос к базе данных. Предыдущий запрос может выглядеть так:

(query (:select 22 "Folie et déraison" 4.5))
;; => ((22 "Folie et déraison" 9/2))

В большинстве случаев, строки запросов и списки начинающиеся с ключевых слов могут быть взаимозаменяемыми. Списки будут скомпилированы в SQL. S-SQL руководство описывает синтаксис используемый этими выражениями. Lisp значения встречающиеся в выражении будут автоматически экранированы. В нижнем запросе использовались только константные значения, но также можно использовать и исполняемые выражения.

(defun database-powered-addition (a b)
  (query (:select (:+ a b)) :single)
)

(database-powered-addition 1030 204)
;; => 1234

Последний аргумент :single показывает, что мы хотим получить в результате не список списков (столбцы в строках), а одиночное значение, так как мы знаем что значение будет одно. Другие опции :rows, :row, :column, :alist и :none. Их влияние на результат описано в справочном руководстве.

Необязательно получать все даные запроса в один момент, вы можете пройти по результатам в цикле с помощью макроса doquery.

(doquery (:select 'x 'y :from 'some-imaginary-table) (x y)
  (format t "On this row, x = ~A and y = ~A.~%" x y)
)

А вот перед нами DAO класс:

(defclass country ()
  ((name :col-type string :initarg :name
         :reader country-name
)

   (inhabitants :col-type integer :initarg :inhabitants
                :accessor country-inhabitants
)

   (sovereign :col-type (or db-null string) :initarg :sovereign
              :accessor country-sovereign
)
)

  (:metaclass dao-class)
  (:keys name)
)

Выше мы определили класс, используемый для управления записями в таблице с тремя колонками: name, inhabitants, and sovereign. В простых случаях, информации выше достаточно для определения таблицы:

(dao-table-definition 'country)
;; => "CREATE TABLE country (
;;      name TEXT NOT NULL,
;;      inhabitants INTEGER NOT NULL,
;;      sovereign TEXT,
;;      PRIMARY KEY (name))"
(execute (dao-table-definition 'country))

Это определение нашей таблицы в базе данных. execute работает так же как и query, но не возвращает результат.

Давайте добавим несколько стран:

(insert-dao (make-instance 'country :name "The Netherlands"
                                    :inhabitants 16400000
                                    :sovereign "Beatrix"
)
)

(insert-dao (make-instance 'country :name "Croatia"
                                    :inhabitants 4400000
)
)

Затем, для обновления населения Хорватии, мы делаем так:

(let ((croatia (get-dao 'country "Croatia")))
  (setf (country-inhabitants croatia) 4500000)
  (update-dao croatia)
)

(query (:select '* :from 'country))
;; => (("The Netherlands" 16400000 "Beatrix")
;;     ("Croatia" 4500000 :NULL))

Идем дальше. Для демонстрации чуть больших возможностей S-SQL синтаксиса, предоставляю запрос функцию-утилиты list-tables для перечисления списка таблиц базы данных:

(sql (:select 'relname :from 'pg-catalog.pg-class
      :inner-join 'pg-catalog.pg-namespace :on (:= 'relnamespace 'pg-namespace.oid)
      :where (:and (:= 'relkind "r")
                   (:not-in 'nspname (:set "pg_catalog" "pg_toast"))
                   (:pg-catalog.pg-table-is-visible 'pg-class.oid)
)
)
)

;; => "(SELECT relname FROM pg_catalog.pg_class
;;      INNER JOIN pg_catalog.pg_namespace ON (relnamespace = pg_namespace.oid)
;;      WHERE ((relkind = 'r') and (nspname NOT IN ('pg_catalog', 'pg_toast'))
;;             and pg_catalog.pg_table_is_visible(pg_class.oid)))"

sql - это макрос который компилирует запрос, он может быть полезен для просмотра того, как выглядят запросы после компиляции, особенно когда вы хотите сделать что-то неожиданное.

Как вы заметили, списки начинающиеся с keyword'ов используются для выражения SQL комманд и операторов (списки начинающиеся с чего-либо другого будут выполнены и результат вставлен в запрос). Закавыченные символы - это столбцы или таблицы (keyword'ы тоже могут использоваться, но добавляют двусмысленность). Синтаксис поддерживает подзапросы, множественные join'ы, хранимые процедуры и т.д. Смотрите справочное руководство для S-SQL для полного понимания.

Наконец, вот вам пример использования "prepared statements":

(defprepared sovereign-of
  (:select 'sovereign :from 'country :where (:= 'name '$1))
  :single!
)

(sovereign-of "The Netherlands")
;; => "Beatrix"

defprepared макрос создает функцию, которая принимает столько же аргументов, сколько $X позиций использовано в запроса. Запрос будет распознан и спланирован единожды (* кол-во подключений к БД) и он может быть быстрее особенно для сложных запросов.

(disconnect-toplevel)

Справка

Справочные руководства для различных компонентов postmodern'а содержаться в различных файлах. Для использования библиотеки в наиболее простых случаях вам необходимо только прочесть Postmodern справку и просмотреть S-SQL руководство. simple-date справка описывает "временные" типы включенные в Postmodern, и CL-postrges справка может быть полезна если вы хотите общаться с PostgreSQL сервером на низком уровне.

Часовые пояса

Simple-date не имеет возможностей хранения часовых поясов. Это значит, что использование ее ошибочно, и если вы действительно нуждаетесь в хранении часовых поясов, вы не должны использовать этот тип или подумайте о методе хранения часовых поясов.

В последнее время много работы было сделано в local-time, которая решает ту же задачу, что и simple-date, но также не понимает часовых поясов. 1.0 репозитарий содержит код для интеграции с CL-postgres, хотя возможно он еще не стабилен.

Переносимость

Лисп код в Postmodern'е теоретически может работать в любой реализации, и похоже работает во всех распространенных. Я активно не тестировал новые релизы, но если вы столкнулись с проблемами, пишите мне через список рассылки, и мы попробуем решить их. Реализации, в которых нет метаобъектного протокола, не будут иметь DAO, но остальные возможности библиотеки работать будут (все широкоиспользуемые реализации поддерживают MOP).

Библиотека точно не будет работать с PostgreSQL версиями старше чем 7.4 (библиотека использует клиент-серверный протокол введенные в этой версии). В версиях перед 8.1, получение даты и времени сломано потому что их бинарное представление изменилось. Часть функциональности insert-dao (automatic defaulting of unbound slots FIXME) работает только PostgreSQL 8.2 и выше.

Вещи, которые должны быть реализованы

Это было бы неплохой возможностью, если бы Postmodern могла помогать вам с определением схемы базы данных и, что более важно, обновлять вашу базу данных, когда изменяетс код. Теоретически это не сложно создать функцию, которая бы сравнивала схему на лиспе и состояние базы данных и помогала бы вам интерактивно обновлять базу данных. PostgreSQL имеет полноценную систему интроспекции. К несчастью, реализация требует большой работы, так как база данных содержит достаточно много различных типов сущностей (таблицы, представления, индексы, процедуры, ограничения, последовательности и т.д.), которые могут быть созданы, изменены и удалены разными путями.

Добавлю от себя, что база данных чаще всего может взаимодействовать и взаимодействует с набором приложений, написанных на разных языках/фреймворках, и ограничивать проектирование схемы каким-то ОРМ'ом не самая переспективная идея. Кроме того собственный опыт показывает, что схема проектируется один раз, а после происходит только добавление сущностей (колонок, таблиц, триггеров и т.д.)

Справочное руководство Postmodern

Это справочное руководство для компонента Postmodern, который является частью одноименной библиотеки.

Заметим, что этот пакет также экспортирует database-connection и database-error типы из CL-postgres и несколько операторов из S-SQL.

query, execute и любые другие функции, которые логически требуют взаимодействия с базой данных, генерируют исключение типа database-error, когда что-то идет не так. Как частный случай, ошибки которые разрывают соединение (ошибки сокета, выключение базы данных) будут генерироваться как подтип database-connection-error, предоставляющие :reconnect рестарт для перезапуска операции, вызвавшей ошибку FIXME.

Соединение

class database-connection

Объекты данного типа содержат подключение к базе данных.

function   connect (database user password host &key (port 5432) pooled-p use-ssl) 
→ database-connection

Создает новое подключение к базе данные с указанными пользователем и паролем. Порт по умолчанию 5432, на котором чаще всего работают PostgreSQL сервера. Если pooled-p равно true, соединение будет взято из пула соединений данного типа, если оно там есть, и когда соединение будет разорвано оно будет помещено в пул. use-ssl может быть :no, :yes или :try, как в open-database, и по умолчанию имеет значение *default-use-ssl*.

variable   *default-use-ssl* 

Значение по умолчанию для use-ssl аргумента для функции connect. При определении она устанавливается в :no. Если вы установите в другое значение, проверьте загрузили ли вы CL+SSL библиотеку.

method  disconnect (database-connection) 

Разрывает соединение с базой данных или перемещает его в пул.

function   connected-p (database-connection) 
→ boolean

Возвращает булево значение о том, имеется ли соединение с сервером.

method  reconnect (database-connection) 

Выполняет переподключение. Данное действие не разрешено для соединения из пула - после того как они отключены от БД, они могут быть использованы какими-нибудь другими процессами, и больше не должны быть использованы.

variable   *database*

Переменная для хранения текущей базы данны. Большинство функций и макросов взаимодействуют с базой данных через данную переменную.

macro   with-connection (spec &body body)

Исполняет body с переменной *database*, связанной с соединением, заданным с помощью spec, которая [spec] должна быть списком переданным на вход функции connect.

macro   call-with-connection (spec thunk) 

Это движок для макроса with-connection. Связывает *database* с новымм соединением заданным spec, которое должно быть списоком, который будет подан на вход connect, и запускает без аргументов функцию полученную во втором параметре в новом окружении. Когда функция завершается или генерирует исключение, новое соединение разрывается.

function  connect-toplevel (database user password host &key (port 5432))

Устанавливает переменную *database* в новое соединение. Используйте ее только, если вам необходимо только одно соединение или вы хотите отладиться в REPL'е.

function  disconnect-toplevel () 

Разрывает соединение хранящееся в переменной *database*.

function  clear-connection-pool () 

Разрывает и удаляет все соединения в пуле.

variable   *max-pool-size*

Устанавливает максимальное количество соединений, содержащихся в одном пуле. Один пул содержит все сохраненные соединения с одинаковыми параметрами. По умолчанию NIL, что означает, что максимальное количество не задано.

Запросы

macro   query (query &rest args/format)
→ result

Выполняет заданный запрос, который может быть строкой или S-SQL формой (список начинающийся keyword'ом). Если запрос содержит позиции ($1, $2 и т.д.) их значения будут взяты из остальных аргументов. Если один из аргументов является keyword'ом из перечисленных ниже, он не будет подставлен как значение, но будет влиять на то, какой результат будет возвращен из функции. Может использоваться любой из перечисленных форматов , по умолчанию используется :rows:

:none Игнорировать результат
:lists, :rows Возвращает список списков, каждый элемент списка содержит значения строки.
:list, :row Возвращает одну строку в виде списка.
:alists Возвращает список alist'ов, которые содержат имена столбцов и значения, имена столбцов отображаются как keyword'ы.
:alist Возвращает одну строку как alist.
:str-alists То же, что и :alists, но использует оригинальные имена столбцов.
:str-alist Возвращает одну строку как alist со строковыми именами столбцов.
:plists Возвращает список plist'ов, которые содержат имена столбцов и значения, имена столбцов отображаются как keyword'ы.
:plist Возвращает один строку как alist.
:columnВозвращает один столбец как список.
:single Возвращает одну ячейку (значение).
:single! То же что и :single, но генерирует исключение если количество строк не равно одной.
(:dao type) Возвращает список DAO заданного типа. Имя полей возвращенных запросом должны совпадать со слотами объекта также, как и при query-dao.
(:dao type :single) Возвращает один DAO заданного типа.

Если база данных возвращает информацию о количестве строк, затронутых запросом, например, при обновлении или удалении, то она будет возвращена, как второе значение.

macro   execute (query &rest args)

То же что и query, но вызывается с опцией :none и возвращает количество затронутых строк в первом возвращаемом значении. (Также возвращает количество затронутых строк во втором значении, но использование этого не рекомендуется.)

macro   doquery (query (&rest names) &body body)

Выполняет переданный запрос (строка или список начинающийся с keyword'а), предоставляет цикл по строкам из результата. Тело будет исполняться со значениями из строки связанными с символами имен столбцов. Для цикла над параметризованным запросом, можно указать список, где car запрос, а cdr содержит аргументы. Например:

(doquery (:select 'name 'score :from 'scores) (n s)
  (incf (gethash n *scores*) s)
)


(doquery ((:select 'name :from 'scores :where (:> 'score '$1)) 100) (name)
  (print name)
)

macro   prepare (query &optional (format :rows))
→ function

Создает функцию, которая может быть использована как интерфейс для "prepared statement". Полученный запрос (или строка или S-SQL форма) может содержать позиции, которые выглядят как $1, $2 и т.д. Результирующая функция принимает столько аргументов, сколько позиций было в запросе, выполняет "prepared query", и возвращает результат в заданном формате (доступны форматы такие же как и у query).

Для запросов, которые выполняются достаточно часто, особенно когда они сложны, это может увеличить скорость, если сервер единожды провел "планирование" запроса. Смотрите PostgreSQL руководство для подробностей.

В некоторых случаях, сервер будет ругаться о том, что не может понять типы аргументов с запросе. В данном случае вы должны добавить декларацию типа (или :: конструкция в строке или :type конструктор в S-SQL) для помощи серверу.

macro   defprepared (name query &optional (format :rows))

Это defun-style вариант для prepare. Он задает функцию верхнего уровня для "prepared statement".

macro   defprepared-with-names (name (&rest args) (query &rest query-args) &optional (format :rows))

То же что и defprepared, но разрешает специфицировать имена аргументов функции в соответствии с использованием в запросе.

(defprepared-with-names user-messages (user &key (limit 10))
  ("select * from messages
    where user_id = $1
    order by date desc
    limit $2"
(user-id user) limit
)

    :plist
)

macro   with-transaction ((&optional name) &body body)

Выполняет заданный body в транзакции базы данных, выполняя commit, если body выполнилось без ошибок, и abort в остальных случаях. Необязательный параметр name может быть назначен транзакции, и может быть использован для commit или abort в body до его завершения.

function  commit-transaction (transaction)

Commit для текущей транзакции.

function  abort-transaction (transaction) 

Откат текущей транзакции.

macro   with-savepoint (name &body body)

Может использоваться только в транзакции. Устанавливает savepoint с заданным именем в начале body, и привязывает к одноименному символу handle данной savepoint. В конце body, savepoint освобождается, если не было исключения, иначе происходит откат к savepoint.

function  release-savepoint (savepoint) 

Явно освобождает savepoint.

function  rollback-savepoint (transaction) 

Откат до заданной savepoint.

function   sequence-next (sequence) 
→ integer

Получить следующее значение для последовательности. Идентификатор последовательности может быть как строкой, так и символом, в этом случае он будет преобразован в строку с помощью S-SQL правил.

function   coalesce (&rest arguments) 
→ value

Возвращает первый не-NULL, не-null (как :null) аргумент, или NIL если такового не найдено. Полезно для обеспечения запасного значения для результата запроса, или, когда передается один аргумент, для преобразования :nulls в NIL.

Интроспекция

function   list-tables (&optional strings-p) 
→ list

Возвращает список таблиц текущей базы данных. Если string-p true, имена будут возвращены как строки, иначе как keyword'ы.

function   table-exists-p (name) 
→ boolean

Проверяет, существует ли таблица. Имя может быть строкой или символом.

function   table-description (name) 
→ list

Возвращает список полей таблицы с именем name. Каждое поле - это список из трех элементов: имя, тип, и булево значение о том, может ли поле быть NULL.

function   list-sequences (&optional strings-p) 
→ list

Возвращает список последовательностей текущей базы данных. Если strings-p true, имена будут возвращены как строки, иначе как keyword'ы.

function   sequence-exists-p (name) 
→ boolean

Проверяет, имеется ли данная последовательность. Имя может быть строкой или символом.

function   list-views (&optional strings-p) 
→ list

Возвращает список представлений текущей базы данных. Если strings-p true, имена будут возвращены как строки, иначе как keyword'ы.

function   view-exists-p (name) 
→ boolean

Проверяет, имеется ли данное представление. Имя может быть строкой или символом.

Database Access Objects

Требует чуть лучшего перевода

Postmodern содержит простую систему для определения CLOS классов, которые отображают строки в базе данных. Не следует рассматривать это в качестве полноценной ORM - они имеют свою нишу, и эта ниша выходит за рамки этой библиотеки.

metaclass  dao-class 

В сердце Postmodern DAO системы лежит метакласс dao-class. Он позволяет вам определить классы для DAO, как обычные CLOS классы. Некоторые из слотов данных классов будут ссылаться на столбцы в базе данных. Для указания ссылки на данные, вставте :col-type опцию содержащую S-SQL выражение (полезно, если Вы хотите возможность вывести определение таблицы из определения класса), или просто опцию :column со значением T. Такие слоты могут также принимать опцию :col-default, используемую для предоставления для базы данных значения по умолчанию в S-SQL выражении.

Определение DAO класса поддерживает две дополнительные опции класса: :table-name для указания имени таблицы, на которую ссылается класс (по умолчанию используется имя класса), и :keys для предоставления множества столбцов для первичного ключя таблицы. Если первичного ключа нет, такие операции, как update-dao и get-dao не будут работать.

Простой пример:

(defclass user ()
  ((name :col-type string :initarg :name :accessor user-name)
  (creditcard :col-type (or db-null integer) :initarg :card :col-default :null)
  (score :col-type bigint :col-default 0 :accessor user-score)
)

  (:metaclass dao-class)
  (:keys name)
)

Форма (or db-null integer) используется для указания столбца, который может иметь значения NULL.

При наследовании от DAO классов, множество столбцов потомков будет также содержать столбцы предков. Первичный ключ для такого класса - это объединение его ключей и всех ключей предков. Классы унаследованные от DAO классов должны всегда использовать метакласс metaclass.

Когда DAO создается с помощью make-instance, можно использовать :fetch-defaults keyword параметр, который, если true, запрос будет создан для получения значений по умолчанию для всех слотов, которые имеют значения по умолчанию для столбцов и не связаны с помощью initargs FIXME. В некоторых случаях, например, serial столбцы, которые имеют неявное значение по умолчанию, это работать не будет. Вы можете обойти это создав последовательность вручную и задав значение по умолчанию (:nextval "my_sequence").

Наконец, слоты DAO класса могут иметь опцию :ghost t для указания того, что они призраки. Они заполняются при запросе данных, но не записываются при обновлении или вставке и даже не включаются в определение таблицы. На данный момент используются для таблиц с oid'ом, и выглядят так:

(oid :col-type integer :ghost t :accessor get-oid)
method   dao-keys (class) 
→ list

Возвращает список имен слотов, являющихся первичным ключем DAO класса.

method  dao-keys (dao) 
→ list

Возвращает список имен слотов, являющихся первичным ключем DAO.

method   dao-exists-p (dao) 
→ boolean

Проверяет, существует ли строка с тем же первичным ключом, что и DAO в базе данных. Возвращает NIL, когда любой из ключевых слотов объекта несвязан.

method   make-dao (type &rest args &key &allow-other-keys) 
→ dao

Комбинирует make-instance с insert-dao. Возвращает созданный dao.

macro   define-dao-finalization (((dao-name class) &rest keyword-args) &body body) 

Создает :around-method для make-dao. Тело кода выполняется в лексическом окружении, где переменная dao-name привязана к только что созданному и вставленному dao. Представление DAO в базе данных затем обновляется для отображения изменений, возможных при выполнении body. Полезно для обработки значений слотов с типом serial, которые не известны до insert-dao.

method   get-dao (type &rest keys) 
→ dao

Выбирает DAO из строки, которая имеет значения первичного ключа, или NIL, если необходимая строка не существует. Объекты созданные с помощью этой функции необходимо инициализировать с помощью функции initialize-instance (псоле загрузки значений из базы данных) без аргументов, даже :default-initargs будут пропущены. То же самое касается select-dao и query-dao.

macro   select-dao (type &optional (test t) &rest sort)
→ list

Выбирает DAO для строк в связанной таблице, для который данный test выполняется в T (или S-SQL выражение или строка). Когда передаются аргументы для сортировки, которые также могут быть строкой или S-SQL формой, они используются для сортировки результата. (Примечание, если вам нужна сортировка, вы должны указать test).

(select-dao 'user (:> 'score 10000) 'name)
function   query-dao (type query &rest args) 
→ list

Выполняет заданный запрос (который может быть или строкой или S-SQL выражением) и возвращает резльтат как список DAO заданного типа. Если запрос содержит "позиции" ($1, $2 и т.д.) их значения могут быть получены из оставшихся аргументов. Имена полей возвращаемых запросом должно совпадать с именами слотов DAO класса, или должны быть связаны с помощью with-column-writers.

variable   *ignore-unknown-columns*

Обычно, когда get-dao, select-dao или query-dao находят столбцы в базе данных, которых нет в DAO классах, они генерируют ошибку. Установка данной переменной в true, заставит их игнорировать ошибки.

method   insert-dao (dao) 
→ dao

Вставляет заданный DAO в базу данных. Когда какие-нибудь из слотов в объекте несвязан, он будет установлен в значение по умолчанию в базе данных. (Если они не имеют значений по умолчанию будет ошибка.) Примечание: данная возможность работает только в PostgreSQL >= 8.2. В ранних версиях не вставляйте DAO с несвязанными слотами.

method   update-dao (dao) 
→ dao

Обновляет представление данного DAO в базе данных значениями из объекта. Такая возможность не предусмотрена для таблиц, которые не имеют первичного ключа FIXME.

function   save-dao (dao) 
→ boolean

Пытается вставить заданный DAO используя insert-dao. Если она возвращает ошибку "уникальность ключа", она пытается обновить строка с помощью update-dao. Возможны условия гонки, в которых сторонний процесс удаляет строку, тогда обновление будет безуспешным. Возвращает булево значение, сообщающее: была ли вставлена новая строка.

Эта функция небезопасна при использовании в транзакции - когда строка с данным ключем уже существует, транзакция будет отменена. Используйте save-dao/transaction в данных случаях.

function   save-dao/transaction (dao) 
→ boolean

Действует также как и save-dao, кроме того защищает вставку объекта с помощью savepoint, так возникшая ошибка не отменит транзакцию.

method  delete-dao (dao)

Удаляет заданный DAO из базы данных.

function   dao-table-name (table) 
→ string

Получает имя таблицы связанной с данным DAO классом (или символ имени данного класса).

function   dao-table-definition (table) 
→ string

По заданному DAO классу или его имени функция сгенерирует строку SQL запроса с определением данной таблицы. Это всего лишь простое определение, так что если вам нужна дополнительная функциональность, например индексы или ограничения, вам придется написать свой собственный запрос, чтобы добавить их.

macro   with-column-writers ((&rest writers) &body body) 

Обеспечивает контроль над тем, как get-dao, select-dao, and query-dao читают значения из базы данных. Она обычно не требуется, но может быть использована для уменьшения количества запросов. writers это список альтернативных имен столбцов (строки или символы) и writers, где writers являются символами ссылкой на слот в объекте, или функции, принимающие два аргумента - экземпляр и значение - которые могут быть использованы как сохранить значение в новый экземпляр FIXME. Если какой-либо DAO-выборка функция вызывается в body, и столбцы, соответствующие данному имена встречаются в результате writers используются вместо умолчанию (попробуйте и сохраните значения в слот, который соответствует имени столбца).

Подходящее использование является добавление некоторых нестолбцовых слотов для класса DAO, а также использование query-dao в с with-column-writers формой для запроса дополнительной информации об объектах, и сохранение его в новые экземпляры FIXME.

Определение и создание таблицы

Это может быть полезным: иметь SQL запросы необходимые для построения таблиц приложения, доступных из кода, и делать автоматическую инициализацию базы данных. Следующие макросы и функции позволяют вам сгруппировать множества SQL запросов в символы, с некоторыми ярлыками для общих элементов в декларации таблицы.

macro deftable (name &body definition)

Определяет таблицу. name может быть символом или списком (символ строка). В первом случае, имя таблица будет взято из символа по правилам S-SQL, во втором случае название дано в явном виде. Тело определения может содержать что угодно, что выполняется в строку, а также S-SQL выражения. В этом теле переменные *table-name* и *table-symbol* связаны со своими значениями. Обратите внимание, что определение вычисляется по порядку, так что вы должны сначала создать таблицу, а затем определять индексы для нее.

function create-table (symbol)

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

function create-all-tables ()

Создает все определенные таблицы.

function create-package-tables (package)

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

variables *table-name*, *table-symbol*

Переменные связаны с соответствующими символом и названием, во время выполнения определения таблицы. Могут быть использованы для определения сокращения, как показано ниже.

function !dao-def ()

Может использоваться только в deftable форме. Добавляет результат вызова dao-table-definition c *table-symbol* к определению.

function !index (&rest columns), !unique-index (&rest columns)

Определяет индекс для таблицы. Столбцы передаются как символы или строки.

function !foreign (target-table columns &optional target-columns &key on-delete on-update)

Добавляет внешний ключ таблице. target-table таблицы на которую ключ ссылается, columns - список имен столбцов или одно имя в данной таблице, и если имена столбцов целевой таблицы отличаются, target-columns должен быть списком имен столбцов или одно имя целевой таблицы.

on-delete и on-update аргументы могут быть использованы для указания ON DELETE и ON UPDATE действий, в соответствии с keyword'ами разрешенными в create-table. Обратите внимание, что они не &key аргументы, а выбираются из &rest аргументов во время выполнения, так что они могут быть определены когда target-columns еще не получены.

Справочное руководство S-SQL

Это справочное руководство для S-SQL компонента библиотеки postmodern.

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

Интерфейс

macro sql (form)
→ string

Конвертирует заданную форму (список начинающийся с keyword'а) в стркоу SQL запроса во время компиляции, в соответствии с указанными здесь правилами.

function sql-compile (form)
→ string

Это вариант макроса sql для использования во время выполнения. Он конвертирует заданный список в SQL запрос, используя те же правила, кроме того что символы в этом списке не должны быть закавычены, чтобы интерпретироваться как идентификаторы.

function sql-template (form)

В случаях когда вы нуждаетесь в построении запроса во время выполнения, но вы не хотите чтобы результат все время перекомпилировался, это функция может быть использована для компиляции единожды и сохранении результата. Она принимает S-SQL форму, которая может содержать $$ символы, и возвращает функцию, которая принимает по одному аргументу для каждого $$. Когда вызывается, эта возвращаемая функция строит SQL строку, в которой все позиции $$ будут заменены на значения аргументов.

macro enable-s-sql-syntax (&optional (char #\Q))

Модифицирует текущую readtable для добавления #Q синтаксиса, который будет прочтен как (sql ...). Префикс может быть указан другой.

function sql-escape-string (string)
→ string

Экранирует строку для включения в PostgreSQL запрос.

method sql-escape (value)
→ string

Обобщение sql-escape-string. В зависимости от типа аргумента возвращает корректную строку для добавления в SQL запрос. Символы конвертируются в SQL имена.

variable *standard-sql-strings*

Используется для настройки, будет ли S-SQL использовать стандартные SQL строки (просто заменив #\' на ''), или обратнослэшево экранировать. Установка в NIL всегда безопасно, но если сервер настроен на разрешение стандартных строк (параметр времени компиляции 'standard_conforming_strings' равен 'on', который будет по умолчанию в будущих версиях PostgreSQL), шум в запросах может быть уменьшен установкой значения в T.

variable *escape-sql-names-p*

Определяет, могут ли двойные кавычки добавляются вокруг имени столбца. Может быть T, в этом случае все именя будут экранированы, NIL, в этом случае не будут, или :auto, в этом случае только ключевые слова будут экранированы. Значение по умолчанию: :auto. Будьте осторожными, при связывании в let - так как компиляция SQL как правило, происходит во время компиляции, результат может оказаться не таким, как вы ожидаете.

function sql-type-name (type)
→ string

Создает SQL эквивалент для заданного лисп типа, если он известен. Смотрите типы.

function to-sql-name (name &optional (escape-p *escape-sql-names-p*))
→ string

Конвертирует символ или строку в имя, которое может быть использованы как SQL идентификатор путем преобразования всех неалфавитноцифровых символов в подеркивания. Также нижний регистр имен улучшает вид запросов. Если передается второй аргумент он перекрывает переменную *escape-sql-names-p*.

function from-sql-name (string)
→ keyword

Преобразует строку, которая содержит SQL идентификатор в keyword путем изменения нижнего регистра в верхний и конвертации подчеркиваний в точки.

macro register-sql-operators (arity &rest names)

Определяет простые SQL операторы. arity может быть одним из значений:

:unary (как 'not'),
:unary-postfix (оператор стоит после операнда),
:n-ary (как '+': оператора при одном операнде),
:2+-ary (как '=', который не имеет смысла для одного операнда),
:n-or-unary (как '-', где оператор может быть как унарном случае).

После arity может следовать любое количество операторов либо только keyword'ы, в этом случае нижнерегистровое символ имени будет использован как SQL оператор, или двухэлементный список содержащий keyword и строку имени FIXME.

SQL типы

S-SQL знает SQL эквиваленты для некоторого количество типов лиспа, и определяет несколько дополнительных типов, которые могут быть использованы для обозначения других SQL типов. Следующая таблица отображает соответсвия:

Lisp type SQL type
smallint smallint
integer integer
bigint bigint
(numeric X Y) numeric(X, Y)
float, real real
double-float, double-precision double-precision
string, text text
(string X) char(X)
(varchar X) varchar(X)
boolean boolean
bytea bytea
date date
timestamp timestamp
intervalinterval
type db-null

Это тип, которому принадлежит только keyword :null. Он используется для отображения NULL значений из базы данных.

SQL синтаксис

S-SQL форма преобразуется в запрос в соответствии со следующими правилами:

Списки начинающиеся с keyword'а - это операторы. Если они известны, они разворачиваются так как описано ниже, если не известны - следуя стандартному правилу: operator(arguments, ...) Закавыченные символы или keyword'ы интерпретируются как имена столбцов или таблиц, и преобразуются в строки с помощью t-sql-name.

Определены следующие операторы:

sql-op :+, :*, :%, :&, :|, :||, :and, :or, :=, :/, :!=, :<, :>, :<=, :>=, :^, :union, :union-all, :intersect, :intersect-all, :except, :except-all (&rest args)

Они разворачиваются в инфиксные операторы. По смыслу, они позволяют более чем два аргумента. :- также может использоваться как унарный оператор для отрицательных значений. Следует отметить, что аргументы для :union, :union-all, :intersect и :except должны быть в запросе (:select forms).

sql-op :~, :not (arg)

Унарные операторы для битового и логического отрицаний.

sql-op :~, :~*, :!~, :!~* (string pattern)

Регулярное выражение для операторов сравнения. Восклицательный знак означает "не соответствует", звездочка делает сравнение регистронезависимым.

sql-op :like, :ilike (string pattern)

Простые SQL операторы сравнения (:ilike регистронезависим).

sql-op :desc (column)

Используется для инвертирования значения оператора в :order-by выражении.

sql-op :nulls-first, :nulls-last (column)

Используется для определения положения :null значений в :order-by выражении.

sql-op :as (form name &rest fields)

Присваивает имя для столбца или таблицы в :select форме. Когда заданы fields, они будут добавлены после имени в круглых скобках. Например, (:as 'table1 't1 'foo 'bar) преобразуется в table1 as t1(foo, bar). Когда вам необходимо специфицировать типы для полей, вы можете сделать что-то вроде (:as 'table2 't2 ('foo integer)). Следует отметить, что имена закавычены, типы нет (когда используется sql-compile или sql-template, вы можете опустить кавычки) FIXME.

sql-op :exists (query)

Оператор EXISTS. В качестве аргумента принимает подзапрос и возвращает true или false в зависимости от того вернул ли подзапрос строку.

sql-op :is-null (arg)

Проверяет, равен ли аргумент NULL.

sql-op :in (value set)

Проверяет, существет ли значение во множестве.

sql-op :not-in (value set)

Инвертирует результат действия вышеназванного оператора :in.

sql-op :set (&rest elements)

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

sql-op :[] (form start &optional end)

Значение элемента массива. Если задан end, возвращает срез массива.

sql-op :extract (unit form)

Выделяет поле из date/time значения. Например: (:extract :month (:now)).

sql-op :case (&rest clauses)

Условное выражение. clauses должны иметь форму (test value).

sql-op :between (n start end)

Проверяет лежит ли значение в интервале.

sql-op :between-symmetric (n start end)

Работает так же как и :between, за исключением того, что start необязательно должен быть меньше end.

sql-op :dot (&rest names)

Может быть использовано для комбинирования нескольких имен в имя вида "A.B" для ссылки на столбец таблицы, или таблицы в схеме. Необходимо отметить, что вы также можете использовать символ с точкой.

sql-op :type (form type)

Добавляет декларацию типа к значению, напрмер: "4.3::real". Второй аргумент не выполняется, но предается в sql-type-name для получения идентификатора типа.

sql-op :raw (string)

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

(let* ((test (sql (:and (:= 'foo 22) (:not-null 'bar))))
        (rows (query (:select '* :from 'baz :where (:raw test))))
)

      (query (:delete-from 'baz :where (:raw test)))
      (do-stuff rows)
)

sql-op :select (&rest args)

Создает select запрос. Аргументы разделяются по keyword'ам найденным между ними. Группа аргументов сразу после :select интерпретируется как выражение, которое должно быть выбрано. После нее опционально может следовать :distinct, который заставляет select выбрать только неповотряющиеся строки, или альтернативно :distinct-on за которым следует группа имен столбцов FIXME. Далее следует опциональный keyword :from, за которым следует как минимум одно имя таблицы и затем любое количество join'ов. join выражение начинается с одного из: :left-join, :right-join, :inner-join, :outer-join или :cross-join, затем имя таблицы или подзапрос, затем если применимо keyword :on, и затем форма. join'у может быть предшествовать :natural (опуская :on выражение) для использования "натурального" join. После join'ов опционально может следовать :where с одной последующей формой. И наконец, опционально включаются :group-by и :having. Первый принимает любое количество аргументов, а второй принимает только один аргумент. Например:

(:select (:+ 'field-1 100) 'field-5
  :from (:as 'my-table 'x)
  :left-join 'your-table :on (:= 'x.field-2 'your-table.field-1)
  :where (:not-null 'a.field-3)
)

sql-op :limit (query amount &optional offset)

В S-SQL limit не является частью оператора select, а представляет собой оператор, которые применяется к запросу (такой метод работает лучше при ограничении объединений или пересечений нескольких запросов, сортировки). Оператор ограничивает количество строк в результате в amount, и опционально может перемещать выборку строк на количество позиций переданных в третьем параметре.

sql-op :order-by (query &rest exprs)

Сортирует результат запроса в соответствии с заданным выражением. Смотрите :desc если вы хотите инвертировать сортировку.

sql-op :function (name (&rest arg-types) return-type stability body)

Создает хранимую SQL (не pgplsql) процедуру. arg-types и return-type интерпретируются как типы и не вычисляются. stability должны быть одним из: :immutable, :stable или :volatile (см. руководство Postgres). Например, функция которая получает foobars по идентификатору:

(:function 'get-foobar (integer) foobar :stable (:select '* :from 'foobar :where (:= 'id '$1)))
sql-op :insert-into (table &rest rest)

Вставляет строку в таблицу. Если второй аргумент :set, другие аргументы должны содержать имена полей и значений, иначе второй аргумент должен быть формой :select, которая выполниться в множество значений для вставки. Например:

(:insert-into 'my-table :set 'field-1 42 'field-2 "foobar")

В конце формы :insert-into есть возможность добавить :returning, с последующими списком имен полей или выражениями. Это заставит запрос вернуть значения этих выражений как одну строку.

sql-op :update (table &rest rest)

Обновляет значения в таблице. После имени таблицы должен идти keyword :set и люое количество имен полей и значений, как в :insert-into, а затем опционально keyword :where с последующим условием, и затем keyword :returning с списком имен полей или выражениями указывающими, что необходимо вернуть.

sql-op :delete-from (table &rest rest)

Удаляет строки из таблицы. Может получать аргумент :where, за которым следует условие, и аргумент :returning, за которым следует одно или более выражений, которые будут возвращены для всех удаленных строк.

sql-op :create-table (name (&rest columns) &rest options)

Создает новую таблицу. После имени таблицы следует список определений столбцов, которые являются списками, которые начинаются с имени и друг за другом следуют следующие keyword'ы:

:typeЕдинственное обязательное поле. Специфицирует тип столбца. Используйте тип так (or db-null integer) для указания возможных NULL значений.
:defaultУказывает значение по умолчанию.
:uniqueЕсли аргумент non-nil, значения в столбце должны быть уникальны.
:primary-keyЕсли non-nil, столбец входит в первичный ключ.
:checkДобавляет ограничение на данную колонку. Значение для данного аргумента должно быть S-SQL выражением, которое возвращает булево значение. Если необходимо оно может ссылаться на другие колонки в таблице.
:referencesДобавляет ограничение "внешний ключ" на данную таблицу. Предоставляемый аргумент должен быть списком такой формы (target &optional on-delete on-update). Если target символ, он указывает на таблицу на которую ссылается внешний ключ. Если target список, его первый элемент таблица, второй элемент столбец, на который ссылается внешний ключ. on-delete, on-update могут использоваться для указания действий, которые выполняются когда строка удаляется или обновляется. Доступные значения: :restrict, :set-null, :set-default, :cascade и :no-action.

После списка столбцов может быть указано ноль и более дополнительных опции (ограничений для таблицы). То списка начинающиеся с одного из нижеперечисленных keyword'ов:

:checkДобавляет ограничение на таблицу. Принимает одно S-SQL выражение, которое возвращает булево значение.
:primary-keyУказывает первичный ключ для таблицы. Аргументы для данной опции - имена столбцов.
:uniqueДобавляет ограничение "уникальности" на группу столбцов. Как и в прошлой опции аргументы это список символов указываещих на колонки.
:foreign-keyСоздает внешний ключ. Аргументы должны иметь форму (columns target &optional on-delete on-update), где columns список столбцов, которые используются для ключа, остальные аргументы имеют тот же смысл что и аргументы :reference для столбцов.

Каждый список может начинаться с :constraint name для создания специфичного именованного ограничения.

Следует отметить, в отличие от большинства других операторов, :create-table ожидает большинство аргументов незакавыченными символами.

Вот вам пример формы :create-table:

(:create-table enemy
  ((name :type string :primary-key t)
  (age :type integer)
  (address :type (or db-null string) :references (important-addresses :cascade :cascade))
  (fatal-weakness :type text :default "None")
  (identifying-color :type (string 20) :unique t)
  (:foreign-key (identifying-color) (colors name))
  (:constraint enemy-age-check :check (:> 'age 12))
)

sql-op:alter-table (name action &rest args)

Изменяет таблицу. Сейчас поддерживается только добавление и удаление ограничений. Смысл аргументов зависит от action:

:addДобавляет ограничение на таблицу. args должны быть ограничением в форме как в :create-table.
:add-columnДобавляет столбец в таблицу. args должны быть ограничением в форме как в :create-table.
:drop-constraintУдаляет ограничение. Первым аргументом должно быть имя ограничения. Вторым опциональным аргументом - поведение при удалении объектов зависящих от ограничения и может быть :cascade или :restrict.
sql-op :drop-table (name)

Удаляет таблицу. Опционально вы можете указать :if-exists перед именем для подавления сообщения об ошибки.

sql-op :create-index (name &rest args)

Создает индекс для таблицы. После имени индекса должен следовать keyword :on, а затем имя таблицы. Затем keyword :fields с последующими одним или более именами столбцов. Опционально может быть добвалено :where выражение для создания частичного индекса.

sql-op :create-unique-index (name &rest args)

Работает также как и :create-index, за исключением того, что созданный индекс будет уникален.

sql-op :drop-index (name)

Удаляет индекс. Принимает :if-exists аргумент как в :drop-table.

sql-op :create-sequence (name &key increment min-value max-value start cache cycle)

Создает последовательность с заданным именем. Остальные аргументы указывают на алгоритм работы последовательности.

sql-op :drop-sequence (name)

Удаляет последовательность. Вы можете указать :if-exists в качестве первого аргумента.

sql-op :create-view (name query)

Создает представление из S-SQL запроса.

sql-op :drop-view (name)

Удаляет представление. Принимает необязательный :if-exists аргумент.

Справочное руководство simple-date

Smiple-date предоставляет типы (CLOS классы) для date, timestamp и interval похожие на те, которые используются в базе данных, для того, чтобы предоставлять простой метод получать, обновлять, вставлять значения данных типов в базу данных. Для данных типов определены несколько очевидных операций.

Наиболее вопиющим недостатком библиотеки является отсутствие поддержки часовых поясов. Библиотека считает, что весь мир живет по UTC. Используйте с осторожностью. Прим. переводчика. Позволю себе вмешаться, и утверждать, что база данных должна оперировать типами без часовых поясов. Преобразование времени - это задача клиентской программы. При этом в этой программе необходимы только две функции to-utc-from-local, to-local-from-utc для вставки в базу и отображения времени пользователю соответственно.

Когда библиотека загружается после CL-postgres, она регистрирует необходимые SQL обработички для чтения/записи связанных типов базы данных.

Тип Date

class date

Отображает дату, без информации о времени.

function encode-date (year month day)
→ date

Создает объект date.

function decode-date (date)
→ (values year month day)

Извлекает элементы из объекта date.

function day-of-week (date)
→ integer

Возвращает день недели для заданной даты. Значения лежат на интервале от 0 до 6, отсчет начинается с воскресенья и заканчивается субботой.

Тип Timestamp

class timestamp

Отображает timestamp с точностью до милисекунды.

function encode-timestamp (year month day &optional (hour 0) (minute 0) (second 0) (millisecond 0))
→ timestamp

Создает Timestamp. Недопустимы отрицательные значения или значения выходящие за границы интервалов (60 для минут, 1000 для милисекунд).

function decode-timestamp (timestamp)
→ (values year month day hour minute second millisecond)

Возвращает компоненты timestamp'а.

function timestamp-to-universal-time (timestamp)
→ universal-time

Преобразует timestamp в соответствующий UT timestamp с округлением до секунды. Необходимо отметить, что функция считает, что timestamp представляет UTC.

function universal-time-to-timestamp (universal-time) → timestamp

Создает timestamp из UT. Здесь также timestamp должен рассматриваться как UTC.

Тип Interval

class interval

Interval отображает период времени. Он содержит абсолютную часть в милисекундах (дни, недели, минуты и т.д. всегда имеют одинаковую длину), и относительную часть для месяцев и лет - количество времени, отображающее месяц или год, не всегда одной длины FIXME.

function encode-interval (&key (year 0) (month 0) (week 0) (day 0) (hour 0) (minute 0) (second 0) (millisecond 0))
→ interval

Создает interval. Аргументы могут быть отрицительными или любой величины.

function decode-interval (interval)
→ (values year month day hour minute second millisecond)

Раскладывает interval на части. Следует отметить, что это зависит от параметров конструктора interval'а, интервал 3600 секунд то же самое, что и 1 час.

Операции

Для предотвращения распространения различных имен функций, используются generic функции для операций над временными значениями. Семантика функций зависит от типов операндов.

method time-add (a b)
→ value

Складывает две объекта. Добавление interval к date или к timestamp'у возвращает date или timestamp, увеличенные на размер interval'а. Сложение двух interval'ов возвращает новый interval, являющийся суммой этих аргументов. Целочисленные значения могут использованы на месте interval'ов, они будут интерпретированы, как количество милисекунд.

method time-subtract (a b)
→ value

Вычитает объекты. Вычитание двух date или timestamp'ов возвращает interval, который отображает различие между ними. По аналогии, вычитание двух интервалов возвращает разницу между ними.

method time= (a b)
→ boolean

Сравнивает два объекта, возвращает булево значение, показывающее, содержат ли аргументы одинаковое время или период.

method time< (a b)
→ boolean

Сравнивает два объекта, возвращает булево значение, показывающее, меньше ли первый аргумент, чем второй.

method time> (a b)
→ boolean

Сравнивает два объекта, возвращает булево значение, показывающее, больше ли первый аргумент, чем второй.

function time<= (a b)
→ boolean

Результат противоложен time>.

function time>= (a b)
→ boolean

Результат противоложен time<.

@2009-2013 lisper.ru