Использование SQLite

Джей А. Крейбич
2010

перевод В.Айсин

Моему двоюродному дедушке Альберту «Ункен Аль» Крейбичу.

1918-1994

Он взял мальчика, чей любимый вопрос был «почему?» и научил его задавать вопрос "как?" (Который также - к большому разочарованию его родителей и кухонного телефона - научил его радости отвечать на этот вопрос, особенно если это касалось плоскогубцев или отверток.)

--Jk

Предисловие

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

Использование SQLite в первую очередь написано для опытных разработчиков программного обеспечения, у которых никогда не было особой потребности в изучении реляционных баз данных. По той или иной причине вы теперь столкнулись с большой задачей по управлению данными и надеетесь, что такой продукт, как SQLite, сможет дать ответ. Чтобы помочь вам, различные главы охватывают язык SQL, API программирования SQLite C и основы проектирования реляционных баз данных, предоставляя вам все необходимое для успешной интеграции SQLite в ваши приложения и разработки.

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

Для полноты картины за десятью главами следуют несколько справочных приложений. Эти ссылки охватывают все команды, выражения и встроенные функции SQL, поддерживаемые SQLite, а также документацию для полного API SQLite.

Версии SQLite

Первое издание этой книги относится к SQLite версии 3.6.23.1. Когда это будет опубликовано, работа над SQLite версии 3.7 завершится. SQLite 3.7 представляет новый режим журнала транзакций, известный как Write Ahead Logging или WAL (запись с опережающей записью). В некоторых средах WAL может обеспечить лучшую производительность одновременных транзакций, чем текущий журнал отката. Однако за такую производительность приходится платить. WAL имеет более строгие операционные требования и требует более продвинутой поддержки со стороны операционной системы.

После того, как WAL будет полностью протестирован и выпущен, поищите на сайте O'Reilly статью, в которой рассказывается об этой новой функции и о том, как получить от нее максимальную отдачу.

Списки электронной почты

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

sqlite-announce@sqlite.org
Этот список ограничен объявлениями о новых выпусках, предупреждениями о критических ошибках и другими важными событиями в сообществе SQLite. Трафик чрезвычайно низкий, и большинство сообщений отправляются командой разработчиков SQLite.
sqlite-users@sqlite.org
Это основной список поддержки SQLite. Он охватывает широкий круг тем, включая вопросы по SQL, вопросы по программированию и вопросы о том, как работает библиотека. Этот список умеренно загружен.
sqlite-dev@sqlite.org
Этот список предназначен для людей, работающих над внутренним кодом самой библиотеки SQLite. Если у вас есть вопросы о том, как использовать опубликованный API SQLite, эти вопросы должны быть включены в список пользователей sqlite. Трафик в этом списке довольно низкий.

Вы можете найти инструкции о том, как присоединиться к этим спискам рассылки на веб-сайте SQLite. Посетите http://www.sqlite.org/support.html для получения более подробной информации.

Список рассылки sqlite-users@sqlite.org может быть весьма полезным, но это умеренно загруженный список. Если вы всего лишь случайный пользователь и не хотите получать столько писем, вы также можете получить доступ к сообщениям в списке и выполнить поиск через веб-архив. Ссылки на несколько различных архивов доступны на странице поддержки SQLite.

Скачать примеры кода

Примеры кода, приведенные в этой книге, доступны для загрузки с веб-сайта O'Reilly. Вы можете найти ссылку на примеры на странице каталога книги по адресу http://oreilly.com/catalog/9780596521196/ (локальная ссылка: UsingSQLiteCode.tar.gz). Файлы включают в себя как примеры SQL, так и примеры на языке C, приведенные в следующих главах.

Как мы сюда попали

Чтобы превратить книгу из идеи в готовый продукт, нужно очень много людей. Хотя мое имя указано на обложке, это было бы невозможно без их помощи.

Во-первых, я хотел бы поблагодарить моего главного редактора Майка Лукидеса за дружбу и поддержку. Благодаря некоторым общим друзьям я впервые начал делать технические обзоры для Майка более восьми лет назад. На протяжении многих лет Майк мягко поощрял меня заняться моим собственным проектом.

Первый шаг на этом пути был сделан почти три года назад. Я загрузил набор экспорта базы данных из проекта Wikipedia и пытался разработать минимальную конфигурацию базы данных, которая (надеюсь) втиснула бы почти все текущие данные на небольшую карту флэш-памяти. Конечная цель состояла в том, чтобы предоставить локальную копию статей Википедии на имеющемся у меня читателе электронных книг. SQLite был естественным выбором. В какой-то момент, разочарованный попытками понять правильную последовательность вызовов, я вскинул руки и воскликнул: «Кто-то должен написать об этом книгу!» - Тынц! - Пресловутая лампочка погасла, и много, много (много...) поздних ночей спустя мы здесь.

За Майком стоит весь коллектив O'Reilly Media. Все, с кем я общался, делали все возможное, чтобы помочь мне, успокоить и решить мои проблемы - иногда все сразу. Производственный персонал понимает, как облегчить жизнь автору, чтобы мы могли сосредоточиться на написании и оставить детали кому-то другому.

Я хотел бы поблагодарить Д. Ричарда Хиппа, создателя и ведущего разработчика SQLite. Помимо координации непрерывной разработки SQLite и предоставления всем нам высококачественного программного продукта, он также был достаточно любезен, чтобы ответить на многочисленные вопросы, а также просмотреть окончательный вариант рукописи. Некоторые хитрые места подверглись нескольким исправлениям, и он всегда быстро пересматривал их и возвращал мне с дополнительными комментариями.

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

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

Я также должен поблагодарить Майка Куласа и всех моих коллег в Volition, Inc. Помимо того, что Майк помог мне найти правильный баланс между моей профессиональной работой и книжной работой, Майк помог мне сориентироваться в политике нашей компании в области интеллектуальной собственности, убедившись, что все идет правильно. Многие коллеги также заслуживают благодарности за то, что просматривали небольшие разделы, рассматривали код, задавали много хороших вопросов и в остальном терпели мои высказывания о том, что у них не хватает времени в течение дня.

Отдаем дань уважения команде Aroma Café в центре города Шампейн, штат Иллинойс. Они всего в нескольких кварталах от моего рабочего места, и значительная часть этой книги была написана в их кафе. Большое спасибо Майклу и его сотрудникам, включая Ким, Сару, Николь и Джерри, за то, что всегда готовили горячий и сливочный мокко.

Наконец, я в огромном долгу перед своей женой Дебби Флигор и двумя нашими сыновьями. Они всегда были готовы уделить мне время для написания и проявляли огромное терпение и понимание. Все они дали больше, чем я имел право попросить, и это достижение принадлежит им в той же степени, что и мне.

Условные обозначения, используемые в этой книге

В этой книге используются следующие типографские условные обозначения:

Курсив
Обозначает новые термины, URL-адреса, адреса электронной почты, имена файлов и расширения файлов.
Фиксированный шрифт
Используется для списков программ, а также внутри абзацев для ссылки на элементы программы, такие как имена переменных или функций, базы данных, типы данных, переменные среды, операторы и ключевые слова.
Фиксированный жирный шрифт
Показывает команды или другой текст, который пользователь должен набирать буквально.
Фиксированный курсив
Показывает текст, который следует заменить значениями, заданными пользователем, или значениями, определяемыми контекстом.
Этот значок обозначает совет, предложение или общее примечание.
Этот значок указывает на предупреждение или предостережение.

Глава 1
Что такое SQLite?

Попросту говоря, SQLite - это общедоступный программный пакет, который предоставляет систему управления реляционными базами данных или СУБД. Системы реляционных баз данных используются для хранения пользовательских записей в больших таблицах. Помимо хранения данных и управления ими, ядро базы данных может обрабатывать сложные команды запросов, которые объединяют данные из нескольких таблиц для создания отчетов и сводок данных. Другие популярные продукты СУБД: Oracle Database, IBM DB2 и Microsoft SQL Server на коммерческой стороне, при этом MySQL и PostgreSQL являются популярными продуктами с открытым исходным кодом.

«Lite» в SQLite не относится к его возможностям. Скорее, SQLite является легковесным, когда речь идет о сложности настройки, административных накладных расходах и использовании ресурсов. SQLite определяется следующими особенностями:

Бессерверный
SQLite не требует для работы отдельного серверного процесса или системы. Библиотека SQLite напрямую обращается к файлам своего хранилища.
Нулевая конфигурация
Нет сервера - нет настройки. Создать экземпляр базы данных SQLite так же просто, как открыть файл.
Кроссплатформенность
Весь экземпляр базы данных находится в одном кроссплатформенном файле и не требует администрирования.
Автономный
Единая библиотека содержит всю систему базы данных, которая интегрируется непосредственно в хост-приложение.
Использует мало ресурсов
Сборка по умолчанию составляет менее мегабайта кода и требует всего несколько мегабайт памяти. С некоторыми изменениями можно значительно уменьшить как размер библиотеки, так и использование памяти.
Транзакционный
Транзакции SQLite полностью совместимы с ACID, что обеспечивает безопасный доступ из нескольких процессов или потоков.
Полнофункциональный
SQLite поддерживает большинство функций языка запросов, имеющихся в стандарте SQL92 (SQL2).
Очень надежный
Команда разработчиков SQLite очень серьезно относится к тестированию и проверке кода.

В целом SQLite предоставляет очень функциональную и гибкую среду реляционной базы данных, которая потребляет минимальные ресурсы и создает минимальные проблемы для разработчиков и пользователей.

Автономный, сервер не требуется

В отличие от большинства продуктов СУБД, SQLite не имеет архитектуры клиент/сервер. Большинство крупномасштабных систем баз данных имеют большой серверный пакет, который составляет ядро базы данных. Сервер базы данных часто состоит из нескольких процессов, которые работают совместно для управления клиентскими соединениями, файловым вводом-выводом, кешами, оптимизацией запросов и обработкой запросов. Экземпляр базы данных обычно состоит из большого количества файлов, организованных в одно или несколько деревьев каталогов в файловой системе сервера. Чтобы получить доступ к базе данных, все файлы должны присутствовать и быть правильными. Это может затруднить перемещение или надежное резервное копирование экземпляра базы данных.

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

Для доступа к базе данных поставщик базы данных обычно предоставляет библиотеки клиентского программного обеспечения. Эти библиотеки должны быть интегрированы в любое клиентское приложение, которое хочет получить доступ к серверу базы данных. Эти клиентские библиотеки предоставляют API для поиска и подключения к серверу базы данных, а также для настройки и выполнения запросов и команд базы данных. На рис. 1-1 показано, как все сочетается в типичной клиент-серверной РСУБД.

Напротив, SQLite не имеет отдельного сервера. Весь механизм базы данных интегрирован в любое приложение, необходимое для доступа к базе данных. Единственный общий ресурс между приложениями - это единственный файл базы данных, который находится на диске. Если вам нужно переместить или создать резервную копию базы данных, вы можете просто скопировать файл. На рис. 1-2 показана инфраструктура SQLite.

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

Рисунок 1-1. Традиционная клиент-серверная архитектура РСУБД, использующая клиентскую библиотеку.

Рисунок 1-2. Бессерверная архитектура SQLite.

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

warning Хотя SQLite не использует традиционную архитектуру клиент/сервер, часто говорят о приложениях как о «клиентах SQLite». Эта терминология часто используется для описания независимых приложений, которые одновременно обращаются к общему файлу базы данных SQLite, и не означает, что существует отдельный сервер.

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

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

Однофайловая база данных

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

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

Возможно, самое главное, пользователи компьютеров привыкли ожидать, что документ, проект или другая «единица данных приложения» хранится в виде единого файла. Наличие всей базы данных в одном файле позволяет приложениям использовать экземпляры базы данных в качестве документов, хранилищ данных или данных о предпочтениях, не противореча ожиданиям клиентов.

Нулевая конфигурация

С точки зрения конечного пользователя, SQLite не требует ничего ни для установки, ни для настройки, не о чем беспокоиться. Хотя разработчикам доступно изрядное количество параметров настройки, они обычно скрыты от конечного пользователя. Устранение сервера и включение ядра базы данных непосредственно в ваше приложение приводит к тому, что вашим клиентам никогда не нужно знать, что они используют базу данных. Довольно практично разработать приложение так, чтобы выбор файла был единственным взаимодействием с клиентом - действием, которое им уже удобно.

Поддержка встроенных устройств

Небольшой размер кода SQLite и консервативное использование ресурсов делают его хорошо подходящим для встраиваемых систем с ограниченными операционными системами. Исходный код ANSI C склоняется к более старому, более консервативному стилю, который должен быть принят даже самым эксцентричным компилятором встроенного процессора. При использовании конфигурации по умолчанию размер скомпилированной библиотеки SQLite на большинстве платформ составляет менее 700 КБ, а для работы требуется менее 4 МБ памяти. За счет исключения более сложных функций библиотека часто может быть уменьшена до 300 КБ или меньше. С небольшими изменениями конфигурации библиотеку можно заставить работать с объемом памяти менее 256 КБ, в результате чего ее общий объем занимаемой памяти составляет не более половины мегабайта, плюс хранилище данных.

SQLite ожидает лишь минимальной поддержки со стороны своей среды хоста и написан по очень модульной системе. Распределитель внутренней памяти можно легко изменить или заменить, в то время как весь доступ к файлам и хранилищам осуществляется через интерфейс Virtual File System (VFS), который можно изменять в соответствии с потребностями и требованиями различных платформ. В общем, SQLite можно заставить работать практически на чем угодно с 32-битным процессором.

Уникальные черты

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

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

SQLite также может создавать базы данных полностью в памяти. По сути, это «файлы» базы данных, которые не имеют резервного хранилища и проводят все свое время в файловом кэше. Несмотря на то, что базам данных в памяти не хватает долговечности и не обеспечивается полная поддержка транзакций, они работают очень быстро (при условии, что у вас достаточно оперативной памяти) и являются отличным местом для хранения временных таблиц и других временных данных.

Есть ряд других функций, которые делают SQLite чрезвычайно гибким. Многие из них, как и виртуальные таблицы, основаны на аналогичных функциях других продуктов, но со своими уникальными особенностями. Эти функции и расширения предоставляют ряд мощных инструментов для адаптации SQLite к вашей конкретной проблеме или ситуации.

Совместимая лицензия

SQLite и код SQLite не имеют пользовательской лицензии. На него не распространяется GNU General Public License (GPL) или какие-либо аналогичные лицензии на открытый/свободный исходный код. Команда разработчиков SQLite предпочла разместить исходный код SQLite в открытом доступе. Это означает, что они явно и сознательно отказались от любых притязаний на авторские права или права собственности на код или производные продукты.

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

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

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

Очень надежный

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

В целом стандартные наборы тестов SQLite состоят из более чем 10 миллионов модульных тестов и тестов запросов. «Тест на выдержку», проводимый перед каждым релизом, состоит из более 2,5 миллиардов тестов. Пакет обеспечивает 100% покрытие операторов и 100% покрытие ветвей, включая пограничные ошибки, такие как нехватка памяти и условия хранения. Набор тестов разработан, чтобы довести систему до установленных пределов и превзойти их, обеспечивая обширный охват как кода, так и рабочих параметров.

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

Жесткое тестирование также обеспечивает чрезвычайно надежную обратную совместимость. Команда SQLite очень серьезно относится к обратной совместимости. Форматы файлов, синтаксис SQL, программные API-интерфейсы и поведения имеют чрезвычайно убедительную историю обратной совместимости. Обновление до новой версии SQLite редко вызывает проблемы совместимости.

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

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

Глава 2
Использование SQLite

SQLite необычайно гибок как в том, где его можно запускать, так и в том, как его можно использовать. В этой главе мы кратко рассмотрим некоторые роли, которые SQLite должен выполнять. Некоторые из этих ролей аналогичны тем, которые используются в традиционных продуктах СУБД клиент/сервер. Другие роли используют преимущества размера и простоты использования SQLite, предлагая решения, которые вы, возможно, не рассматриваете с полной базой данных клиент/сервер.

Младшая база данных

Многолетний опыт научил разработчиков тому, что большие клиент-серверные платформы РСУБД являются мощными инструментами для безопасного хранения, организации и управления данными. К сожалению, большинство крупных СУБД требуют значительных ресурсов и ухода. Это повышает их производительность и емкость, но также ограничивает то, как и где они могут быть практически развернуты.

SQLite предназначен для заполнения этих пробелов, предоставляя те же мощные и знакомые инструменты для безопасного хранения, организации и управления данными в небольших средах с более ограниченными ресурсами. SQLite предназначен для дополнения, а не замены более крупных платформ РСУБД в ситуациях, когда простота и удобство использования более важны, чем емкость и параллелизм.

Эта дополнительная роль позволяет приложениям и инструментам использовать управление реляционными данными (и многолетний опыт, связанный с этим), даже если они работают на небольших платформах без административного надзора. Разработчики могут посмеяться над идеей установки MySQL на настольную систему (или мобильный телефон!) Просто для поддержки приложения адресной книги, но с SQLite это не только становится возможным, но и становится полностью практичным.

Файлы приложений

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

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

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

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

Файлы базы данных SQLite кроссплатформенны, что упрощает миграцию. Содержимым файла можно легко и безопасно поделиться с другими приложениями, не беспокоясь о подробных спецификациях формата файла. Общий формат файлов также упрощает доступ к файлам для автоматизированных сценариев или утилит для устранения неполадок. Несколько приложений могут даже получить доступ к одному и тому же файлу одновременно, а библиотека прозрачно позаботится обо всех необходимых блокировках файлов и синхронизации кеша.

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

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

Кэш приложения

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

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

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

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

Архивы и хранилища данных

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

В отличие от многих продуктов СУБД, библиотека SQLite имеет доступ к файлам базы данных, доступным только для чтения. Это позволяет читать хранилища данных непосредственно с оптического диска или другой файловой системы, доступной только для чтения. Это особенно полезно для систем с ограниченным пространством на жестком диске, таких как игровые приставки.

Резервный клиент/сервер

SQLite хорошо работает в качестве «резервной» базы данных в тех ситуациях, когда более надежная СУБД обычно была бы правильным выбором, если бы она была доступна. SQLite может быть особенно полезна для демонстрации и оценки приложений и инструментов, которые обычно зависят от базы данных.

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

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

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

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

Помимо оценок и демонстраций, поддержку SQLite можно использовать для продвижения «облегченной» или «персональной версии» более крупного продукта. Добавление продукта начального уровня, более подходящего и рентабельного для небольших установок, может открыть значительное количество новых клиентов, обеспечивая недорогое и легкое введение в линейку продуктов.

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

Инструмент обучения

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

Учитывая его компактный размер, просто разместить версию инструментов командной строки для Windows, Mac OS X и Linux вместе с несколькими базами данных на небольшом флэш-накопителе. Благодаря отсутствию процесса установки и полностью кроссплатформенным файлам базы данных это обеспечивает обучающую среду «с ходу», которая будет работать практически с любым компьютером.

Архитектура «база данных в файле» позволяет студентам легко делиться своей работой. Целые экземпляры базы данных можно прикрепить к электронному письму или опубликовать в дискуссионном форуме. Однофайловый формат также упрощает резервное копирование незавершенной работы, позволяя студентам экспериментировать и изучать различные решения, не беспокоясь о потере данных.

Общий механизм SQL

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

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

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

Не лучший выбор

Хотя SQLite зарекомендовал себя чрезвычайно гибким, есть некоторые роли, которые выходят за рамки его проектных целей. Хотя SQLite может работать в этих областях, он может быть не лучшим вариантом. Если вы столкнетесь с каким-либо из этих требований, возможно, будет более практичным рассмотреть более традиционный продукт РСУБД клиент/сервер.

Высокая скорость транзакций

SQLite может поддерживать умеренную скорость транзакций, но он не предназначен для поддержки уровня одновременного доступа, обеспечиваемого многими продуктами РСУБД клиент/сервер. Многие серверные системы могут обеспечивать блокировку на уровне таблиц или строк, что позволяет обрабатывать несколько транзакций параллельно без риска потери данных.

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

В зависимости от размера и сложности ваших обновлений SQLite может обрабатывать несколько сотен транзакций в минуту из разных процессов или потоков. Однако если вы начинаете видеть проблемы с производительностью или ожидаете более высокой скорости транзакций, система клиент/сервер, вероятно, обеспечит лучшую производительность транзакций.

Чрезвычайно большие наборы данных

Нет ничего необычного в том, чтобы найти базы данных SQLite, размер которых приближается к десятку гигабайт или более, но есть некоторые практические ограничения на объем данных, которые могут (или должны) быть помещены в базу данных SQLite. Поскольку SQLite помещает все в один файл (и, следовательно, в единую файловую систему), очень большие наборы данных могут отрицательно сказаться на возможностях операционной системы или структуры файловой системы. Хотя большинство современных файловых систем способны обрабатывать файлы размером в терабайт и более, это не всегда означает, что они очень хороши в этом. Во многих файловых системах происходит значительное снижение производительности для шаблонов произвольного доступа, если размер файла гигабайт и более.

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

Контроль доступа

База данных SQLite не имеет данных аутентификации или авторизации. Вместо этого SQLite зависит от разрешений файловой системы для управления доступом к необработанному файлу базы данных. Это существенно ограничивает доступ одним из трех состояний: полный доступ для чтения/записи, доступ только для чтения или отсутствие доступа вообще. Доступ для записи является абсолютным и позволяет изменять как данные, так и структуру самой базы данных.

Хотя API SQLite предоставляет базовый механизм авторизации на уровне приложения, его легко обойти, если у пользователя есть прямой доступ к файлу базы данных. В целом это делает SQLite непригодным для хранилищ конфиденциальных данных, требующих индивидуального контроля доступа.

Клиент/сервер

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

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

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

Репликация

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

Системы репликации могут быть построены на основе базового API базы данных, но такие системы имеют тенденцию быть несколько хрупкими. В целом, если вы ищете репликацию в реальном времени, особенно на уровне безопасности транзакций, вам потребуется более сложная платформа РСУБД.

Большинство этих требований относятся к сфере, где сложность и административные издержки приносятся в жертву емкости и производительности. Это имеет смысл для крупной клиент-серверной платформы РСУБД, но это несколько противоречит целям разработки SQLite - оставаться простым и не требующим обслуживания. Чтобы свести разочарование к минимуму, используйте подходящий для работы инструмент.

Пользователи с большими именами

На веб-сайте SQLite говорится, что «SQLite - это наиболее широко применяемый механизм баз данных SQL в мире». Это довольно смелое заявление, особенно если учесть, что, когда большинство людей думают о платформах реляционных баз данных, они обычно думают о таких именах, как Oracle, SQL Server и MySQL.

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

Тем не менее, список известных пользователей SQLite составляет внушительный список. Веб-браузер Firefox и почтовый клиент Thunderbird используют несколько баз данных SQLite для хранения файлов cookie, истории, настроек и других данных учетной записи. Многие продукты Skype, Adobe и McAfee также используют механизм SQLite. Библиотека SQLite также интегрирована в ряд популярных языков сценариев, включая PHP и Python.

Apple, Inc., активно внедрила SQLite, а это означает, что каждый iPhone, iPod touch и iPad, а также каждая копия iTunes и многие другие приложения Macintosh поставляются с несколькими базами данных SQLite. Среды Symbian, Android, BlackBerry и Palm webOS обеспечивают встроенную поддержку SQLite, в то время как WinCE имеет стороннюю поддержку. Скорее всего, если у вас есть смартфон, на нем хранится ряд баз данных SQLite.

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

Крупные клиент-серверные платформы РСУБД продемонстрировали тысячам разработчиков возможности систем управления реляционными данными. SQLite перенес эту мощь из серверной на настольные компьютеры и мобильные устройства по всему миру.

Глава 3
Сборка и установка SQLite

Эта глава посвящена созданию SQLite. Мы расскажем, как собрать и установить дистрибутив SQLite в Linux, Mac OS X и Windows. База кода SQLite изначально поддерживает все эти операционные системы, а предварительно скомпилированные библиотеки и исполняемые файлы для всех трех сред доступны на веб-сайте SQLite. Все загрузки, включая исходные и предварительно скомпилированные двоичные файлы, можно найти на веб-странице загрузки SQLite (http://www.sqlite.org/download.html).

Продукты SQLite

Проект SQLite состоит из четырех основных продуктов:

Ядро SQLite
Ядро SQLite содержит собственно ядро базы данных и общедоступный API. Ядро может быть встроено в статическую или динамическую библиотеку или может быть встроено непосредственно в приложение.
Инструмент командной строки sqlite3
Приложение sqlite3 - это инструмент командной строки, созданный на основе ядра SQLite. Это позволяет разработчику отправлять интерактивные команды SQL ядру SQLite. Это чрезвычайно полезно для разработки и отладки запросов.
Расширение tcl
SQLite имеет сильную историю с языком Tcl. Эта библиотека, по сути, является копией ядра SQLite с прикрепленными привязками Tcl. При компиляции в библиотеку этот код предоставляет интерфейсы SQLite языку Tcl через Tcl Extension Architecture (TEA). Помимо собственного C API, эти привязки Tcl являются единственным официальным программным интерфейсом, поддерживаемым непосредственно командой SQLite.
Инструмент анализатора SQLite
Анализатор SQLite используется для анализа файлов базы данных. Он отображает статистику о размере файла базы данных, фрагментации, доступном свободном пространстве и других точках данных. Это наиболее полезно для отладки проблем производительности, связанных с физическим расположением файла базы данных. Его также можно использовать, чтобы определить, подходит ли он для VACUUM (переупаковка и дефрагментация) базы данных. Веб-сайт SQLite предоставляет предварительно скомпилированные исполняемые файлы sqlite3_analyzer для большинства настольных платформ. Исходный код анализатора доступен только через дистрибутив исходных кодов разработки.

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

Бинарные дистрибутивы

Страница загрузки SQLite включает предварительно скомпилированные автономные версии инструмента командной строки sqlite3 для Linux, Mac OS X и Windows. Если вы хотите начать экспериментировать с SQLite, вы можете просто загрузить инструмент командной строки, распаковать его, запустить и начать вводить команды SQL. Возможно, вам даже не придется сначала загружать его - Mac OS X и большинство дистрибутивов Linux включают копию утилиты sqlite3 как часть операционной системы. Страница загрузки SQLite также включает предварительно скомпилированные автономные версии sqlite3_analyzer для всех трех операционных систем.

Бинарные динамические библиотеки ядра SQLite и расширения Tcl также доступны для Linux и Windows. Файлы Linux распространяются как общие объекты (файлы .so), а загружаемые файлы Windows содержат файлы DLL. Для Mac OS X недоступны предварительно скомпилированные библиотеки. Библиотеки требуются только в том случае, если вы пишете собственное приложение, но не хотите компилировать ядро SQLite непосредственно в свое приложение.

Распространение документации

Страница загрузки SQLite включает в себя распространение документации. Файл sqlite_docs_3 _x_x.zip содержит большую часть статического содержимого с веб-сайта SQLite. Документация на веб-сайте SQLite не версируется и всегда отражает синтаксис API и SQL для самой последней версии SQLite. Если вы не планируете постоянно обновлять свой дистрибутив SQLite, полезно получить копию документации, которая идет с версией SQLite, которую вы используете.

Исходные дистрибутивы

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

Объединение

Официальное распространение кода известно как объединение (amalgamation). Объединение представляет собой единый исходный файл на C, который содержит все ядро SQLite. Он создается путем сборки отдельных файлов разработки в один исходный файл на C размером почти 4 мегабайта и длиной более 100 000 строк. Объединение вместе с соответствующим файлом заголовка - это все, что нужно для интеграции SQLite в ваше приложение.

У объединения есть два основных преимущества. Во-первых, все в одном файле чрезвычайно легко интегрировать SQLite в хост-приложение. Многие проекты просто копируют файлы объединения в свои собственные исходные каталоги. Также можно скомпилировать ядро SQLite в библиотеку и просто связать библиотеку с вашим приложением.

Во-вторых, слияние также помогает улучшить производительность. Многие оптимизации компилятора ограничиваются одной единицей трансляции. В C это единственный исходный файл. Поместив всю библиотеку в один файл, хороший оптимизатор может обработать весь пакет сразу. По сравнению с компиляцией отдельных исходных файлов, на некоторых платформах производительность увеличивается на 5% и более только за счет объединения.

Единственный недостаток использования объединения - размер. Некоторые отладчики имеют проблемы с файлами, длина которых превышает 65 535 строк. Обычно все работает правильно, но может быть сложно установить точки останова или просмотреть трассировку стека. Компиляция исходного файла длиной более 100 000 строк также требует значительных ресурсов. Хотя это не проблема для большинства настольных систем, это может выйти за рамки любых компиляторов, работающих на ограниченных платформах.

Исходные файлы

При работе с объединением есть четыре важных исходных файла:

sqlite3.c
Исходный файл объединения, который включает все ядро SQLite, а также общие расширения.
sqlite3.h
Заголовочный файл объединения, который предоставляет основной API.
sqlite3ext.h
Файл заголовка расширения, который используется для создания расширений SQLite.
shell.c
Источник приложения sqlite3, который предоставляет интерактивную оболочку командной строки.

Первые два, sqlite3.c и sqlite3.h, - это все, что нужно для интеграции SQLite в большинство приложений. Файл sqlite3ext.h используется для создания расширений и модулей. Создание расширений рассматривается в разделе Расширения SQLite. Файл shell.c содержит исходный код оболочки командной строки sqlite3. Все эти файлы могут быть созданы в Linux, Mac OS X или Windows без каких-либо дополнительных файлов конфигурации.

Загрузка исходников

Веб-сайт SQLite предлагает пять пакетов распространения исходного кода. Большинству людей будет интересен один из первых двух файлов.

sqlite-amalgamation-3_x_x.zip
Распространение объединения Windows.
sqlite-amalgamation-3.x.x.tar.gz
Распространение объединения Unix.
sqlite-3_x_x-tea.tar.gz
Распространение расширения Tcl.
sqlite-3.x.x.tar.gz
Распространение дерева исходных текстов Unix. Это не поддерживается, и файлы сборки не обслуживаются.
sqlite-source-3_x_x.zip
Исходный код Windows. Это не поддерживается.

Объединенный файл Windows состоит из четырех основных файлов и файла .def для создания библиотеки DLL. Никакие файлы makefile, проекта или решения не включены.

Файл объединения Unix, который работает в Linux, Mac OS X и многих других разновидностях Unix, содержит четыре основных файла плюс страницу руководства sqlite3. Дистрибутив Unix также содержит базовый сценарий конфигурации, а также другие файлы autoconf, сценарии и make-файлы. Файлы autoconf также должны работать в среде Minimalist GNU для Windows (MinGW) (http://www.mingw.org/).

Дистрибутив расширения Tcl - это специализированная версия объединения. Это интересно только тем, кто работает с языком Tcl. См. поставляемую документацию для получения более подробной информации.

Дерево исходных кодов Unix является неподдерживаемым устаревшим дистрибутивом. Так выглядел стандартный дистрибутив до того, как объединение стало официально поддерживаемым дистрибутивом. Он доступен для тех, у кого есть более старые среды сборки или ветки разработки, которые используют старый формат распространения. Этот дистрибутив также включает ряд файлов README, которые больше нигде не доступны.

Хотя исходные файлы обновляются, сценарии конфигурации и make-файлы, включенные в дистрибутив дерева исходных текстов Unix, больше не поддерживаются и не работают должным образом на большинстве платформ. Если у вас нет значительной необходимости использовать дистрибутив дерева исходных текстов, вам следует использовать вместо него один из дистрибутивов объединения.

Дистрибутив исходного кода Windows представляет собой, по сути, файл .zip каталога исходных текстов из дистрибутива дерева исходных текстов, за вычетом некоторых тестовых файлов. Это строго исходные файлы и файлы заголовков и не содержат скриптов сборки, make-файлов или файлов проекта.

Сборка

Существует несколько различных способов создания SQLite, в зависимости от того, что вы пытаетесь создать и где вы хотите его установить. Если вы пытаетесь интегрировать ядро SQLite в хост-приложение, самый простой способ сделать это - просто скопировать sqlite3.c и sqlite3.h в исходный каталог вашего приложения. Если вы используете IDE, файл sqlite3.c можно просто добавить в файл проекта вашего приложения и настроить с использованием правильных путей поиска и директив сборки. Если вы хотите создать собственную версию библиотеки SQLite или утилиты sqlite3, это также легко сделать вручную.

Весь исходный код SQLite написан на C. Он не может быть скомпилирован компилятором C++. Если вы получаете ошибки, связанные с определениями структур, скорее всего, вы используете компилятор C++. Убедитесь, что вы используете ванильный компилятор C.

Конфигурация

Если вы используете объединенный дистрибутив Unix, вы можете собрать и установить SQLite с помощью стандартного скрипта конфигурации. После загрузки дистрибутива его довольно легко распаковать, настроить и собрать исходный код:

$ tar xzf sqlite-amalgamation-3.x.x.tar.gz
$ cd sqlite-3.x.x
$ ./configure
  [...]
$ make

По умолчанию это объединит ядро SQLite как в статические, так и в динамические библиотеки. Он также создаст утилиту sqlite3. Они будут построены с включенными многими дополнительными функциями (такими как полнотекстовый поиск и поддержка R*Tree). После этого команда make install установит эти файлы вместе с файлами заголовков и страницей руководства sqlite3. По умолчанию все устанавливается в /usr/local, хотя это можно изменить, задав параметр --prefix=/path/to/install для настройки. Для получения информации о других параметрах сборки введите команду configure --help.

Вручную

Поскольку основное объединение SQLite состоит только из двух исходных файлов и двух файлов заголовков, его очень просто собрать вручную. Например, чтобы собрать оболочку sqlite3 в Linux или большинстве других систем Unix:

$ cc -o sqlite3 shell.c sqlite3.c -ldl -lpthread

Дополнительные библиотеки необходимы для поддержки динамического связывания и потоков. Mac OS X включает эти библиотеки в стандартную системную группу, поэтому при сборке для Mac OS X дополнительных библиотек не требуется:

$ cc -o sqlite3 shell.c sqlite3.c

Очень похожие команды и на Windows, с использованием компилятора Visual Studio C из командной строки:

> cl /Fesqlite3 shell.c sqlite3.c

Это объединит ядро SQLite и оболочку в одно приложение. Это означает, что для работы полученному исполняемому файлу sqlite3 не потребуется установленная библиотека.

Если вы хотите создать что-то с одним из установленных дополнительных модулей, вам необходимо определить соответствующие директивы компилятора. Это показывает, как создавать вещи в Unix с включенным расширением FTS3 (полнотекстовый поиск):

$ cc -DSQLITE_ENABLE_FTS3 -o sqlite3 shell.c sqlite3.c -ldl -lpthread

Или в Windows:

> cl /Fesqlite3 /DSQLITE_ENABLE_FTS3 shell.c sqlite3.c

Встраивание ядра SQLite в динамическую библиотеку немного сложнее. Нам нужно создать объектный файл, а затем построить библиотеку, используя этот объектный файл. Если вы уже создали утилиту sqlite3 и у вас есть файл sqlite3.o (или .obj), вы можете пропустить первый шаг. Во-первых, в Linux и большинстве систем Unix:

$ cc -c sqlite3.c
$ ld -shared -o libsqlite3.so sqlite3.o

Некоторым версиям Linux может также потребоваться опция -fPIC при компиляции.

Mac OS X использует немного другой формат динамической библиотеки, поэтому команда для его создания немного отличается. Также требуется явная ссылка на стандартную библиотеку C:

$ cc -c sqlite3.c
$ ld -dylib -o libsqlite3.dylib sqlite3.o -lc

И, наконец, создание Windows DLL (для которой требуется файл sqlite3.def):

> cl /c sqlite3.c
> link /dll /out:sqlite3.dll /def:sqlite3.def sqlite3.obj

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

Настройка сборки

Ядро SQLite знает о большом количестве директив компилятора. В Приложении А все это подробно рассматривается. Многие из них используются для изменения стандартных значений по умолчанию или настройки некоторых максимальных размеров и ограничений. Директивы компилятора также используются для включения или отключения определенных функций и расширений. Всего существует несколько десятков директив.

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

Варианты сборки и установки

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

Создание общей библиотеки таким способом - один из наиболее простых способов интеграции и установки SQLite, но зачастую это не лучший подход. Проект SQLite довольно часто выпускает новые версии. Хотя они серьезно относятся к обратной совместимости, иногда в конфигурацию по умолчанию вносятся изменения. Также есть случаи, когда приложения становятся зависимыми от ошибок, связанных с конкретной версией, или неопределенного (или недокументированного) поведения. Существует также большое количество настраиваемых параметров сборки, которые поддерживает SQLite. Все эти проблемы могут быть трудными для создания общесистемной сборки, подходящей для каждого приложения, использующего SQLite.

Эта проблема усугубляется по мере того, как количество приложений, использующих SQLite, продолжает расти, создавая все больше и больше копий SQLite для конкретных приложений. Даже если приложение (или набор приложений) имеет свою собственную частную копию библиотеки SQLite, все равно существует возможность неправильного связывания и несовместимости версий.

Чтобы избежать этих проблем, рекомендуемым способом использования SQLite является интеграция всего механизма базы данных непосредственно в ваше приложение. Это можно сделать, создав статическую библиотеку и затем связав ее, или просто встроив исходный код объединения непосредственно в код вашего приложения. Этот метод обеспечивает действительно настраиваемую сборку, которая жестко привязана к приложению, которое ее использует, что исключает любую возможность несовместимости версий или сборок.

Пожалуй единственный раз, когда может быть целесообразно использовать динамическую библиотеку, - это когда вы строите на основе существующей установленной (и поддерживаемой системой) библиотеки. Это включает Mac OS X, многие дистрибутивы Linux, а также большинство телефонных сред. В этом случае вы зависите от операционной системы, чтобы поддерживать согласованную сборку. Обычно это работает для достаточно простых задач, но ваше приложение должно быть в некоторой степени гибким. Системные библиотеки часто замораживаются с каждым основным выпуском, но есть вероятность, что рано или поздно системное программное обеспечение (включая системные библиотеки SQLite) будет обновлено. Возможно, вашему приложению придется работать с разными версиями системной библиотеки, если вам необходимо поддерживать разные версии операционной системы. По всем этим же причинам не рекомендуется вручную заменять или обновлять системную копию SQLite.

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

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

Введение в sqlite3

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

Даже если у вас нет намерения распространять копию sqlite3 с вашим приложением, чрезвычайно полезно иметь копию для тестирования и отладки запросов. Если ваше приложение использует настроенную сборку ядра SQLite, вы, вероятно, захотите создать копию sqlite3, используя те же параметры сборки.

Для начала просто запустите команду SQLite. Если вы укажете имя файла (например, test.db), sqlite3 откроет (или создаст) этот файл. Если имя файла не указано, sqlite3 автоматически откроет безымянную временную базу данных:

$ sqlite3 test.db
SQLite version 3.6.23.1
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite>

Приглашение sqlite> означает, что sqlite3 готов принимать команды. Мы можем начать с некоторых основных выражений:

sqlite> SELECT 3 * 5, 10;
15|10
sqlite>

Команды SQL также можно вводить в несколько строк. Если конечная точка с запятой не найдена, предполагается, что оператор будет продолжен. В этом случае приглашение изменится на ...>, чтобы указать, что sqlite3 ожидает ввода дополнительных данных:

sqlite> SELECT 1 + 2,
    ...> 6 + 3;
    3|9
    sqlite>

Если вы когда-нибудь неожиданно окажетесь на приглашении ...>, убедитесь, что вы закончили предыдущую строку точкой с запятой.

Помимо обработки операторов SQL, существует ряд команд, специфичных для оболочки. Их иногда называют «точечными командами», потому что они начинаются с точки. Точечные команды управляют форматированием вывода оболочки, а также предоставляют ряд служебных функций. Например, команду .read можно использовать для выполнения файла, содержащего команды SQL.

Точечные команды должны вводиться в приглашении sqlite>. Они должны быть указаны в одной строке и не должны заканчиваться точкой с запятой. Вы не можете смешивать операторы SQL и точечные команды.

Две из наиболее полезных точечных команд (помимо .help) - это .headers и .mode. Обе они контролируют некоторые аспекты вывода базы данных. Включение заголовков и установка режима вывода на столбец приведет к созданию таблицы, которую большинству людей будет легче читать:

sqlite> SELECT 'abc' AS start, 'xyz' AS end;
abc|xyz
sqlite> .headers on
sqlite> .mode column
sqlite> SELECT 'abc' AS start, 'xyz' AS end;
start      end
---------- ----------
abc        xyz
sqlite>

Также полезна команда .schema. Здесь будут перечислены все команды DDL (CREATE TABLE, CREATE INDEX и т. д.), используемые для определения базы данных. Более полный список всех параметров командной строки sqlite3 и команд с точкой см. в Приложении A.

Резюме

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

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

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

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

Глава 4
Язык SQL

В этой главе представлен обзор языка структурированных запросов (Structured Query Language) или SQL. Хотя иногда оно произносится как «продолжение», официальное произношение каждой буквы - «эс-кью-эл». Язык SQL является основным средством взаимодействия практически со всеми современными системами реляционных баз данных. SQL предоставляет команды для настройки таблиц, индексов и других структур данных в базе данных. Команды SQL также используются для вставки, обновления и удаления записей данных, а также для запроса этих записей для поиска определенных значений данных.

Все взаимодействие с реляционной базой данных осуществляется через язык SQL. Это верно при интерактивном вводе команд или при использовании API программирования. Во всех случаях данные хранятся, изменяются и извлекаются с помощью команд SQL. Часто люди просматривают список вызовов API в поисках функций, обеспечивающих прямой доступ программы к таблице или структурам данных индекса. Таких функций не существует. API структурирован вокруг подготовки и выдачи команд SQL ядру базы данных. Если вы хотите запросить таблицу или вставить значение с помощью API, вы должны создать и выполнить соответствующую команду SQL. Если вы хотите программировать реляционные базы данных, вы должны знать SQL.

Изучение SQL

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

Для тех, кто только начинает работать, наиболее важными командами являются CREATE TABLE, INSERT и SELECT. Это позволит вам создать таблицу, вставить некоторые данные в таблицу, а затем запросить данные и отобразить их. Как только вы освоитесь с этими командами, вы сможете более подробно изучить остальные. Не стесняйтесь вернуться к этой главе или к справочнику по командам в Приложении C. Справочник по командам содержит подробное описание каждой команды, включая некоторые из более сложных синтаксисов, которые не рассматриваются в этой главе.

Всегда помните, что SQL - это командный язык. Предполагается, что вы знаете, что делаете. Если вы вводите команды SQL напрямую через приложение sqlite3, программа не будет останавливаться и запрашивать подтверждение перед обработкой опасных или деструктивных команд. При вводе команд вручную всегда стоит делать паузу и оглядываться на то, что вы набрали, прежде чем нажимать «Return».

Если вы уже достаточно знакомы с языком SQL, можно смело пропустить эту главу. Большая часть информации здесь относится к языку SQL в целом, но есть некоторая информация о конкретном диалекте SQL, который распознает SQLite. Опять же, Приложение C предоставляет ссылку на конкретный синтаксис SQL, используемый SQLite.

Краткая предыстория

Хотя первая официальная спецификация SQL была опубликована в 1986 году Американским национальным институтом стандартов (ANSI), язык восходит к началу 1970-х годов и новаторской работе над реляционными базами данных, которая велась в IBM. Текущие стандарты SQL ратифицированы и опубликованы Международной организацией по стандартизации (ISO). Хотя новый стандарт публикуется каждые несколько лет, последний значительный набор изменений в базовом языке можно проследить до стандарта SQL: 1999 (также известного как «SQL3»). Последующие стандарты в основном касались хранения и обработки данных на основе XML. В целом, эволюция SQL прочно укоренилась в практических аспектах разработки баз данных, и во многих случаях новые стандарты служат только для ратификации и стандартизации синтаксиса или функций, которые уже некоторое время присутствуют в коммерческих продуктах баз данных.

Декларативность

Ядро SQL - декларативный язык. На декларативном языке вы заявляете, какими должны быть результаты, и позволяете языковому процессору выяснить, как обеспечить желаемые результаты. Сравните это с императивными языками, такими как C, Java, Perl или Python, где каждый шаг вычисления или операции должен быть явно записан, предоставляя программисту возможность шаг за шагом вести программу к правильному выводу.

Первые стандарты SQL были специально разработаны, чтобы сделать язык доступным и пригодным для использования «людьми, не имеющими отношения к компьютерам» - по крайней мере, в соответствии с определением этого термина в 1980-х годах. Это одна из причин, по которой команды SQL имеют несколько английский синтаксис. Большинство команд SQL имеют форму глагол-субъект. Например, CREATE (глагол) TABLE (тема), DROP INDEX, UPDATE table_name.

Почти англоязычная декларативная природа SQL имеет как преимущества, так и недостатки. Декларативные языки обычно более просты (особенно для непрограммистов) для понимания. Как только вы привыкнете к общей структуре команд, использование английских ключевых слов может облегчить запоминание синтаксиса. Фиксированная структура команд также упрощает для движка базы данных оптимизацию запросов и более эффективный возврат данных.

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

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

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

Самый большой недостаток SQL состоит в том, что формальная стандартизация почти всегда следовала за общими реализациями. Почти каждый продукт базы данных (включая SQLite) имеет специальные расширения и улучшения основного языка, которые помогают отличать его от других продуктов или предоставляют функции или системы управления, которые не охвачены основным стандартом SQL. Часто эти улучшения связаны с повышением производительности, и их трудно игнорировать.

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

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

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

Общий синтаксис

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

Базовый синтаксис

SQL состоит из ряда различных команд, таких как CREATE TABLE или INSERT. Эти команды выдаются и обрабатываются по очереди. Каждая команда реализует отдельное действие или функцию системы баз данных.

Хотя в командах и ключевых словах SQL обычно используются все заглавные буквы, язык SQL не чувствителен к регистру*. Все команды и ключевые слова нечувствительны к регистру, как и идентификаторы (например, имена таблиц и имена столбцов).

Идентификаторы должны быть даны как литералы. При необходимости идентификаторы могут быть заключены в двойные кавычки (" "), соответствующие стандартам, чтобы разрешить включение в идентификатор пробелов или других нестандартных символов. SQLite также позволяет заключать идентификаторы в квадратные скобки ([ ]) или обратные кавычки (` `) для совместимости с другими популярными продуктами баз данных. SQLite оставляет за собой право использовать любой идентификатор, использующий в качестве префикса sqlite_.

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

Однострочные комментарии начинаются с двойного тире (--) и доходят до конца строки. SQL также поддерживает многострочные комментарии с использованием синтаксиса комментариев C (/* */).

Как и в большинстве языков, числовые литералы представлены голыми. Распознаются как целые (453), так и действительные (рациональные) числа (43.23), а также экспоненциальная научная запись (9.745e-6). Чтобы избежать неоднозначности в парсере, SQLite требует, чтобы десятичная точка всегда была представлена точкой (.), независимо от текущего параметра интернационализации.

Текстовые литералы заключаются в одинарные кавычки (' '). Чтобы представить строковый литерал, содержащий символ одинарной кавычки, используйте две одинарные кавычки в строке (publisher = 'O''Reilly'). Экранирование обратной косой чертой в стиле C (\') не является частью стандарта SQL и не поддерживаются SQLite. Литералы BLOB (двоичные данные) могут быть представлены как x (или X), за которым следует строковый литерал из шестнадцатеричных символов (x'A554E59C').

Текстовые литералы используют одинарные кавычки. Двойные кавычки зарезервированы для идентификаторов (имен таблиц, столбцов и т. д.). Экранирование обратной косой чертой в стиле C не является частью стандарта SQL.

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

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

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

[[database_name.]table_name.]column_name

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

* Если не указано иное, нечувствительность к регистру применяется только к символам ASCII. То есть к символам, представленным значениями меньше 128.

Трехзначная логика

SQL позволяет присвоить любому значению NULL. NULL сам по себе не является значением (SQLite фактически реализует его как уникальный тип без значения), но используется как маркер или флаг для представления неизвестных или отсутствующих данных. Идея состоит в том, что бывают случаи, когда значения для определенного элемента строки могут быть недоступны или неприменимы.

NULL может не быть значением, но его можно присвоить элементам данных, которые обычно имеют значения и поэтому могут отображаться в выражениях. Проблема в том, что NULL плохо взаимодействуют с другими значениями. Если NULL представляет неизвестное, которое может быть любым возможным значением, как мы можем узнать, истинно или ложно выражение NULL > 3?

Чтобы справиться с этой проблемой, SQL должен использовать концепцию, называемую трехзначной логикой (three-valued logic). Трехзначная логика часто обозначается аббревиатурой TVL или 3VL и более формально известна как троичная логика (ternary logic). 3VL по существу добавляет состояние «unknown» к знакомой системе логической логики «true/false».

Вот таблицы истинности для операторов 3VL NOT, AND и OR:

Value NOT Value
True False
False True
NULL NULL
3VL AND TRUE FALSE NULL
TRUE TRUE FALSE NULL
FALSE FALSE FALSE FALSE
NULL NULL FALSE NULL
3VL OR TRUE FALSE NULL
TRUE TRUE TRUE TRUE
FALSE TRUE FALSE NULL
NULL TRUE NULL NULL

3VL также определяет, как работают сравнения. Например, любая проверка равенства (=), включающая NULL, будет оцениваться как NULL, включая NULL = NULL. Помните, что NULL не является значением, это флаг неизвестного, поэтому выражение NULL = NULL спрашивает: «Это неизвестное равно этому неизвестному?» Единственный практический ответ: «Это неизвестно». Может равно, а может и нет. Подобные правила применяются к сравнениям больше, меньше и другим параметрам.

Вы не можете использовать оператор равенства (=) для проверки значений NULL. Вы должны использовать оператор IS NULL.

Если у вас возникли проблемы с разрешением выражения, просто помните, что NULL отмечает неизвестное или неразрешенное значение. Вот почему выражение False AND NULL ложно, а True AND NULL равно NULL. В случае первого выражения NULL можно заменить на true или false без изменения результата выражения. Это неверно для второго выражения, где результат неизвестен (другими словами, NULL), потому что вывод зависит от неизвестного ввода.

Простые операторы

SQLite поддерживает следующие унарные префиксные операторы:

- +
Они регулируют знак значения. Оператор «-» меняет знак значения, эффективно умножая его на -1,0. Оператор «+», по сути, не работает, оставляя значение с тем же знаком, что и раньше. Он не делает отрицательные значения положительными.
~
Как и в языке C, оператор «~» выполняет побитовую инверсию. Этот оператор не является частью стандарта SQL.
NOT
Оператор NOT меняет логическое выражение на противоположное, используя 3VL.

Также существует ряд бинарных операторов. Они перечислены здесь в порядке убывания приоритета.

||
Конкатенация строк. Это единственный оператор конкатенации строк, признанный стандартом SQL. Многие другие продукты для баз данных позволяют использовать «+» для конкатенации, но SQLite - нет.
+ - * / %
Стандартные арифметические операторы для сложения, вычитания, умножения, деления и модуля (остатка).
| & << >>
Поразрядные операторы or, and и shift-high/shift-low, как в языке C. Эти операторы не являются частью стандарта SQL.
< <= => >
Операторы сравнительного теста. Опять же, как и в языке C, у нас есть меньше, меньше или равно, больше или равно и больше. Эти операторы подчиняются 3VL SQL в отношении NULL.
= == != <>
Операторы проверки равенства. И «=», и «==» проверяют на равенство, а «!=» и «<>» проверяют на неравенство. Будучи логическими операторами, эти тесты подчиняются 3VL SQL в отношении NULL. В частности, value = NULL всегда будет возвращать NULL.
IN LIKE GLOB MATCH REGEXP
Эти пять ключевых слов представляют собой логические операторы, возвращающие, истинное, ложное или нулевое состояние. См. Приложение D для более подробной информации об этих операторах.
AND OR
Логические операторы. Опять же, они подчиняются 3VL SQL.

В дополнение к этим основам SQL поддерживает ряд специальных операций с выражениями. Дополнительные сведения об этих и любых других выражениях, относящихся к SQL, см. в приложении D.

Языки данных SQL

Команды SQL делятся на четыре основные категории или языка. Каждый язык определяет подмножество команд, которые служат связанной цели. Первый язык - это язык определения данных (Data Definition Language) или DDL, который относится к командам, которые определяют структуру таблиц, представлений, индексов и других контейнеров данных и объектов в базе данных. CREATE TABLE (используется для определения новой таблицы) и DROP VIEW (используется для удаления представления) являются примерами команд DDL.

Вторая категория команд известна как язык обработки данных (Data Manipulation Language) или DML. Это все команды, которые вставляют, обновляют, удаляют и запрашивают фактические значения данных из структур данных, определенных DDL. INSERT (используется для вставки новых значений в таблицу) и SELECT (используется для запроса или поиска данных из таблиц) являются примерами команд DML.

С DML и DDL связан язык управления транзакциями (Transaction Control Language) или TCL. Команды TCL могут использоваться для управления транзакциями команд DML и DDL. BEGIN (используется для начала транзакции с несколькими операциями) и COMMIT (используется для завершения и принятия транзакции) являются примерами команд TCL.</p>

Последняя категория - это язык управления данными (Data Control Language) или DCL. Основная цель DCL - предоставить или отменить управление доступом. Подобно разрешениям файлов, команды DCL используются, чтобы разрешить (или запретить) конкретным пользователям базы данных (или группам пользователей) разрешение на использование или доступ к определенным ресурсам в базе данных. Эти разрешения могут применяться как к DDL, так и к DML. Разрешения DDL могут включать возможность создания реальной или временной таблицы, в то время как разрешения DML могут включать возможность чтения, обновления или удаления записей определенной таблицы. GRANT (используется для назначения разрешения) и REVOKE (используется для удаления существующего разрешения) являются основными командами DCL.

SQLite поддерживает большинство стандартизированных команд DDL, DML и TCL, но в нем отсутствуют какие-либо команды DCL. Поскольку SQLite не имеет имен пользователей или логинов, он не имеет никакой концепции назначенных разрешений. Скорее, SQLite зависит от разрешений типа данных, чтобы определить, кто может открывать и получать доступ к базе данных.

Язык определения данных

DDL используется для определения структуры контейнеров данных и объектов в базе данных. Наиболее распространенными из этих контейнеров и объектов являются таблицы, индексы и представления. Как вы увидите, большинство объектов определяется с помощью варианта команды CREATE, например CREATE TABLE или CREATE VIEW. Команда DROP используется для удаления существующего объекта (и всех данных, которые он может содержать). Примеры включают DROP TABLE или DROP INDEX. Поскольку синтаксис команд сильно отличается, такие операторы, как CREATE TABLE или CREATE INDEX, обычно считаются отдельными командами, а не вариантами одной команды CREATE.

В некотором смысле команды DDL похожи на файлы заголовков C/C++. Команды DDL используются для определения структуры, имен и типов контейнеров данных в базе данных, точно так же, как файл заголовка обычно определяет определения типов, структуры, классы и другие структуры данных. Команды DDL обычно используются для установки и настройки новой базы данных перед вводом данных.

Команды DDL определяют базовую структуру базы данных и обычно запускаются при создании новой базы данных.

Команды DDL часто хранятся в файле сценария, поэтому структуру базы данных можно легко воссоздать. Иногда, особенно во время разработки, вам может потребоваться воссоздать только часть базы данных. Чтобы помочь в этом, большинство команд CREATE в SQLite имеют необязательное предложение IF NOT EXISTS.

Обычно оператор CREATE возвращает ошибку, если объект с запрошенным именем уже существует. Если присутствует необязательное предложение IF NOT EXISTS, эта ошибка подавляется и ничего не делается, даже если структура существующего объекта и нового объекта несовместимы. Точно так же большинство операторов DROP допускают необязательное предложение IF EXISTS, которое молча игнорирует любой запрос на удаление объекта, которого нет.

В следующих примерах варианты команд IF EXISTS и IF NOT EXISTS явно не вызываются. Подробную информацию о синтаксисе, поддерживаемом SQLite, см. в справочнике по командам SQLite в приложении C.

Таблицы

Самая распространенная команда DDL - CREATE TABLE. Никакие значения данных не могут храниться в базе данных до тех пор, пока не будет определена таблица для хранения этих данных. Как минимум, команда CREATE TABLE определяет имя таблицы плюс имя каждого столбца. В большинстве случаев вы захотите также определить тип для каждого столбца, хотя типы не являются обязательными при использовании SQLite. Необязательные ограничения, условия и значения по умолчанию также могут быть назначены каждому столбцу. Также могут быть назначены ограничения на уровне таблицы, если они включают несколько столбцов.

В некоторых больших системах СУБД команда CREATE TABLE может быть довольно сложной, определяя все виды параметров и конфигураций хранилища. Версия CREATE TABLE для SQLite несколько проще, но по-прежнему доступно множество опций. Полное объяснение команды см. в CREATE TABLE в приложении C.

Основы

Самый простой синтаксис CREATE TABLE выглядит примерно так:

CREATE TABLE table_name
(
    column_name column_type,
    [...]
);

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

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

Типы столбцов

В большинстве баз данных используется строгая статическая типизация столбцов. Это означает, что элементы столбца могут содержать только значения, совместимые с определенным типом столбца. SQLite использует метод динамической типизации, известный как манифестная типизация (manifest typing). Для каждого значения строки манифестная типизация записывает тип значения вместе с данными значения. Это позволяет почти любому элементу любой строки содержать почти любой тип значения.

В самом строгом смысле SQLite поддерживает только пять конкретных типов данных. Они известны как классы хранения (storage classes) и представляют различные способы хранения данных на диске в SQLite. Каждое значение имеет один из этих пяти собственных классов хранения:

NULL
NULL считается отдельным типом. Тип NULL не содержит значения. Литеральные значения NULL представлены ключевым словом NULL.
Integer
Целое число со знаком. Целочисленные значения имеют переменную длину, составляющую 1, 2, 3, 4, 6 или 8 байтов, в зависимости от минимального размера, необходимого для хранения определенного значения. Целые числа имеют диапазон от -9 223 372 036 854 775 808 до +9 223 372 036 854 775 807 или примерно 19 цифр. Литеральные целые числа представлены любой простой последовательностью десятичныхё цифр (без запятых), которая не включает десятичную точку или показатель степени.
Float
Число с плавающей запятой, хранящееся как 8-байтовое значение в собственном формате процессора. Почти для всех современных процессоров это число двойной точности IEEE 754. Литеральные числа с плавающей запятой представлены любой пустой серией десятичных цифр, которые включают десятичную точку или показатель степени.
Text
Строка переменной длины, хранящаяся в кодировке базы данных (UTF-8, UTF-16BE или UTF-16LE). Буквальные текстовые значения представлены с помощью символьных строк в одинарных кавычках.
BLOB
Длина необработанных байтов, скопированных точно так, как указано. Литеральные BLOB-объекты представлены в виде шестнадцатеричных текстовых строк, которым предшествует x. Например, запись x'1234ABCD' представляет 4-байтовый BLOB. BLOB означает большой двоичный объект.

Текст SQLite и значения BLOB всегда имеют переменную длину. Максимальный размер текста или значения BLOB ограничен директивой времени компиляции. Предел по умолчанию составляет ровно один миллиард байт или чуть меньше полного гигабайта. Максимальное значение этой директивы - два гигабайта.

Поскольку элементы большинства столбцов могут содержать значения любого типа, понятие «тип» столбца может вводить в заблуждение. Вместо того, чтобы быть абсолютным типом, как в большинстве баз данных, тип столбца SQLite (как определено в CREATE TABLE) становится скорее предложением, чем жестким и быстрым правилом. Это называется сродством типа (type affinity) и по сути представляет собой желаемую категорию типа. Cродство каждого типа имеет определенные правила о том, какие типы значений он может хранить, и как различные значения будут преобразованы при сохранении в этом столбце. Как правило, сродство типов вызывает преобразование или миграцию типов только в том случае, если это может быть выполнено без потери данных или точности.

Каждый столбец таблицы должен иметь один из пяти типов соответствия:

Text
Столбец с привязкой к тексту будет хранить только значения типа NULL, текст или BLOB. Если вы попытаетесь сохранить значение с числовым типом (с плавающей точкой или целым числом), оно будет преобразовано в текстовое представление перед сохранением как тип текстового значения.
Numeric
Столбец с числовым соответствием будет хранить любой из пяти типов. Значения с целочисленными типами и типами с плавающей запятой, а также с типами NULL и BLOB сохраняются без преобразования. Каждый раз при сохранении значения текстового типа предпринимается попытка преобразовать значение в числовой тип (целое число или число с плавающей запятой). Если преобразование работает, значение сохраняется в соответствующем числовом типе. Если преобразование не удается, текстовое значение сохраняется без преобразования любого типа.
Integer
Столбец с целочисленным сродством работает практически так же, как числовое сродство. Единственное отличие состоит в том, что любое значение с типом float, в котором отсутствует дробная составляющая, будет преобразовано в целочисленный тип.
Float
Столбец с привязкой к числам с плавающей запятой также работает практически так же, как и числовое соответствие. Единственное отличие состоит в том, что большинство значений с целочисленными типами преобразуются в значения с плавающей запятой и сохраняются как тип с плавающей запятой.
None
Столбец без привязки не имеет предпочтения по сравнению с классом хранения. Каждое значение сохраняется в указанном типе без попытки что-либо преобразовать.

Поскольку сродство типов не является частью стандарта SQL, в SQLite есть ряд правил, которые пытаются сопоставить традиционные типы столбцов с наиболее логическим сродством типов. Сродство типа столбца определяется объявленным типом столбца в соответствии со следующими правилами (совпадения подстрок не зависят от регистра):

  1. Если тип столбца не задан, то ему присваивается сродство none.
  2. Если тип столбца содержит подстроку «INT», то столбцу присваивается сродство integer.
  3. Если тип столбца содержит любую из подстрок «CHAR», «CLOB» или «TEXT», тогда столбцу присваивается сродство text.
  4. Если тип столбца содержит подстроку «BLOB», то столбцу присваивается сродство none.
  5. Если тип столбца содержит любую из подстрок «REAL», «FLOA» или «DOUB», то ему присваивается сродство float.
  6. Если совпадений не найдено, столбцу присваивается сродство numeric.

Как следует из первого правила, тип столбца не является обязательным. SQLite позволит вам создать таблицу, просто назвав столбцы, например CREATE TABLE t (i, j, k);. Вы также заметите, что нет какого-либо конкретного списка распознаваемых типов столбцов. Вы можете использовать любой тип столбца, какой захотите, даже придумывая свои собственные имена.

Это может показаться немного быстрым и небрежным для системы набора текста, но работает довольно хорошо. Выделяя определенные подстроки, а не пытаясь определить конкретные типы, SQLite может обрабатывать операторы SQL (и их типы, специфичные для базы данных) практически из любой базы данных, при этом выполняя довольно хорошую работу по сопоставлению типов с соответствующим сродством. Единственный тип названия столбца, с которым вам нужно быть осторожным, - это, например, «floating point». Подстрока «int» в слове «point» вызовет правило 2 до того, как подстрока «floa» в слове «floating» перейдет к правилу 5, и сродство столбца в конечном итоге станет integer.

Ограничения столбца

Помимо имен и типов столбцов, определение таблицы может также накладывать ограничения на определенные столбцы или наборы столбцов. Более полное представление команды CREATE TABLE выглядит примерно так:

CREATE TABLE table_name
(
    column_name  column_type   column_constraints...,
    [... ,]

    table_constraints,
    [...]
);

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

Ограничения столбца можно использовать для определения настраиваемого порядка сортировки (COLLATE collation_name). Параметры сортировки определяют способ сортировки текстовых значений. В дополнение к определяемым пользователем параметрам сопоставления SQLite включает сопоставление NOCASE, которое игнорирует регистр при сортировке.

Также может быть присвоено значение по умолчанию (DEFAULT value). Почти все столбцы имеют значение по умолчанию NULL. Вы можете использовать ограничение столбца DEFAULT, чтобы установить другое значение по умолчанию. По умолчанию это может быть литерал или выражение. Выражения необходимо заключать в круглые скобки.

Чтобы помочь с настройками даты и времени по умолчанию, SQLite также включает три специальных ключевых слова, которые могут использоваться в качестве значения по умолчанию: CURRENT_TIME, CURRENT_DATE и CURRENT_TIMESTAMP. Они будут записывать время, дату или дату и время в формате UTC, соответственно, при вставке новой строки. См. «Функции даты и времени» для получения дополнительной информации о функциях даты и времени.

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

Значения столбцов также могут подвергаться произвольным ограничениям, заданным пользователем, перед их назначением (CHECK ( expression )). Некоторые типы ограничений также позволяют вам указать действие, которое будет предпринято в ситуациях, когда ограничение будет нарушено. См. CREATE TABLE и UPDATE в приложении C для более подробной информации.

Когда несколько ограничений столбца используются в одном столбце, ограничения перечисляются одно за другим без запятых. Несколько примеров:

CREATE TABLE parts
(
    part_id   INTEGER   PRIMARY KEY,
    stock     INTEGER   DEFAULT 0   NOT NULL,
    desc      TEXT      CHECK( desc != '' )   -- empty strings not allowed
);

Чтобы обеспечить соблюдение ограничения столбца UNIQUE, для этого столбца будет автоматически создан уникальный индекс. Для каждого столбца (или набора столбцов) с пометкой UNIQUE будет создан отдельный индекс. Поддержание индекса связано с некоторыми расходами, поэтому имейте в виду, что принудительное выполнение ограничения столбца UNIQUE может иметь последствия для производительности.

Первичные ключи

В дополнение к этим другим ограничениям один столбец (или набор столбцов) может быть обозначен как PRIMARY KEY. Каждая таблица может иметь только один первичный ключ. Первичные ключи должны быть уникальными, поэтому обозначение столбца как PRIMARY KEY также подразумевает ограничение UNIQUE и приведет к созданию автоматического уникального индекса. Если столбец отмечен как UNIQUE, так и PRIMARY KEY, будет создан только один индекс.

В SQLite PRIMARY KEY не означает NOT NULL. Это противоречит стандарту SQL и считается ошибкой, но такое поведение существует так давно, что есть опасения по поводу его исправления и выхода из строя существующих приложений. В результате всегда рекомендуется явно отмечать хотя бы один столбец из каждого PRIMARY KEY как NOT NULL.

Есть также несколько веских причин для определения первичного ключа, которые будут обсуждаться в разделе «Таблицы и ключи», но единственная важная, конкретная вещь, которая вытекает из определения PRIMARY KEY, - это автоматический уникальный индекс. Есть также несколько незначительных сокращений синтаксиса.

Если, однако, столбец первичного ключа имеет тип, обозначенный как INTEGER (и именно INTEGER), то этот столбец становится «корневым» столбцом таблицы.

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

Если таблица включает столбец INTEGER PRIMARY KEY, то этот столбец становится псевдонимом для автоматического столбца ROWID. Вы по-прежнему можете ссылаться на столбец по любому из имен ROWID, но вы также можете ссылаться на столбец по его «реальному» пользовательскому имени. В отличие от самого PRIMARY KEY, столбцы INTEGER PRIMARY KEY имеют автоматическое ограничение NOT NULL, связанное с ними. Они также строго типизированы, чтобы принимать только целочисленные значения.

Столбцы INTEGER PRIMARY KEY обладают двумя значительными преимуществами. Во-первых, поскольку столбец является псевдонимом корневого столбца ROWID таблицы, нет необходимости во вторичном индексе. Сама таблица действует как индекс, обеспечивая эффективный поиск без затрат на обслуживание внешнего индекса.

Во-вторых, столбцы INTEGER PRIMARY KEY могут автоматически предоставлять уникальные значения по умолчанию. Когда вы вставляете строку без явного значения для столбца ROWID (или псевдонима ROWID), SQLite автоматически выбирает значение, которое на единицу больше самого большого существующего значения в столбце. Это обеспечивает простой способ автоматической генерации уникальных ключей. При достижении максимального значения база данных будет случайным образом пробовать другие значения в поисках неиспользуемого ключа.

Столбцы INTEGER PRIMARY KEY могут быть дополнительно помечены как AUTOINCREMENT. В этом случае автоматически сгенерированные значения идентификаторов будут постоянно увеличиваться, предотвращая повторное использование значения идентификатора из ранее удаленной строки. Если достигнуто максимальное значение, вставки с автоматическими значениями INTEGER PRIMARY KEY больше не возможны. Однако это маловероятно, поскольку область типа INTEGER PRIMARY KEY достаточно велика, чтобы допускать 1000 вставок в секунду в течение почти 300 миллионов лет.

При использовании автоматических значений или значений AUTOINCREMENT всегда можно вставить явное значение ROWID (или псевдоним ROWID). За исключением обозначения INTEGER PRIMARY KEY, SQLite не предлагает никаких других функций автоматической последовательности.

Помимо PRIMARY KEY, столбцы также могут быть помечены как FOREIGN KEY. Эти столбцы ссылаются на строки в другой (чужой) таблице. Внешние ключи можно использовать для создания ссылок между строками в разных таблицах. См. «Таблицы и ключи» для получения подробной информации.

Ограничения таблицы

Определения таблиц также могут включать ограничения на уровне таблицы. В общем, ограничения таблицы и ограничения столбцов работают одинаково. Ограничения на уровне таблицы по-прежнему действуют для отдельных строк. Основное отличие состоит в том, что с помощью синтаксиса ограничения таблицы вы можете применить ограничение к группе столбцов, а не только к одному столбцу. Совершенно законно определять ограничение таблицы только с одним столбцом, фактически определяя ограничение столбца. Ограничения по нескольким столбцам иногда называют составными ограничениями (compound constraints).

На уровне таблицы SQLite поддерживает ограничения UNIQUE, CHECK и PRIMARY KEY. Ограничение проверки очень похоже, требует только выражения (CHECK (expression)). Как ограничения UNIQUE, так и PRIMARY KEY, если они заданы как ограничение таблицы, требуют списка столбцов (например, UNIQUE (column_name, [...]), PRIMARY KEY (column_name, [...])). Как и в случае ограничений столбцов, любое ограничение UNIQUE или PRIMARY KEY на уровне таблицы (которое подразумевает UNIQUE) автоматически создает уникальный индекс для соответствующих столбцов.

Ограничения таблицы, которые применяются к нескольким столбцам, используют набор столбцов как группу. Например, когда UNIQUE или PRIMARY KEY применяются более чем к одному столбцу, каждый отдельный столбец может иметь повторяющиеся значения. Ограничение только предотвращает репликацию набора значений в назначенных столбцах. Если вы хотите, чтобы каждый отдельный столбец также был UNIQUE, вам нужно добавить соответствующие ограничения для отдельных столбцов.

Рассмотрим таблицу, содержащую записи обо всех комнатах в кампусе, состоящем из нескольких зданий:

CREATE TABLE rooms
(
    room_number      INTEGER  NOT NULL,
    building_number  INTEGER  NOT NULL,
    [...,]

    PRIMARY KEY( room_number, building_number )
);

Очевидно, нам нужно разрешить более одной комнаты с номером 101. Нам также необходимо разрешить более одной комнаты в здании 103. Но в здании 103 должна быть только одна комната 101, поэтому мы применяем ограничение к обоим столбцам. В этом примере мы решили превратить эти столбцы в составной первичный ключ, поскольку номер здания и номер комнаты объединяются для типичного определения конкретной комнаты. В зависимости от конструкции остальной части базы данных, было бы одинаково правильно определить простое ограничение UNIQUE для этих двух столбцов и назначить произвольный столбец room_id в качестве первичного ключа.

Таблицы из запросов

Вы также можете создать таблицу из результатов запроса. Это немного другой синтаксис CREATE TABLE, который создает новую таблицу и предварительно загружает ее данными одной командой:

CREATE [TEMP] TABLE table_name AS SELECT query_statement;

Используя эту форму, вы не указываете количество столбцов, их имена или типы. Вместо этого выполняется инструкция запроса, а выходные данные используются для определения имен столбцов и предварительной загрузки данных в новую таблицу. С помощью этого синтаксиса невозможно назначить ограничения или модификаторы столбцов. Любой столбец результата, который является прямой ссылкой на столбец, унаследует привязку столбца, но всем столбцам будет дана привязка NONE. Оператор запроса состоит из команды SELECT. Более подробную информацию о SELECT можно найти в главе 5.

Таблицы, созданные таким образом, не обновляются динамически - команда запроса запускается только один раз при создании таблицы. После ввода данных в новую таблицу они остаются неизменными до тех пор, пока вы их не измените. Если вам нужен объект в виде таблицы, который может динамически обновляться, используйте VIEW (см. «Представления»).

В этом примере показано необязательное ключевое слово TEMP (также можно использовать полное слово TEMPORARY) в CREATE TEMP TABLE. Этот модификатор может использоваться в любом варианте CREATE TABLE, но часто используется вместе с вариантом ...AS SELECT..., показанным здесь. Временные таблицы имеют две особенности. Во-первых, временные таблицы могут быть видны только соединению с базой данных, которое их создало. Это позволяет одновременно повторно использовать имена таблиц, не беспокоясь о конфликте между разными клиентами. Во-вторых, все связанные временные таблицы автоматически очищаются и удаляются при закрытии соединения с базой данных.

Вообще говоря, CREATE TABLE...AS SELECT - не лучший выбор для создания стандартных таблиц. Если вам нужно скопировать данные из старой таблицы в новую, лучше использовать CREATE TABLE для определения пустой таблицы со всеми соответствующими модификаторами столбцов и ограничениями таблицы. Затем вы можете массово скопировать все данные в новую таблицу, используя вариант команды INSERT, которая позволяет использовать операторы запроса. См. «INSERT» для получения подробной информации.

Изменение таблиц

SQLite поддерживает ограниченную версию команды ALTER TABLE. В настоящее время ALTER TABLE поддерживает только две операции: добавить столбец (add column) и переименовать (rename). Вариант добавления столбца позволяет добавлять новые столбцы в существующую таблицу. Он не может их удалить. Новые столбцы всегда добавляются в конец списка столбцов. Применяются и некоторые другие ограничения.

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

Для получения полной информации см. ALTER TABLE в приложении C.

Удаление таблиц

Команда CREATE TABLE используется для создания таблиц, а DROP TABLE - для их удаления. Команда DROP TABLE удаляет таблицу и все содержащиеся в ней данные. Определение таблицы также удаляется из системных каталогов базы данных.

Команда DROP TABLE очень проста. Единственный аргумент - это имя таблицы, которую вы хотите удалить:

DROP TABLE table_name;

Помимо удаления таблицы, DROP TABLE также удалит все индексы, связанные с таблицей. Будут удалены как автоматически созданные индексы (например, те, которые используются для обеспечения ограничения UNIQUE), так и индексы, созданные вручную.

Виртуальные таблицы

Виртуальные таблицы можно использовать для подключения к SQLite любого источника данных, включая другие базы данных. Виртуальная таблица создается с помощью команды CREATE VIRTUAL TABLE. Хотя она очень похожа на CREATE TABLE, есть важные отличия. Например, виртуальные таблицы нельзя сделать временными, и они не позволяют использовать предложение IF NOT EXISTS. Чтобы удалить виртуальную таблицу, вы используете обычную команду DROP TABLE.

Дополнительные сведения о виртуальных таблицах, включая полный синтаксис CREATE VIRTUAL TABLE, см. в главе 10.

Представления

Представления предоставляют способ упаковки запросов в предопределенный объект. После создания представления действуют более или менее как таблицы, доступные только для чтения. Как и таблицы, новые представления могут быть помечены как TEMP с тем же результатом. Основной синтаксис команды CREATE VIEW:

CREATE [TEMP] VIEW view_name AS SELECT query_statement

Синтаксис CREATE VIEW практически идентичен команде CREATE TABLE...AS SELECT. Это связано с тем, что обе команды служат одной цели с одним важным отличием. Результатом команды CREATE TABLE является новая таблица, содержащая полную копию данных. Оператор SELECT запускается ровно один раз, а выходные данные запроса сохраняются во вновь определенной таблице. После создания таблица будет содержать свою собственную независимую копию данных.

С другой стороны, представление полностью динамично. Каждый раз, когда на представление ссылаются или запрашивают, выполняется базовая инструкция SELECT для повторного создания представления. Это означает, что данные, отображаемые в представлении, автоматически обновляются по мере изменения данных в базовых таблицах. В некотором смысле представления похожи на именованные запросы.

Представления обычно используются одним из двух способов. Во-первых, их можно использовать для упаковки часто используемых запросов в более удобную форму. Это особенно верно, если запрос сложный и подвержен ошибкам. Создавая представление, вы можете быть уверены, что каждый раз будете получать один и тот же запрос.

Представления также обычно используются для создания удобных версий стандартных таблиц. Типичный пример - таблицы с записями даты и времени. Обычно любое значение времени или даты записывается в формате всемирного координированного времени (Coordinated Universal Time) или UTC. UTC - более подходящий формат для даты и времени, поскольку он недвусмысленен и не зависит от часового пояса. К сожалению, это также может сбивать с толку, если вы находитесь в нескольких часовых поясах. Часто бывает полезно создать представление, которое имитирует базовую таблицу, но преобразует все время и даты из UTC в местный часовой пояс. Таким образом, данные в исходных таблицах остаются неизменными, но представление осуществляется в более удобных для пользователя единицах.

Представления удаляются с помощью команды DROP VIEW:

DROP VIEW view_name;

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

Индексы

Индексы (indexes (или indices)) - это средство оптимизации поиска в базе данных путем предварительной сортировки и индексации одного или нескольких столбцов таблицы. В идеале это позволяет находить определенные строки в таблице без необходимости сканировать каждую строку в таблице. Таким образом, индексы могут значительно повысить производительность некоторых типов запросов. Однако индексы не бесплатны и требуют обновлений при каждом изменении таблицы, а также дополнительного места для хранения. Бывают даже ситуации, когда индекс вызывает падение производительности. См. «Индексы» для получения дополнительной информации о том, когда имеет смысл использовать индекс.

Базовый синтаксис для создания индекса определяет имя нового индекса, а также имена индексируемых таблиц и столбцов. Индексы всегда связаны с одной (и только одной) таблицей, но они могут включать один или несколько столбцов из этой таблицы:

CREATE [UNIQUE] INDEX index_name ON table_name ( column_name [, ...] );

Обычно индексы допускают повторяющиеся значения. Необязательное ключевое слово UNIQUE указывает, что повторяющиеся записи недопустимы, и любая попытка вставить или обновить таблицу с неуникальным значением вызовет ошибку. Для уникальных индексов, которые ссылаются на более чем один столбец, все столбцы должны совпадать, чтобы запись считалась повторяющейся. Как обсуждалось с CREATE TABLE, NULL не считается значением, поэтому индекс UNIQUE не предотвратит одно или несколько значений NULL. Если вы хотите предотвратить значения NULL, вы должны указать NOT NULL в исходном определении таблицы.

Каждый индекс привязан к определенной таблице, но все они имеют общее пространство имен. Хотя вы можете назвать индекс как угодно, стандартной практикой является присвоение имени индекса стандартному префиксу (например, idx_), а затем включение имени таблицы и, возможно, имен столбцов, включенных в индекс. Например:

REATE INDEX idx_employees_name ON employees ( name );

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

Как и все команды DROP, команда DROP INDEX очень проста и требует только имени удаляемого индекса:

DROP INDEX index_name;

Удаление индекса приведет к удалению индекса из базы данных, но оставит связанную таблицу нетронутой.

Как описано ранее, команда CREATE TABLE автоматически создаст уникальные индексы для обеспечения ограничения UNIQUE или PRIMARY KEY. Все автоматические индексы начинаются с префикса sqlite_. Поскольку эти индексы необходимы для принудительного определения таблицы, их нельзя удалить вручную с помощью команды DROP INDEX. Удаление автоматических индексов изменит поведение таблицы, как определено исходной командой CREATE TABLE.

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

Язык манипулирования данными

Язык манипулирования данными предназначен для получения пользовательских данных в базе данных и из нее. После того, как все структуры данных и другие объекты базы данных были созданы с помощью команд DDL, команды DML могут использоваться для загрузки этих структур данных, полных полезных данных.

DML, поддерживаемый SQLite, делится на две основные категории. Первая категория состоит из команд «обновления», которые включают в себя собственно команду UPDATE, а также команды INSERT и DELETE. Как вы могли догадаться, эти команды используются для обновления (или изменения), вставки и удаления строк таблицы. Все эти команды каким-то образом изменяют сохраненные данные. Команды обновления являются основным средством управления всеми данными в базе данных.

Вторая категория состоит из команд «запроса», которые используются для извлечения данных из базы данных. Фактически, есть только одна команда запроса: SELECT. Команда SELECT не только выводит на печать возвращаемые значения, но и предоставляет большое количество опций для объединения различных таблиц и строк и других операций с данными перед возвратом окончательного результата.

SELECT, несомненно, самая сложная команда SQL. Это также, пожалуй, самая важная команда SQL. В этой главе будут рассмотрены только самые основы SELECT, а затем мы посвятим ей следующую главу, пройдя по частям все ее части. Чтобы подробно рассмотреть полный синтаксис команды, SELECT получает целую главу (глава 5).

Команды модификации строки

Для добавления, изменения и удаления данных из базы данных используются три команды. INSERT добавляет новые строки, UPDATE изменяет существующие строки, а DELETE удаляет строки. Эти три команды используются для сохранения всех фактических значений данных в базе данных. Все три команды обновления работают на уровне строк, добавляя, изменяя или удаляя указанные строки. Хотя все три команды могут работать с несколькими строками, каждая команда может напрямую воздействовать только на строки, содержащиеся в одной таблице.

INSERT

Команда INSERT используется для создания новых строк в указанной таблице. Есть две значимые версии команды. Первая версия использует предложение VALUES, чтобы указать список значений для вставки:

INSERT INTO table_name (column_name [, ...]) VALUES (new_value [, ...]);

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

INSERT INTO parts ( name, stock, status ) VALUES ( 'Widget', 17, 'IN STOCK' );

В этом примере мы пытаемся вставить новую строку в таблицу «parts». Обратите внимание на использование одинарных кавычек для текстовых литералов.

Технически список имен столбцов необязателен. Если не указан явный список столбцов, команда INSERT попытается объединить значения в пары с полным списком столбцов таблицы:

INSERT INTO table_name VALUES (new_value [, ...]);

Уловка с этим форматом заключается в том, что количество и порядок значений должны точно соответствовать количеству и порядку столбцов в определении таблицы. Это означает, что невозможно использовать значения по умолчанию даже в столбцах INTEGER PRIMARY KEY. Чаще всего это нежелательно. Этот формат также сложнее поддерживать в исходном коде приложения, поскольку он должен тщательно обновляться при изменении формата таблицы. В общем, рекомендуется всегда явно перечислять столбцы в операторе INSERT.

При массовом импорте данных обычно выполняется цикл по наборам данных, вызывая INSERT снова и снова. Обработка этих операторов по очереди может быть довольно медленной, поскольку каждая команда обновит как таблицу, так и все соответствующие индексы, а затем убедится, что данные полностью записаны на физический диск перед (наконец-то!) запуском следующего INSERT. Это довольно длительный процесс, поскольку он требует физического ввода-вывода.

Чтобы ускорить массовую вставку, обычно объединяют группы от 1000 до 10000 операторов INSERT в одну транзакцию. Группирование операторов вместе существенно увеличит общую скорость вставок за счет задержки физического ввода-вывода до конца транзакции. См. «Язык управления транзакциями» для получения дополнительной информации о транзакциях.

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

Вторая версия INSERT позволяет вам определять значения с помощью оператора запроса. Это очень похоже на команду CREATE TABLE...AS SELECT, хотя таблица уже должна существовать. Это единственная версия INSERT, которая может вставлять более одной строки с помощью одной команды:

INSERT INTO table_name (column_name, [...]) SELECT query_statement;

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

Как и в случае с версией INSERT VALUES, список столбцов технически необязателен, но по тем же причинам все же рекомендуется предоставить явный список столбцов.

Все версии команды INSERT также поддерживают необязательное условие разрешения конфликтов. Это условие конфликта определяет, что следует делать, если результаты INSERT нарушат ограничение базы данных. Наиболее распространенным примером является INSERT OR REPLACE, который вступает в игру, когда INSERT при выполнении вызывает нарушение ограничения UNIQUE. Если присутствует разрешение конфликта REPLACE, любая существующая строка, которая может вызвать нарушение ограничения UNIQUE, сначала удаляется, а затем разрешается продолжить INSERT. Этот конкретный шаблон использования настолько распространен, что целую фразу INSERT OR REPLACE можно заменить просто REPLACE. Например, REPLACE INTO table_name....

См. INSERT и UPDATE в приложении C для получения дополнительной информации о деталях разрешения конфликтов.

UPDATE

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

UPDATE table_name SET column_name=new_value [, ...] WHERE expression

Для команды требуется имя таблицы, за которым следует список пар имя/значение столбца, которые должны быть назначены. Какие строки обновляются, определяется условным выражением, которое проверяется для каждой строки таблицы. Наиболее распространенный шаблон использования использует выражение для проверки равенства в некотором уникальном столбце, таком как столбец PRIMARY KEY.

Если условие WHERE не задано, команда UPDATE попытается обновить указанные столбцы в каждой строке таблицы.

Не считается ошибкой, если выражение WHERE оценивается как ложное для каждой строки в таблице, что не приводит к фактическим обновлениям.

Вот более конкретный пример:

-- Update the price and stock of part_id 454:
UPDATE parts SET price = 4.25, stock = 75 WHERE part_id = 454;

В этом примере предполагается, что части таблицы содержат как минимум три столбца: price, stock и part_id. База данных найдет каждую строку с part_id равным 454. В этом случае можно предположить, что part_id является столбцом PRIMARY KEY, поэтому будет обновлена только одна строка. Столбцам цены и запасов в этой строке будут присвоены новые значения.

Полный синтаксис UPDATE можно найти в приложении C.

DELETE

Как вы могли догадаться, команда DELETE используется для удаления одной или нескольких строк из одной таблицы. Строки полностью удаляются из таблицы:

DELETE FROM table_name WHERE expression;

Команде требуется только имя таблицы и условное выражение для выделения строк. Выражение WHERE используется для выбора конкретных строк для удаления, точно так же, как оно используется в команде UPDATE.

Если условие WHERE не задано, команда DELETE попытается удалить каждую строку таблицы.

Как и в случае с UPDATE, не считается ошибкой, если выражение WHERE принимает значение false для каждой строки в таблице, что не приводит к фактическому удалению.

Некоторые конкретные примеры:

-- Delete the row with rowid 385:
DELETE FROM parts WHERE part_id = 385;

-- Delete all rows with a rowid greater than or equal to 43
-- and less than or equal to 246:
DELETE FROM parts WHERE part_id >= 43 AND part_id <= 246;

В этих примерах предполагается, что у нас есть таблица с именем parts, которая содержит по крайней мере один уникальный столбец с именем part_id.

Как уже отмечалось, если предложение WHERE не задано, команда DELETE попытается удалить каждую строку в таблице. SQLite оптимизирует этот конкретный случай, усекая всю таблицу, а не обрабатывая каждую отдельную строку. Усечение таблицы происходит намного быстрее, чем удаление каждой отдельной строки, но усечение обходит обработку отдельной строки. Если вы хотите обрабатывать каждую строку по мере ее удаления, укажите предложение WHERE, которое всегда имеет значение true:

DELETE FROM parts WHERE 1; -- delete all rows, force per-row processing

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

Команда запроса

Последняя команда DML, которую следует рассмотреть, - это команда SELECT. SELECT используется для извлечения или возврата значений из базы данных. Практически каждый раз, когда вы хотите извлечь или вернуть какое-либо значение, вам нужно будет использовать команду SELECT. Обычно возвращаемые значения являются производными от содержимого базы данных, но SELECT также может использоваться для возврата значения простых выражений. Это отличный способ проверить выражения, например:

sqlite> SELECT 1+1, 5*32, 'abc'||'def', 1>2;
1+1         5*32        'abc' || 'def'  1>2
----------  ----------  --------------  ----------
2           160         abcdef          0

SELECT - это команда только для чтения и не будет изменять базу данных (если SELECT не встроен в другую команду, например CREATE TABLE...AS SELECT или INSERT INTO...SELECT).

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

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

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

А пока мы просто дадим вам попробовать. Это должно предоставить достаточно информации, чтобы поэкспериментировать с другими командами в этой главе. Самая простая форма SELECT:

SELECT output_list FROM input_table WHERE row_filter;

Список вывода - это список выражений, которые должны быть вычислены и возвращены для каждой результирующей строки. Чаще всего это просто список столбцов. Список вывода может также включать подстановочный знак (*), указывающий, что должны быть возвращены все известные столбцы.

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

Предложение WHERE - это условное выражение фильтрации, которое применяется к каждой строке. По сути, это то же самое, что предложение WHERE в командах UPDATE и DELETE. Те строки, которые оцениваются как истинные, будут частью результата, а другие строки будут отфильтрованы.

Рассмотрим эту таблицу:

sqlite> CREATE TABLE tbl ( a, b, c, id INTEGER PRIMARY KEY );
sqlite> INSERT INTO tbl ( a, b, c ) VALUES ( 10, 10, 10 );
sqlite> INSERT INTO tbl ( a, b, c ) VALUES ( 11, 15, 20 );
sqlite> INSERT INTO tbl ( a, b, c ) VALUES ( 12, 20, 30 );

Мы можем вернуть всю таблицу следующим образом:

sqlite> SELECT * FROM tbl;
a           b           c           id
----------  ----------  ----------  ----------
10          11          12          1
10          15          20          2
10          20          30          3

Мы также можем просто вернуть определенные столбцы:

sqlite> SELECT a, c FROM tbl;
a           c
----------  ----------
10          20
10          12
11          30

Или конкретные строки:

sqlite> SELECT * FROM tbl WHERE id = 2;
a           b           c           id
----------  ----------  ----------  ----------
11          15          20          2

Для получения более подробной информации см. главу 5 и приложение C.

Язык управления транзакциями

Язык управления транзакциями используется вместе с языком управления данными для управления обработкой и раскрытием изменений. Транзакции являются фундаментальной частью того, как реляционные базы данных защищают целостность и надежность данных, которые они хранят. Транзакции автоматически используются во всех командах DDL и DML.

ACID транзакции

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

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

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

Стандарт надежных и устойчивых транзакций - это тест ACID. ACID означает Atomic, Consistent, Isolated и Durable. Любая система транзакций, которую стоит использовать, должна обладать этими качествами.

Atomic
Транзакция должна быть атомарной в том смысле, что изменение нельзя разбить на более мелкие части. Когда транзакция фиксируется в базе данных, должна быть применена вся транзакция или вся транзакция не должна применяться. Применение только части транзакции должно быть невозможным.
Consistent
Транзакция также должна поддерживать согласованность базы данных. Типичная база данных имеет ряд правил и ограничений, которые помогают гарантировать, что хранимые данные верны и соответствуют структуре базы данных. Предполагая, что база данных запускается в согласованном состоянии, применение транзакции должно поддерживать согласованность базы данных. Это важно, потому что база данных может (и часто это неизбежно) стать несогласованной, пока транзакция открыта. Например, при переводе средств между вычитанием из одной учетной записи и добавлением на другую учетную запись существует момент, когда общая сумма средств, представленная в базе данных, изменяется и может стать несовместимой с записанной суммой. Это приемлемо, если транзакция в целом согласована на момент ее фиксации.
Isolated
Открытая транзакция также должна быть изолирована от других клиентов. Когда клиент открывает транзакцию и начинает выдавать отдельные команды изменения, результаты этих команд видны клиенту. Однако эти изменения не должны быть видны какой-либо другой системе, имеющей доступ к базе данных, и не должны быть интегрированы в постоянную запись базы данных до тех пор, пока транзакция не будет зафиксирована полностью. И наоборот, изменения, совершенные другими клиентами после начала транзакции, не должны быть видны этой транзакции. Изоляция необходима для того, чтобы транзакции были атомарными и последовательными. Если бы другие клиенты могли видеть полупримененные транзакции, транзакции не могли бы претендовать на атомарность по своей природе и не сохраняли бы целостность базы данных, как это видят другие клиенты.
Durable
Наконец, транзакция должна быть надежной. Если транзакция успешно зафиксирована, она должна стать постоянной и необратимой частью записи базы данных. После того, как будет возвращен статус успеха транзакции, не должно иметь значения, будет ли процесс остановлен, система потеряет питание или файловая система базы данных исчезнет - после перезапуска зафиксированные изменения должны присутствовать в базе данных. И наоборот, если система отключается до фиксации транзакции, то после перезапуска изменений, внесенных в транзакцию, не должно быть.

Большинство людей думают, что атомарность транзакций является их наиболее важным качеством, но все четыре аспекта должны работать вместе, чтобы обеспечить общую целостность базы данных. Особенно часто упускается из виду долговечность. SQLite очень старается гарантировать, что, если транзакция будет успешно зафиксирована, эти изменения будут физически записаны в постоянное хранилище и останутся там. Сравните это с традиционными операциями файловой системы, когда записи могут попадать в файловый кеш операционной системы. Обновления могут находиться в кэше от нескольких секунд до нескольких минут, прежде чем, наконец, будут помещены в хранилище. Даже в этом случае данные могут ждать в буферах устройства, прежде чем, наконец, будут сохранены в физическом хранилище. Хотя этот тип буферизации может повысить эффективность, это означает, что обычное приложение действительно понятия не имеет, когда его данные надежно сохранятся в постоянном хранилище.

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

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

SQL-транзакции

Обычно SQLite находится в режиме автоматической фиксации (autocommit). Это означает, что SQLite автоматически запускает транзакцию для каждой команды, обрабатывает команду и (при условии, что ошибок не было) автоматически фиксирует транзакцию. Этот процесс прозрачен для пользователя, но важно понимать, что даже индивидуально введенные команды обрабатываются в рамках транзакции, даже если никакие команды TCL не используются.

Режим автоматической фиксации можно отключить, явно открыв транзакцию. Команда BEGIN используется для запуска или открытия транзакции. После того, как явная транзакция была открыта, она будет оставаться открытой до тех пор, пока не будет зафиксирована или откачена назад. Ключевое слово TRANSACTION указывать необязательно:

BEGIN [ DEFERRED | IMMEDIATE | EXCLUSIVE ] [TRANSACTION]

Необязательные ключевые слова DEFERRED, IMMEDIATE и EXCLUSIVE специфичны для SQLite и управляют способом получения требуемых блокировок чтения/записи. Если к базе данных одновременно обращается только один клиент, режим блокировки в значительной степени не имеет значения. Когда к базе данных может обращаться более одного клиента, режим блокировки определяет, как сбалансировать одноранговый доступ с гарантированным успехом транзакции.

По умолчанию все транзакции (включая транзакции с автоматической фиксацией) используют режим DEFERRED. В этом режиме блокировки базы данных не выполняются, пока они не потребуются. Это наиболее «добрососедский» режим, который позволяет другим клиентам продолжать доступ к базе данных и использовать ее до тех пор, пока у транзакции не останется другого выбора, кроме как заблокировать их. Это позволяет другим клиентам продолжать использовать базу данных, но если блокировки недоступны, когда они требуются транзакции, транзакция завершится ошибкой и, возможно, потребуется откат и перезапуск.

Режим BEGIN IMMEDIATE пытается немедленно получить зарезервированную блокировку. В случае успеха он гарантирует, что блокировки записи будут доступны для транзакции, когда они необходимы, но по-прежнему позволяет другим клиентам продолжать доступ к базе данных для операций только для чтения. Режим EXCLUSIVE пытается заблокировать всех других клиентов, включая клиентов только для чтения. Хотя режимы IMMEDIATE и EXCLUSIVE являются более ограничительными для других клиентов, их преимущество состоит в том, что они выйдут из строя немедленно, если требуемые блокировки недоступны, а не после того, как вы введете свои команды DDL или DML.

После открытия транзакции вы можете продолжать выполнять другие команды SQL, включая команды DML и DDL. Вы можете думать об изменениях, вызванных этими командами, как о «предлагаемых» изменениях. Изменения видны только локальному клиенту и не были полностью и постоянно применены к базе данных. Если клиентский процесс прерывается или сервер теряет питание в середине открытой транзакции, транзакция и любые предлагаемые изменения будут потеряны, но остальная часть базы данных останется нетронутой и согласованной. Только после закрытия транзакции предлагаемые изменения фиксируются в базе данных и становятся «реальными». Команда COMMIT используется для закрытия транзакции и фиксации изменений в базе данных. Вы также можете использовать псевдоним END. Как и в случае с BEGIN, ключевое слово TRANSACTION необязательно.

COMMIT [TRANSACTION]
END [TRANSACTION]

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

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

Чтобы отменить транзакцию и откатить все предложенные изменения, вы можете использовать команду ROLLBACK. Опять же, ключевое слово TRANSACTION необязательно:

ROLLBACK [TRANSACTION]

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

И COMMIT, и ROLLBACK завершат текущую транзакцию, вернув SQLite в режим автоматической фиксации.

Точки сохранения

Помимо транзакций, совместимых с ACID, SQLite также поддерживает точки сохранения (save-points). Точки сохранения позволяют отмечать определенные точки в транзакции. Затем вы можете принять или откатиться к отдельным точкам сохранения без необходимости фиксировать или откатывать всю транзакцию. В отличие от транзакций, вы можете иметь более одной активной точки сохранения одновременно. Точки сохранения иногда называют вложенными транзакциями (nested transactions).

Точки сохранения обычно используются в сочетании с крупными многоэтапными транзакциями, где для некоторых шагов или подпроцедур требуется возможность отката. Точки сохранения позволяют транзакции продолжаться и (при необходимости) откатывать на один шаг за раз. Они также позволяют приложению исследовать разные возможности, пытаясь выполнить одну процедуру, а если это не сработает, попробовать другую, без необходимости откатывать всю транзакцию, чтобы начать заново. В некотором смысле точки сохранения можно рассматривать как маркеры «отмены» в потоке команд SQL.

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

SAVEPOINT savepoint_name

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

Чтобы освободить точку сохранения и принять все предложенные изменения, сделанные с момента установки точки сохранения, используйте команду RELEASE:

RELEASE [SAVEPOINT] savepoint_name

Команда RELEASE не фиксирует никаких изменений на диске. Скорее, она сглаживает все изменения в стеке точек сохранения в слой ниже названной точки сохранения. Затем точка сохранения удаляется. Любые точки сохранения, содержащиеся в названной точке сохранения, автоматически освобождаются.

Чтобы отменить набор команд и отменить все обратно на место, где была установлена точка сохранения, используйте команду ROLLBACK TO:

ROLLBACK [TRANSACTION] TO [SAVEPOINT] savepoint_name

В отличие от транзакции ROLLBACK, ROLLBACK TO точки сохранения не закрывает и не удаляет точку сохранения. ROLLBACK TO выполняет откат и отменяет любые изменения, внесенные с момента создания точки сохранения, но оставляет состояние транзакции точно таким, каким оно было после выполнения команды SAVE POINT.

Рассмотрим следующую серию операторов SQL. Отступ используется для отображения стека точек сохранения:

CREATE TABLE t (i);
BEGIN;
  INSERT INTO t (i) VALUES 1;
  SAVEPOINT aaa;
    INSERT INTO t (i) VALUES 2;
    SAVEPOINT bbb;
      INSERT INTO t (i) VALUES 3;

На этом этапе, если выдается команда ROLLBACK TO bbb, состояние базы данных будет таким, как если бы были введены следующие команды:

CREATE TABLE t (i);
BEGIN;
  INSERT INTO t (i) VALUES 1;
  SAVEPOINT aaa;
    INSERT INTO t (i) VALUES 2;
    SAVEPOINT bbb;

Опять же, обратите внимание, что откат к точке сохранения bbb по-прежнему оставляет точку сохранения на месте. Любые новые команды будут связаны с SAVEPOINT bbb. Например:

CREATE TABLE t (i);
BEGIN;
  INSERT INTO t (i) VALUES 1;
  SAVEPOINT aaa;
    INSERT INTO t (i) VALUES 2;
    SAVEPOINT bbb;
      DELETE FROM t WHERE i=1;

Продолжая, если бы была введена команда RELEASE aaa, мы получили бы эквивалент:

CREATE TABLE t (i);
BEGIN;
  INSERT INTO t (i) VALUES 1;
  INSERT INTO t (i) VALUES 2;
  DELETE FROM t WHERE i=1;

В этом случае предложенные изменения как в точках сохранения aaa, так и в прилагаемых точках сохранения bbb были выпущены и объединены. Однако транзакция все еще открыта, и для того, чтобы сделать предлагаемые изменения постоянными, по-прежнему потребуется COMMIT.

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

Если команда SAVEPOINT выполняется, когда SQLite находится в режиме автоматической фиксации, то есть вне транзакции, то будет запущена стандартная автоматическая фиксация BEGIN DEFERRED TRANSACTION. Однако, в отличие от большинства команд, транзакция автоматической фиксации не будет автоматически фиксироваться после возврата команды SAVEPOINT, оставляя систему внутри открытой транзакции. Автоматическая транзакция будет оставаться активной до тех пор, пока не будет освобождена исходная точка сохранения или пока внешняя транзакция не будет явно зафиксирована или откатится. Это единственная ситуация, когда RELEASE точки сохранения будет иметь прямое влияние на включающую транзакцию. Как и в случае с другими точками сохранения, при откате точки сохранения с автоматической фиксацией транзакция останется открытой, а исходная точка сохранения будет открытой, но пустой.

Системные каталоги

Многие системы реляционных баз данных, включая SQLite, хранят данные о состоянии системы в серии структур данных, известных как системные каталоги (system catalogs). Все системные каталоги SQLite начинаются с префикса sqlite_. Хотя многие из этих каталогов содержат внутренние данные, их можно запросить с помощью SELECT, как если бы они были стандартными таблицами. Большинство системных каталогов доступны только для чтения. Если вы столкнулись с неизвестной базой данных и не знаете, что в ней находится, изучение системных каталогов - хорошее место для начала.

Все невременные базы данных SQLite имеют каталог sqlite_master. Это основная запись всех объектов базы данных. Если в какой-либо из таблиц есть заполненный столбец AUTOINCREMENT, в базе данных также будет каталог sqlite_sequence. Этот каталог используется для отслеживания следующего допустимого значения последовательности (дополнительную информацию об AUTOINCREMENT см. в разделе «Первичные ключи»). Если использовалась команда SQL ANALYZE, она также сгенерирует одну или несколько таблиц sqlite_stat#, например sqlite_stat1 и sqlite_stat2. Эти таблицы содержат различную статистику о значениях и распределениях в различных индексах и используются, чтобы помочь оптимизатору запросов выбрать более эффективное решение для запросов. Для получения дополнительной информации см. ANALYZE в приложении C.

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

Имя столбца Тип столбца Значение
type Text Тип объекта базы данных
name Text Идентификационное имя объекта
tbl_name Text Имя связанной таблицы
rootpage Integer Только для внутреннего использования
sql Text SQL, используемый для определения объекта

Столбец type может быть table (включая виртуальные таблицы), index, view или trigger. Столбец name дает имя самого объекта, а столбец tbl_name дает имя таблицы или представления, с которым связан объект. Для таблиц и представлений tbl_name - это просто копия столбца name. Последний столбец sql содержит полную копию исходной команды SQL, использованной для определения объекта, такой как команда CREATE TABLE или CREATE TRIGGER.

Для временных баз данных нет системного каталога sqlite_master. Вместо этого у них есть таблица sqlite_temp_master.

Подведение итогов

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

Если вы хотите глубже изучить SQL, есть буквально сотни книг на выбор. Один только О'Рейли издает около дюжины заголовков только на языке SQL. Хотя есть некоторые различия между SQL, поддерживаемым SQLite, и другими основными системами баз данных, SQLite довольно точно следует стандарту. В большинстве случаев SQLite отклоняется от стандарта, он делает это в попытке поддержать общие нотации или использование в других популярных продуктах баз данных. Если вы работаете над оберткой, вы обращаетесь к некоторым концепциям более высокого уровня или базовым структурам запросов, вероятно, вам поможет учебник или книга, написанные практически для любого продукта базы данных. Может потребоваться небольшая настройка, чтобы запросы выполнялись под SQLite, но изменения обычно минимальны.

Популярные книги О'Рейли, посвященные языку SQL, включают Learning SQL (Beaulieu), SQL in a Nutshell (Kline, Kline, Hunt) и SQL Cookbook (Molinaro). Более подробные обсуждения можно найти в The Art of SQL (Faroult, Robson). Популярные справочники также включают SQL For Smarties (Celko, Morgan Kaufmann) и Introduction to SQL (van der Lans, Addison Wesley). Эти две большие, но очень полные. Существует также The SQL Guide to SQLite (van der Lans, lulu.com), в котором гораздо глубже рассматривается диалект SQL, специально используемый SQLite.

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

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

Глава 5
Команда SELECT

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

С точки зрения синтаксиса и функциональности SELECT - самая сложная команда SQL. В большинстве приложений это также одна из наиболее часто используемых команд. Если вы хотите получить максимальную отдачу от своей базы данных (и ее дизайна), вам потребуется твердое понимание того, как правильно использовать SELECT.

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

Таблицы SQL

Основная структура данных SQL - это таблица. Таблицы используются как для хранения, так и для обработки данных. Мы видели, как определять таблицы с помощью команды CREATE TABLE, но давайте рассмотрим некоторые детали.

Таблица состоит из заголовка и тела. Заголовок определяет имя и тип (в SQLite, сродство) каждого столбца. Имена столбцов в таблице должны быть уникальными. Заголовок также определяет порядок столбцов, который фиксируется как часть определения таблицы.

Тело таблицы содержит все строки. Каждая строка состоит из одного элемента данных для каждого столбца. Все строки в таблице должны иметь одинаковое количество элементов данных, по одному для каждого столбца. Каждый элемент может содержать ровно одно значение данных (или NULL).

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

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

Строки таблицы SQL не имеют определенного порядка.

Очень распространенная ошибка - предполагать, что данный запрос всегда будет возвращать строки в одном и том же порядке. Если вы специально не попросили запрос отсортировать возвращенные строки в определенном порядке, нет гарантии, что строки будут продолжать возвращаться в том же порядке. Не позволяйте коду вашего приложения зависеть от естественного порядка неупорядоченного запроса. Другая версия SQLite может по-разному оптимизировать запрос, что приведет к другому порядку строк. Даже такая простая вещь, как добавление или удаление индекса, может изменить порядок строк в несортированном результате.

Чтобы убедиться, что ваш код не делает никаких предположений о порядке строк, вы можете включить PRAGMA reverse_unordered_selects. Это заставит SQLite изменить естественный порядок строк любого оператора SELECT, который не имеет явного порядка (предложение ORDER BY). См. reverse_unordered_selects в приложении F для более подробной информации.

Канал SELECT

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

Самый общий формат автономного оператора SQLite SELECT выглядит следующим образом:

SELECT [DISTINCT] select_heading
    FROM source_tables
    WHERE filter_expression
    GROUP BY grouping_expressions
        HAVING filter_expression
    ORDER BY ordering_expressions
    LIMIT count
        OFFSET count

Каждая команда SELECT должна иметь заголовок выбора, который определяет возвращаемые значения. Каждая дополнительная строка (FROM, WHERE, GROUP BY и т. д.) Представляет собой необязательное предложение.

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

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

Пункты в операторе SELECT не оцениваются в том же порядке, в котором они написаны. Скорее порядок их оценки выглядит примерно так:

  1. FROM source_tables
    Обозначает одну или несколько исходных таблиц и объединяет их в одну большую рабочую таблицу.

  2. WHERE filter_expression
    Отфильтровывает определенные строки из рабочей таблицы.

  3. GROUP BY grouping_expressions
    Группирует наборы строк в рабочей таблице на основе аналогичных значений.

  4. SELECT select_heading
    Определяет столбцы набора результатов и (если применимо) группирующие агрегаты.

  5. HAVING filter_expression
    Отфильтровывает определенные строки из сгруппированной таблицы. Требуется GROUP BY.

  6. DISTINCT
    Удаляет повторяющиеся строки.

  7. ORDER BY ordering_expressions
    Сортирует строки набора результатов.

  8. OFFSET count
    Пропускает строки в начале набора результатов. Требуется LIMIT.

  9. LIMIT count
    Ограничивает вывод набора результатов определенным количеством строк.

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

Предложение FROM

Предложение FROM берет одну или несколько исходных таблиц из базы данных и объединяет их в одну большую таблицу. Исходные таблицы обычно называются таблицами из базы данных, но они также могут быть представлениями или подзапросами (см. «Подзапросы» для получения дополнительной информации о подзапросах).

Таблицы объединяются с помощью оператора JOIN. Каждый JOIN объединяет две таблицы в большую таблицу. Три или более таблиц можно объединить, связав вместе несколько операторов JOIN. Операторы JOIN оцениваются слева направо, но существует несколько различных типов объединений, и не все из них коммутативны или ассоциативны. Это делает упорядочение и группировку очень важными. При необходимости можно использовать круглые скобки для правильной группировки объединений.

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

SQL определяет три основных типа объединений: CROSS JOIN, INNER JOIN и OUTER JOIN.

CROSS JOIN

CROSS JOIN сопоставляет каждую строку первой таблицы с каждой строкой второй таблицы. Если во входных таблицах есть столбцы x и y, соответственно, итоговая таблица будет иметь столбцы x+y. Если входные таблицы содержат n и m строк, соответственно, итоговая таблица будет иметь n·m строк. В математике CROSS JOIN известен как декартово произведение.

Синтаксис CROSS JOIN довольно прост:

SELECT ... FROM t1 CROSS JOIN t2 ...

На рис. 5-1 показано, как рассчитывается CROSS JOIN.

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

Рисунок 5-1. В CROSS JOIN каждая строка из первой таблицы сопоставляется с каждой строкой во второй таблице.

INNER JOIN

INNER JOIN очень похож на CROSS JOIN, но имеет встроенное условие, которое используется для ограничения количества строк в результирующей таблице. Условное выражение обычно используется для объединения или сопоставления строк из двух исходных таблиц. INNER JOIN без какого-либо типа условного выражения (или выражения, которое всегда принимает истинное значение) приведет к CROSS JOIN. Если во входных таблицах есть столбцы x и y, соответственно, в итоговой таблице будет столбцов не более, чем x+y (в некоторых случаях их может быть меньше). Если входные таблицы имеют n и m строк, соответственно, итоговая таблица может иметь от нуля до n·m строк, в зависимости от условия. INNER JOIN - это наиболее распространенный тип соединения и тип соединения по умолчанию. Это делает ключевое слово INNER необязательным.

Есть три основных способа указать условное выражение. Первый - с выражением ON. Это дает простое выражение, которое оценивается для каждой потенциальной строки. Фактически присоединяются только те строки, которые имеют значение true. A JOIN...ON выглядит так:

SELECT ... FROM t1 JOIN t2 ON conditional_expression ...

Пример этого показан на рис. 5-2.

Рисунок 5-2. В INNER JOIN строки сопоставляются на основе условия.

Если во входных таблицах есть столбцы C и D, соответственно, JOIN...ON всегда будет приводить к столбцам C+D.

Условное выражение можно использовать для проверки чего угодно, но наиболее распространенный тип выражений проверяет равенство одинаковых столбцов в обеих таблицах. Например, в базе данных бизнес-сотрудников, вероятно, будет таблица employee, которая содержит (среди прочего) столбец name и столбец eid (ID сотрудника). Любая другая таблица, которая должна связать строки с конкретным сотрудником, также будет иметь столбец eid, который действует как указатель или ссылка на правильного сотрудника. Эта связь делает очень распространенными запросы с выражениями ON, подобными:

SELECT ... FROM employee JOIN resource ON employee.eid = resource.eid ...

Этот запрос приведет к созданию выходной таблицы, в которой строки из таблицы resource правильно сопоставлены с соответствующими строками в таблице employee.

У этого JOIN есть две проблемы. Во-первых, условие ON - это слишком много для того, чтобы напечатать что-то настолько распространенное. Во-вторых, результирующая таблица будет иметь два столбца eid, но для любой данной строки значения этих двух столбцов всегда будут идентичны. Чтобы избежать избыточности и сделать формулировку короче, условия внутреннего соединения могут быть объявлены с помощью выражения USING. Это выражение определяет список из одного или нескольких столбцов:

SELECT ... FROM t1 JOIN t2 USING ( col1 ,... ) ...

Запросы из базы данных сотрудников теперь будут выглядеть примерно так:

SELECT ... FROM employee JOIN resource USING ( eid ) ...

Для появления в условии USING имя столбца должно существовать в обеих таблицах. Для каждого перечисленного имени столбца условие USING проверяет равенство между парами столбцов. В итоговой таблице будет только по одному экземпляру каждого столбца из списка.

Если этого недостаточно, SQL предоставляет дополнительное сокращение. NATURAL JOIN похож на JOIN...USING, только он автоматически проверяет равенство между значениями каждого столбца, который существует в обеих таблицах:

SELECT ... FROM t1 NATURAL JOIN t2 ...

Если во входных таблицах есть столбцы x и y, соответственно, JOIN...USING или NATURAL JOIN приведет к любому столбцу от max(x,y) до x+y.

Предполагая, что eid является единственным идентификатором столбца, который появляется как в таблице employee, так и в таблице resource, наш бизнес-запрос становится чрезвычайно простым:

SELECT ... FROM employee NATURAL JOIN resource ...

Выражения NATURAL JOIN удобны, поскольку они очень лаконичные и позволяют изменять ключевую структуру различных таблиц без необходимости обновлять все соответствующие запросы. Они также могут быть немного опасными, если вы не будете соблюдать дисциплину при именовании столбцов. Поскольку ни один из столбцов не назван явно, проверка корректности соединения не выполняется. Например, если совпадающие столбцы не найдены, JOIN автоматически (и без предупреждения) перейдет в CROSS JOIN, как и любой другой INNER JOIN. Точно так же, если два столбца случайно получат одно и то же имя, NATURAL JOIN автоматически включит их в условие соединения, хотите вы этого или нет.

OUTER JOIN

OUTER JOIN является расширением INNER JOIN. Стандарт SQL определяет три типа выражений OUTER JOIN: LEFT, RIGHT и FULL. В настоящее время SQLite поддерживает только LEFT OUTER JOIN.

OUTER JOIN имеют условное выражение, идентичное INNER JOIN, выраженное с помощью ключевого слова ON, USING или NATURAL. Таблица исходных результатов рассчитывается аналогично. После вычисления первичного JOIN соединение OUTER возьмет все несвязанные строки из одной или обеих таблиц, дополнит их значениями NULL и добавит их в результирующую таблицу. В случае LEFT OUTER JOIN это делается с любыми несоответствующими строками из первой таблицы (таблица, которая появляется слева от слова JOIN).

На рис. 5-3 показан пример LEFT OUTER JOIN.

Рисунок 5-3. OUTER JOIN похож на INNER JOIN, но в таблицу результатов включаются только несовпадающие строки. Это показывает LEFT OUTER JOIN, где несопоставленные строки из левой (t1) таблицы добавляются к результатам.

Результат LEFT OUTER JOIN будет содержать по крайней мере один экземпляр каждой строки из левой таблицы. Если во входных таблицах есть столбцы x и y, соответственно, в итоговой таблице будет не более, чем столбцы x+y (точное количество зависит от того, какое условие используется). Если входные таблицы содержат n и m строк, соответственно, итоговая таблица может иметь от n до n·m строк.

Поскольку результат включает несовпадающие строки, OUTER JOIN часто специально используются для поиска неразрешенных или «висящих» строк.

Псевдонимы таблиц

Поскольку оператор JOIN объединяет столбцы разных таблиц в одну большую таблицу, могут быть случаи, когда в результирующей рабочей таблице будет несколько столбцов с одинаковым именем. Чтобы избежать двусмысленности, любая часть оператора SELECT может квалифицировать любую ссылку на столбец с именем исходной таблицы. Однако бывают случаи, когда и этого недостаточно. Например, бывают ситуации, когда вам нужно присоединить таблицу к самой себе, в результате чего рабочая таблица имеет два экземпляра одной и той же исходной таблицы. Это не только делает имя каждого столбца неоднозначным, но и делает невозможным их различение по имени исходной таблицы. Другая проблема связана с подзапросами, поскольку у них нет конкретных имен исходных таблиц.

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

SELECT ... FROM x AS x1 JOIN x AS x2 ON x1.col1 = x2.col2 ...

Или, в случае подзапроса:

SELECT ... FROM ( SELECT ... ) AS sub ...

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

Если какой-либо из столбцов подзапроса конфликтует со столбцом из стандартной исходной таблицы, то теперь вы можете использовать классификатор sub в качестве имени таблицы. Например, sub.col1.

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

Предложение WHERE

Предложение WHERE используется для фильтрации строк из рабочей таблицы, созданной предложением FROM. Это очень похоже на предложение WHERE в командах UPDATE и DELETE. Предоставляется выражение, которое оценивается для каждой строки. Любая строка, которая заставляет выражение оценивать значение false или NULL, отбрасывается. В итоговой таблице будет такое же количество столбцов, что и в исходной таблице, но может быть меньше строк. Не считается ошибкой, если предложение WHERE удаляет все строки в рабочей таблице. На рис. 5-4 показано, как работает предложение WHERE.

Рисунок 5-4. Предложение WHERE фильтрует строки на основе выражения фильтра.

Некоторые предложения WHERE могут быть довольно сложными, что приводит к длинной серии операторов AND, используемых для объединения подвыражений. Однако большинство фильтрует определенную строку, ища определенное значение ключа.

Предложение GROUP BY

Предложение GROUP BY используется для свертывания или «сглаживания» групп строк. Группы можно подсчитывать, усреднять или иным образом агрегировать. Если вам нужно выполнить какие-либо межстрочные операции, требующие данных из более чем одной строки, скорее всего, вам понадобится GROUP BY.

Предложение GROUP BY предоставляет список выражений группировки и необязательных сопоставлений. Очень часто выражения являются простыми ссылками на столбцы, но могут быть произвольными выражениями. Синтаксис выглядит так:

GROUP BY grouping_expression [COLLATE collation_name] [,...]

Процесс группировки состоит из двух этапов. Во-первых, список выражений GROUP BY используется для организации строк таблицы в разные группы. После определения групп заголовок SELECT (обсуждается в следующем разделе) определяет, как эти группы объединяются в одну строку. В итоговой таблице будет по одной строке для каждой группы.

Чтобы разбить рабочую таблицу на группы, список выражений оценивается в каждой строке таблицы. Все строки, которые производят эквивалентные значения, сгруппированы вместе. С каждым выражением можно указать необязательное сопоставление. Если выражение группировки включает текстовые значения, сопоставление используется для определения эквивалентных значений. Дополнительные сведения о сопоставлениях см. в разделе «Предложение ORDER BY».

На рис. 5-5 показано, как строки группируются вместе с предложением GROUP BY.

Рисунок 5-5. Предложение GROUP BY группирует строки на основе списка выражений группировки.

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

Поскольку для GROUP BY обычно используются выражения, определенные в заголовке SELECT, можно просто ссылаться на выражения заголовка SELECT в списке выражений GROUP BY. Если выражение GROUP BY задано как буквальное целое число, это число используется как индекс столбца в таблице результатов, определяемой заголовком SELECT. Индексы начинаются с самого левого столбца. Выражение GROUP BY также может ссылаться на псевдоним столбца результатов. Псевдонимы столбцов результатов объяснены в следующем разделе.

Заголовок SELECT

Заголовок SELECT используется для определения формата и содержимого итоговой таблицы результатов. Любой столбец, который вы хотите отобразить в итоговой таблице результатов, должен быть определен выражением в заголовке SELECT. Заголовок SELECT - единственный обязательный шаг в конвейере команд SELECT.

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

Кроме того, вы можете указать имя столбца с помощью ключевого слова AS:

SELECT expression [AS column_name] [,...]

Не путайте ключевое слово AS, используемое в заголовке SELECT, с ключевым словом, используемым в предложении FROM. Заголовок SELECT использует ключевое слово AS для присвоения имени столбца одному из выходных столбцов, в то время как предложения FROM используют ключевое слово AS для назначения псевдонима исходной таблицы.

Указывать имя выходного столбца необязательно, но рекомендуется. Имя столбца, присвоенное таблице результатов, не определяется строго, если пользователь не предоставляет псевдоним столбца AS. Если ваше приложение ищет конкретное имя столбца в результатах запроса, обязательно назначьте известное имя с помощью AS. Присвоение имени столбцу также позволит другим частям оператора SELECT ссылаться на выходной столбец по имени. Шаги в конвейере SELECT, которые обрабатываются перед заголовком SELECT, такие как предложения WHERE и GROUP BY, также могут ссылаться на выходные столбцы по имени, если выражение столбца не содержит агрегатной функции.

Если нет рабочей таблицы (нет предложения FROM), список выражений оценивается один раз, создавая единственную строку. Эта строка затем используется в качестве рабочей таблицы. Это полезно для тестирования и оценки автономных выражений.

Хотя заголовок SELECT, похоже, фильтрует столбцы из рабочей таблицы, так же как предложение WHERE фильтрует строки, это не совсем правильно. Все столбцы исходной рабочей таблицы по-прежнему доступны для предложений, которые обрабатываются после заголовка SELECT. Например, можно отсортировать результаты (с помощью ORDER BY, который обрабатывается после заголовка SELECT), используя столбец, который не отображается в выходных данных запроса.

Было бы точнее сказать, что заголовок SELECT помечает определенные столбцы для вывода. Неиспользуемые столбцы удаляются только после обработки всего конвейера SELECT и готовности результатов к возврату. Рис. 5-6 иллюстрирует этот момент.

Рисунок 5-6. Заголовок SELECT помечает определенные столбцы для вывода. Неиспользуемые столбцы не удаляются до фактического возврата результата запроса. Более поздние предложения SELECT (например, ORDER BY) по-прежнему имеют доступ к столбцам, которые не являются частью результата запроса.

В дополнение к стандартным выражениям SELECT поддерживает два подстановочных знака. Простая звездочка (*) приведет к выводу каждого определяемого пользователем столбца из каждой исходной таблицы в предложении FROM. Вы также можете настроить таргетинг на определенную таблицу (или псевдоним таблицы), используя формат table_name.*. Хотя оба этих подстановочных знака могут возвращать более одного столбца, их можно смешивать с другими выражениями в списке выражений. Подстановочные знаки не могут использовать псевдоним столбца, поскольку они часто возвращают более одного столбца.

Имейте в виду, что символы подстановки SELECT не возвращают автоматически сгенерированные столбцы ROWID. Чтобы вернуть как ROWID, так и определяемые пользователем столбцы, просто запросите их оба:

SELECT ROWID, * FROM table;

Подстановочные знаки действительно включают любой определяемый пользователем столбец INTEGER PRIMARY KEY, который заменил стандартный столбец ROWID. См. «Первичные ключи» для получения дополнительной информации о взаимодействии столбцов ROWID и INTEGER PRIMARY KEY.

Помимо определения столбцов результата запроса, заголовок SELECT определяет, как группы строк (созданные предложением GROUP BY) объединяются в одну строку. Это делается с помощью агрегатных функций. Агрегатная функция принимает выражение столбца в качестве входных данных и агрегирует или объединяет все значения столбцов из строк группы и создает одно выходное значение. Общие агрегатные функции включают count(), min(), max() и avg(). Приложение E содержит полный список всех встроенных агрегатных функций.

Любой столбец или выражение, которые не передаются через агрегатную функцию, будет предполагать, какое значение содержится в последней строке группы. Однако, поскольку таблицы SQL неупорядочены и заголовок SELECT обрабатывается до предложения ORDER BY, мы действительно не знаем, какая строка является «последней». Это означает, что значения для любого неагрегированного вывода будут взяты из некоторой по существу случайной строки в группе. На рис. 5-7 показано, как это работает.

Рисунок 5-7. Заголовок SELECT сгладит любые группы строк, созданные GROUP BY. На этом рисунке показано, как разные столбцы из одной группы строк объединяются в выходную строку. Любое значение, не вычисленное агрегатной функцией, берется из последней строки. Поскольку столбец A использовался как выражение GROUP BY, известно, что все строки имеют одно и то же значение, и его можно безопасно вернуть. Столбец B обрабатывается агрегатной функцией, и его также можно безопасно вернуть. Столбец C возвращать небезопасно, поскольку порядок строк в группе не определен.

В некоторых случаях выбор значения из случайной строки - неплохая вещь. Например, если выражение заголовка SELECT также используется как выражение GROUP BY, то мы знаем, что этот столбец имеет эквивалентное значение в каждой строке группы. Независимо от того, какую строку вы выберете, вы всегда получите одно и то же значение.

Проблемы могут возникнуть, когда заголовок SELECT использует ссылки на столбцы, которые не были частью предложения GROUP BY и не были переданы через агрегатные функции. В таких случаях нет детерминированного способа выяснить, каким будет выходное значение. Чтобы избежать этого, при использовании предложения GROUP BY выражения заголовка SELECT должны использовать только ссылки на столбцы в качестве входных данных агрегатной функции, или выражения заголовка должны соответствовать тем, которые используются в предложении GROUP BY.

Вот несколько примеров. В этом случае все выражения являются пустыми ссылками на столбцы, чтобы прояснить ситуацию:

SELECT col1, sum( col2 ) FROM tbl GROUP BY col1; -- well formed

Это хорошо сформулированное заявление. Предложение GROUP BY показывает, что строки группируются на основе значений в col1. Это делает безопасным появление col1 в заголовке SELECT, поскольку каждая строка в определенной группе будет иметь эквивалентное значение в col1. Заголовок SELECT также ссылается на col2, но он передается в агрегатную функцию. Агрегатная функция возьмет все значения col2 из разных строк в группе и выдаст логический ответ - в данном случае численное суммирование.

Результатом этого оператора будут две колонки. В первом столбце будет одна строка для каждого уникального значения из col1. Каждая строка второго столбца будет иметь сумму всех значений в столбце col2, которые связаны со значением col1, указанным в первом столбце результатов. Более подробные примеры можно найти в конце главы.

Следующее утверждение сформировано неправильно:

SELECT col1, col2 FROM tbl GROUP BY col1; -- NOT well formed

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

Хотя каждая строка в группе должна иметь эквивалентное значение в столбце или выражении, которое использовалось в качестве ключа группировки, это не всегда означает, что значения точно такие же. Если использовалось сопоставление, такое как NOCASE, разные значения (например, 'ABC' и 'abc') считаются эквивалентными. В этих случаях невозможно узнать конкретное значение, которое будет возвращено из заголовка SELECT. Например:

CREATE TABLE tbl ( t );
INSERT INTO tbl VALUES ( 'ABC' );
INSERT INTO tbl VALUES ( 'abc' );
SELECT t FROM tbl GROUP BY t COLLATE NOCASE;

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

Наконец, если заголовок SELECT содержит агрегатную функцию, но оператор SELECT не имеет предложения GROUP BY, вся рабочая таблица рассматривается как одна группа. Поскольку сглаженные группы всегда возвращают одну строку, это приведет к тому, что запрос вернет только одну строку, даже если рабочая таблица не содержит строк.

Предложение HAVING

Функционально предложение HAVING идентично предложению WHERE. Предложение HAVING состоит из выражения фильтра, которое оценивается для каждой строки рабочей таблицы. Любая строка, имеющая значение false или NULL, отфильтровывается и удаляется. В итоговой таблице будет такое же количество столбцов, но может быть меньше строк.

Основное различие между предложением WHERE и предложением HAVING заключается в том, где они появляются в конвейере SELECT. Предложение HAVING обрабатывается после предложений GROUP BY и SELECT, что позволяет HAVING фильтровать строки на основе результатов любого агрегата GROUP BY. Предложения HAVING могут даже иметь свои собственные агрегаты, что позволяет им фильтровать агрегированные результаты, не являющиеся частью заголовка SELECT.

Предложения HAVING должны содержать только выражения фильтра, зависящие от вывода GROUP BY. Вся остальная фильтрация должна выполняться в предложении WHERE.

Оба предложения HAVING и WHERE могут ссылаться на имена столбцов результатов, определенные в заголовке SELECT, с помощью ключевого слова AS. Основное отличие состоит в том, что предложение WHERE может ссылаться только на выражения, не содержащие агрегатных функций, а предложение HAVING может ссылаться на любой столбец результатов.

Ключевое слово DISTINCT

Ключевое слово DISTINCT просканирует набор результатов и удалит все повторяющиеся строки. Это гарантирует, что возвращенные строки составляют правильный набор. При определении того, является ли строка дубликатом, учитываются только столбцы и значения, указанные в заголовке SELECT. Это один из немногих случаев, когда NULL можно сравнивать и такие строки тоже будут исключены.

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

Предложение ORDER BY

Предложение ORDER BY используется для сортировки или упорядочивания строк таблицы результатов. Предоставляется список из одного или нескольких выражений сортировки. Первое выражение используется для сортировки таблицы. Второе выражение используется для сортировки любых эквивалентных строк из первой сортировки и так далее. Каждое выражение можно отсортировать по возрастанию или убыванию.

Базовый формат предложения ORDER BY выглядит так:

ORDER BY expression [COLLATE collation_name] [ASC|DESC] [,...]

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

Выражение ORDER BY может использовать любой исходный столбец, включая те, которые не отображаются в результате запроса. Как и GROUP BY, если выражение ORDER BY состоит из буквального целого числа, предполагается, что это индекс столбца. Индексы столбцов начинаются слева с 1, поэтому фраза ORDER BY 2 отсортирует таблицу результатов по второму столбцу.

Поскольку SQLite позволяет хранить разные типы данных в одном столбце, сортировка может стать немного интереснее. При сортировке столбца смешанного типа значения NULL будут отсортированы вверх. Затем целочисленные и действительные значения будут смешаны вместе в правильном числовом порядке. За числами будут следовать текстовые значения со значениями BLOB в конце. Попытки преобразовать типы не будут. Например, текстовое значение, содержащее строковое представление числа, будет отсортировано с другими текстовыми значениями, а не с числовыми значениями.

В случае числовых значений естественный порядок сортировки четко определен. Текстовые значения сортируются с помощью активного сопоставления, тогда как значения BLOB всегда сортируются с использованием сопоставления BINARY. SQLite поставляется с тремя встроенными функциями сопоставления. Вы также можете использовать API для определения ваших собственных функций сопоставления. Три встроенных сопоставления:

BINARY
Текстовые значения сортируются в соответствии с семантикой вызова memcmp() POSIX. Кодировка текстового значения не принимается во внимание, по сути, рассматривается как большая двоичная строка. Значения BLOB всегда сортируются с помощью этого сопоставления. Это сопоставление по умолчанию.
NOCASE
Так же, как BINARY, только символы верхнего регистра ASCII преобразуются в нижний регистр перед выполнением сравнения. Преобразование регистра строго выполняется для 7-битных значений ASCII. Обычный дистрибутив SQLite не поддерживает сопоставления с поддержкой UTF.
RTRIM
То же, что и BINARY, игнорируются только завершающие (правые) пробелы.

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

В целом, существует множество ситуаций, когда ORDER BY оправдан, или даже требуется. Просто имейте в виду, что его использование может повлечь за собой значительные затраты, и вы не должны иметь привычки привязывать его к каждому запросу «просто потому».

Предложения LIMIT и OFFSET

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

Есть три способа определить LIMIT и OFFSET:

LIMIT limit_count
LIMIT limit_count OFFSET offset_count
LIMIT offset_count, limit_count
Обратите внимание, что если и limit, и offset задаются в третьем формате, порядок чисел меняется на обратный.

Вот несколько примеров. Обратите внимание, что значение OFFSET определяет количество пропущенных строк, а не позицию первой строки:

LIMIT 10           -- returns the first 10 rows (rows 1 - 10)
LIMIT 10 OFFSET 3  -- returns rows 4  - 13
LIMIT 3 OFFSET 20  -- returns rows 21 - 23
LIMIT 3, 20        -- returns rows 4  - 23 (different from above!)

Хотя это не является строго обязательным, вы обычно хотите определить ORDER BY, если вы используете LIMIT. Без ORDER BY нет четко определенного порядка результата, что делает ограничение и смещение несколько бессмысленными.

Продвинутые методы

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

Подзапросы

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

Подзапросы чаще всего встречаются в предложении FROM, где они действуют как вычисляемая исходная таблица. Этот тип подзапроса может возвращать любое количество строк или столбцов и аналогичен созданию представления или выполнению запроса, записи результатов во временную таблицу и последующему обращению к этой таблице в основном запросе. Основное преимущество использования встроенного подзапроса состоит в том, что оптимизатор запросов может объединить подзапрос с основным оператором SELECT и рассмотреть всю проблему в целом, что часто приводит к более эффективному плану запроса.

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

SELECT * FROM TblA AS a JOIN TblB AS b;
SELECT * FROM TblA AS a JOIN (SELECT * FROM TblB) AS b;

Подзапросы могут отображаться в других местах, включая общие выражения, используемые в любой команде SQL. Операторы EXISTS и IN используют подзапросы. Фактически, вы можете использовать подзапрос в любом месте, где выражение ожидает список литеральных значений (однако подзапрос нельзя использовать для создания списка идентификаторов). См. приложение D для получения более подробной информации о выражениях SQL.

Составные операторы SELECT

Помимо подзапросов, несколько операторов SELECT могут быть объединены вместе, чтобы сформировать составной (compound) SELECT. Составные операторы SELECT используют операторы набора для строк, генерируемых серией операторов SELECT.

Для правильного объединения каждый оператор SELECT должен генерировать одинаковое количество столбцов. Имена столбцов из первого оператора SELECT будут использоваться для общего результата. Только последний оператор SELECT может иметь предложение ORDER BY, LIMIT или OFFSET, которое применяется к полной таблице составных результатов. Синтаксис составного SELECT выглядит так:

SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ...

compound_operator

SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ...

[...]

ORDER BY ... LIMIT ... OFFSET ...

Для включения дополнительных операторов SELECT можно использовать несколько составных операторов.

UNION ALL
Оператор UNION ALL объединяет все строки, возвращаемые каждым оператором SELECT, в одну большую таблицу. Если два блока SELECT генерируют N и M строк соответственно, итоговая таблица будет иметь N+M строк.
UNION
Оператор UNION очень похож на оператор UNION ALL, но он удаляет любые повторяющиеся строки, включая дубликаты, которые поступили из одного и того же блока SELECT. Если два блока SELECT генерируют N и M строк соответственно, результирующая таблица может иметь от 1 до N+M строк.
INTERSECT
Оператор INTERSECT вернет один экземпляр любой строки, обнаруженной (один или несколько раз) в обоих блоках SELECT. Если два блока SELECT генерируют N и M строк соответственно, результирующая таблица может иметь от 0 до MIN(N,M) строк.
EXCEPT
Оператор EXCEPT вернет все строки в первом блоке SELECT, которых нет во втором блоке SELECT. По сути, это оператор вычитания. Если в первом блоке есть повторяющиеся строки, все они будут удалены одной совпадающей строкой во втором блоке. Если два блока SELECT генерируют N и M строк соответственно, результирующая таблица может иметь от 0 до N строк.

SQLite поддерживает составные операторы UNION, UNION ALL, INTERSECT и EXCEPT. На рис. 5-8 показан результат каждой операции.

Рисунок 5-8. Составные операторы UNION ALL, UNION, INTERSECT и EXCEPT.

После объединения всех составных операторов любые завершающие ORDER BY, LIMIT и OFFSET применяются к конечной таблице результатов. В случае составных операторов SELECT выражения, присутствующие в любом предложении ORDER BY, должны точно соответствовать одному из столбцов результатов или использовать индекс столбца.

Альтернативная нотация JOIN

Есть два стиля записи соединений. Стиль, показанный ранее в этой главе, известен как нотация явного соединения (explicit join notation). Она названа так потому, что использует ключевое слово JOIN для явного описания того, как каждая таблица присоединяется к следующей. Нотация явного соединения также известна как нотация соединения ANSI (ANSI join notation), поскольку она была введена, когда SQL прошел процесс стандартизации.

Старая исходная нотация соединения известна как нотация неявного соединения (implicit join notation). Используя эту нотацию, предложение FROM представляет собой просто список таблиц, разделенных запятыми. Таблицы в списке объединяются с использованием декартова произведения, а соответствующие строки извлекаются с дополнительными условиями WHERE. Фактически, он переводит каждое соединение в состояние CROSS JOIN, а затем перемещает условия соединения из предложения FROM в предложение WHERE.

В этом первом утверждении используется нотация явного соединения, которую мы узнали ранее в этой главе:

SELECT ...
    FROM employee JOIN resource ON ( employee.eid = resource.eid )
    WHERE ...

Это тот же самый оператор, написанный с использованием нотации неявного соединения:

SELECT ...
    FROM employee, resource
    WHERE employee.eid = resource.eid AND ...

Между двумя обозначениями нет разницы в производительности: это чисто синтаксис.

В общем, явное представление (первое) стало стандартным способом работы. Большинство людей находят явную нотацию более простой для чтения, что делает цель запроса более прозрачной и легкой для понимания. Я всегда чувствовал, что явная нотация немного чище, поскольку она помещает полную спецификацию соединения в предложение FROM, оставляя предложение WHERE свободным для фильтров, специфичных для запроса. Используя явную нотацию, предложение FROM (и только предложение FROM) полностью и независимо указывает, что вы выбираете «from».

Явная нотация также позволяет вам более точно определять тип и порядок каждого JOIN. В SQLite вы должны использовать явную нотацию, если хотите OUTER JOIN - неявная нотация может использоваться только для указания CROSS JOIN или INNER JOIN.

Если вы изучаете SQL впервые, я настоятельно рекомендую вам освоиться с явной нотацией. Просто имейте в виду, что существует много кода SQL (включая старые книги и учебные пособия), использующего старую неявную нотацию.

Примеры SELECT

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

Все эти примеры будут использовать эти данные:

CREATE TABLE x ( a, b );
INSERT INTO x VALUES ( 1, 'Alice' );
INSERT INTO x VALUES ( 2, 'Bob' );
INSERT INTO x VALUES ( 3, 'Charlie' );

CREATE TABLE y ( c, d );
INSERT INTO y VALUES ( 1, 3.14159 );
INSERT INTO y VALUES ( 1, 2.71828 );
INSERT INTO y VALUES ( 2, 1.61803 );

CREATE TABLE z ( a, e );
INSERT INTO z VALUES ( 1, 100 );
INSERT INTO z VALUES ( 1, 150 );
INSERT INTO z VALUES ( 3, 300 );
INSERT INTO z VALUES ( 9, 900 );

В этих примерах показан инструмент командной строки sqlite3. Следующие точечные команды были введены, чтобы облегчить понимание вывода. Последняя команда заставит sqlite3 печатать строку [NULL] всякий раз, когда встречается NULL. Обычно NULL дает пустой вывод, неотличимый от пустой строки:

.headers on
.mode column
.nullvalue [NULL]

Этот набор данных доступен на странице загрузки книги на веб-сайте O'Reilly как файл SQL и база данных SQLite. (UsingSQLiteCode.tar.gz.) Я предлагаю вам сесть с копией sqlite3 и попробовать эти команды. Попробуйте поэкспериментировать с разными вариантами.

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

Простые SELECTы

Начнем с простого выбора, который возвращает все столбцы и строки в таблице x. Синтаксис SELECT * по умолчанию возвращает все столбцы:

sqlite> SELECT * FROM x;

a           b
----------  ----------
1           Alice
2           Bob
3           Charlie

Мы также можем возвращать выражения, а не только столбцы:

sqlite> SELECT d, d*d AS dSquared FROM y;
d           dSquared
----------  ------------
3.14159     9.8695877281
2.71828     7.3890461584
1.61803     2.6180210809

Простые JOINы

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

sqlite> SELECT * FROM x JOIN y;
sqlite> SELECT * FROM x CROSS JOIN y;
sqlite> SELECT * FROM x, y;

a           b           c           d
----------  ----------  ----------  ----------
1           Alice       1           3.14159
1           Alice       1           2.71828
1           Alice       2           1.61803
2           Bob         1           3.14159
2           Bob         1           2.71828
2           Bob         2           1.61803
3           Charlie     1           3.14159
3           Charlie     1           2.71828
3           Charlie     2           1.61803

В случае перекрестного соединения каждая строка в таблице a сопоставляется с каждой строкой в таблице y. Поскольку в обеих таблицах было три строки и два столбца, набор результатов состоит из девяти строк (3·3) и четырех столбцов (2+2).

JOIN...ON

Затем довольно простое внутреннее соединение с использованием базового условия объединения ON:

sqlite> SELECT * FROM x JOIN y ON a = c;

a           b           c           d
----------  ----------  ----------  ----------
1           Alice       1           3.14159
1           Alice       1           2.71828
2           Bob         2           1.61803

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

Следующий оператор требует, чтобы столбцы были уточнены, поскольку и таблица x, и таблица z имеют столбец. Обратите внимание, что возвращаются два разных столбца, по одному из каждой исходной таблицы:

sqlite> SELECT * FROM x JOIN z ON x.a = z.a;

a           b           a           e
----------  ----------  ----------  ----------
1           Alice       1           100
1           Alice       1           150
3           Charlie     3           300

JOIN...USING, NATURAL JOIN

Если мы используем синтаксис NATURAL JOIN или USING, повторяющийся столбец будет удален. Поскольку и таблица x, и таблица z имеют только общий столбец a, оба этих оператора дают одинаковый результат:

sqlite> SELECT * FROM x JOIN z USING ( a );
sqlite> SELECT * FROM x NATURAL JOIN z;

a           b           e
----------  ----------  ----------
1           Alice       100
1           Alice       150
3           Charlie     300

OUTER JOIN

LEFT OUTER JOIN вернет те же результаты, что и INNER JOIN, но также будет включать строки из таблицы x (левая/первая таблица), которые не были сопоставлены:

sqlite> SELECT * FROM x LEFT OUTER JOIN z USING ( a );

a           b           e
----------  ----------  ----------
1           Alice       100
1           Alice       150
2           Bob         [NULL]
3           Charlie     300

В этом случае строка Bob из таблицы x не имеет соответствующей строки в таблице z. Эти значения столбцов, обычно предоставляемые таблицей z, дополняются NULL, а затем строка включается в набор результатов.

JOIN-соединения

Также возможен JOIN нескольких таблиц вместе. В этом случае мы соединяем таблицу x с таблицей y, а затем присоединяем результат к таблице z:

sqlite> SELECT * FROM x JOIN y ON x.a = y.c LEFT OUTER JOIN z ON y.c = z.a;

a           b           c           d           a           e
----------  ----------  ----------  ----------  ----------  ----------
1           Alice       1           3.14159     1           100
1           Alice       1           3.14159     1           150
1           Alice       1           2.71828     1           100
1           Alice       1           2.71828     1           150
2           Bob         2           1.61803     [NULL]      [NULL]

Если вы не видите, что здесь происходит, работайте с объединениями по одному. Сначала посмотрите, что даст FROM x JOIN y ON x.a = y.c (показано в одном из предыдущих примеров). Затем посмотрите, как этот набор результатов будет сочетаться с таблицей z с помощью LEFT OUTER JOIN.

Самосоединение JOIN

В нашем последнем примере соединения показано самосоединение, при котором таблица соединяется сама с собой. Это создает два уникальных экземпляра одной и той же таблицы и требует использования псевдонимов таблиц:

sqlite> SELECT * FROM x AS x1 JOIN x AS x2 ON x1.a + 1 = x2.a;

a           b           a           b
----------  ----------  ----------  ----------
1           Alice       2           Bob
2           Bob         3           Charlie

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

Примеры WHERE

Далее предложение WHERE используется для фильтрации строк. Мы можем выделить конкретную строку:

sqlite> SELECT * FROM x WHERE b = 'Alice';

a           b
----------  ----------
1           Alice

Или диапазон значений:

sqlite> SELECT * FROM y WHERE d BETWEEN 1.0 AND 3.0;

c           d
----------  ----------
1           2.71828
2           1.61803

В этом случае выражение WHERE ссылается на выходной столбец по присвоенному имени:

sqlite> SELECT c, d, c+d AS sum FROM y WHERE sum < 4.0;

c           d           sum
----------  ----------  ----------
1           2.71828     3.71828
2           1.61803     3.61803

Примеры GROUP BY

Теперь давайте посмотрим на несколько операторов GROUP BY. Здесь мы группируем таблицу z по столбцу a. Поскольку в z.a есть три уникальных значения, на выходе будет три строки. Однако только группа a=1 имеет более одной строки. Мы можем видеть это в значениях count(), возвращаемых вторым столбцом:

sqlite> SELECT a, count(a) AS count FROM z GROUP BY a;

a           count
----------  ----------
1           2
3           1
9           1

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

sqlite> SELECT a, sum(e) AS total FROM z GROUP BY a;

a           total
----------  ----------
1           250
3           300
9           900

Мы даже можем вычислить собственное среднее значение и сравнить его с агрегатом avg():

sqlite> SELECT a, sum(e), count(e),
   ...>    sum(e)/count(e) AS expr, avg(e) AS agg
   ...>    FROM z GROUP BY a;

a           sum(e)      count(e)    expr        agg
----------  ----------  ----------  ----------  ----------
1           250         2           125         125.0
3           300         1           300         300.0
9           900         1           900         900.0

Предложение HAVING можно использовать для фильтрации строк на основе результатов агрегирования sum():

sqlite> SELECT a, sum(e) AS total FROM z GROUP BY a HAVING total > 500;

a           total
----------  ----------
9           900

Примеры ORDER BY

Вывод также можно отсортировать. Большинство этих примеров уже выглядят отсортированными, но в основном это случайность. Предложение ORDER BY применяет определенный порядок:

sqlite> SELECT * FROM y ORDER BY d;

c           d
----------  ----------
2           1.61803
1           2.71828
1           3.14159

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

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

Что дальше

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

Глава 6
Дизайн базы данных

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

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

Таблицы и ключи

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

Ключи определяют таблицу

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

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

Хотя в столбцах, составляющих первичный ключ, нет ничего особенного, сами ключи играют очень важную роль в проектировании и использовании базы данных. Их роль в качестве уникального идентификатора для каждой строки делает их аналогичными ключу хеш-таблицы или ключу словарного класса структуры данных. По сути, они представляют собой «поисковые» значения для строк таблицы.

Первичный ключ можно определить с помощью команды CREATE TABLE. Явная идентификация первичного ключа приведет к тому, что система базы данных автоматически создаст UNIQUE индекс для всех столбцов первичного ключа. Объявление первичного ключа также позволяет использовать некоторые сокращения синтаксиса при установлении отношений между таблицами.

Например, это определение таблицы определяет поле employee_id как первичный ключ:

CREATE TABLE employee (
    employee_id   INTEGER   PRIMARY KEY   NOT NULL,
    name          TEXT   NOT NULL
    /* ...etc... */
);

Дополнительную информацию о синтаксисе, используемом для определения первичного ключа, см. в разделе «Первичные ключи».

В документации схемы первичные ключи часто обозначаются аббревиатурой «PK». Первичные ключи также часто подчеркиваются двойным подчеркиванием при составлении таблиц, как показано на рис. 6-1.

Рисунок 6-1. Первичные ключи иногда обозначаются аббревиатурой PK. При построении схемы таблицы также часто используется двойное подчеркивание.

Многие запросы к базе данных используют первичный ключ таблицы в качестве ввода или вывода. У базы данных может быть запрос на возврат строки с заданным ключом, например, «вернуть запись для сотрудника № 953». Также часто запрашивают набор ключей, например, «собрать значения идентификаторов для всех сотрудников, нанятых более двух лет назад». Затем этот набор ключей может быть присоединен к другой таблице как часть отчета.

Внешние ключи

Помимо идентификации каждой строки в таблице, первичные ключи также играют центральную роль в объединении таблиц. Поскольку первичный ключ действует как уникальный идентификатор строки, вы можете создать ссылку на конкретную строку, записав первичный ключ строки. Это называется внешним ключом. Внешний ключ - это копия или запись первичного ключа, который используется как ссылка или указатель на другую (или «внешнюю») строку, чаще всего в другой таблице.

Как и первичный ключ, столбцы, составляющие внешний ключ, можно определить с помощью команды CREATE TABLE. В этом примере мы определяем формат назначения задачи. Каждая задача назначается конкретному сотруднику путем ссылки на поле employee_id из таблицы сотрудников:

CREATE TABLE task_assignment (
    task_assign_id   INTEGER   PRIMARY KEY,
    task_name        TEXT      NOT NULL,
    employee_id      INTEGER   NOT NULL   REFERENCES employee( employee_id )
    /* ...etc... */
);

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

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

В отличие от собственного первичного ключа таблицы, внешние ключи не обязательно должны быть уникальными. Это связано с тем, что несколько значений внешнего ключа (несколько строк) в одной таблице могут ссылаться на одну и ту же строку в другой таблице. Это называется отношением "один ко многим". См. раздел «Отношения один ко многим». Внешние ключи часто помечаются аббревиатурой «FK», как показано на рис. 6-2.

Рисунок 6-2. Внешние ключи - это копии первичного ключа из другой строки. Внешние ключи действуют как ссылки или указатели на другие строки. Их часто обозначают аббревиатурой FK.

Ограничения внешнего ключа

Объявление внешних ключей в определении таблицы позволяет базе данных применять ограничения внешнего ключа. Ограничения внешнего ключа используются для синхронизации ссылок на внешний ключ. Среди прочего, ограничения внешнего ключа могут предотвратить «висячие ссылки», требуя, чтобы все значения внешнего ключа правильно соответствовали значению строки из столбцов таблицы, на которую указывает ссылка. Внешние ключи также могут иметь значение NULL. NULL четко отмечает внешний ключ как неназначенный, что немного отличается от недопустимого значения. Во многих случаях неназначенные внешние ключи не соответствуют структуре базы данных. В этом случае столбцы внешнего ключа должны быть объявлены с ограничением NOT NULL.

Используя наш предыдущий пример, ограничения внешнего ключа потребуют, чтобы каждый элемент task_assignment.employee_id содержал значение действительного employee.employee_id. По умолчанию внешний ключ NULL также разрешен, но мы определили столбец task_assignment.employee_id с ограничением NOT NULL. Это требует, чтобы каждая задача ссылалась на действительного сотрудника.

Встроенная поддержка внешнего ключа была добавлена в SQLite 3.6.19, но по умолчанию отключена. Вы должны использовать команду PRAGMA foreign_keys, чтобы включить ограничения внешнего ключа. Это было сделано, чтобы избежать проблем с существующими приложениями и файлами баз данных. В будущей версии SQLite по умолчанию могут быть включены ограничения внешнего ключа. Если ваше приложение зависит от этого параметра, оно должно явно включать или выключать его.

Изменения таблицы внешнего ключа или таблицы, на которую имеется ссылка, потенциально могут вызвать нарушение ограничения внешнего ключа. Например, если оператор попытается обновить значение task_assignment.employee_id до недопустимого employee_id, ограничение внешнего ключа будет нарушено. Точно так же, если строке employee было присвоено новое значение employee_id, любые существующие ссылки task_assignment, указывающие на старое значение, станут недействительными. Это также нарушит ограничение внешнего ключа.

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

Также доступны другие разрешения конфликтов. Например, при использовании определения внешнего ключа ON DELETE CASCADE удаление сотрудника приведет к тому, что база данных автоматически удалит все задачи, назначенные этому сотруднику. Дополнительные сведения о разрешении конфликтов и других дополнительных параметрах внешнего ключа см. на веб-сайте SQLite. Актуальную документацию по поддержке внешних ключей SQLite можно найти на http://www.sqlite.org/foreignkeys.html.

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

Общие идентификационные ключи

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

Когда столбец SQLite определен как INTEGER PRIMARY KEY, этот столбец заменит скрытый столбец ROWID, который действует как корневой столбец каждой таблицы. Использование INTEGER PRIMARY KEY позволяет значительно повысить производительность. Это также позволяет SQLite автоматически назначать упорядоченные значения идентификаторов. Для получения дополнительной информации см. «Первичные ключи».

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

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

Фактически, многие наборы данных не имеют действительно уникального представления. Например, имена людей недостаточно уникальны, чтобы их можно было использовать в качестве ключей базы данных. Имена достаточно уникальны, и они хорошо справляются с идентификацией людей лично, но им не хватает неотъемлемой и полной уникальности, которой требует хороший дизайн базы данных. Имена также меняются время от времени, например, когда люди женятся.

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

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

Использование произвольного идентификатора также полезно, если обычный первичный ключ имеет большой физический размер. Поскольку каждый внешний ключ является полной копией первичного ключа, неразумно использовать длинное текстовое значение или BLOB в качестве первичного ключа. Скорее было бы лучше использовать произвольный идентификатор и просто ссылку на идентификатор.

Последний комментарий по именам ключей. Часто возникает соблазн назвать общее поле идентификатора чем-нибудь простым, например id. В конце концов, если у вас есть таблица employee, может показаться несколько излишним называть первичный ключ employee_id; вы получаете множество ссылок на столбцы, которые читают employee.employee_id, когда кажется, что employee.id достаточно ясен.

Что ж, само по себе это достаточно ясно, но первичные ключи, как правило, отображаются в других таблицах как внешние ключи. В то время как employee.employee_id может быть немного избыточным, имя task_assignment.employee_id - нет. Это имя также дает вам важные подсказки о функции столбца (внешний ключ) и о том, на какую таблицу и столбец он ссылается (столбец employee_id, который является столбцом PK таблицы employee). Использование одного и того же имени для первичных и внешних ключей делает внутреннее значение и связь намного более очевидными. Он также позволяет использовать ярлыки, такие как синтаксис NATURAL JOIN или JOIN...USING( ). Обе эти формы требуют, чтобы совпадающие столбцы имели одно и то же имя.

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

Будьте конкретны

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

Менталитет «таблицы должны быть большими» быстро приведет к плохому дизайну. Хотя в основе большинства проектов будет несколько больших значимых таблиц, типичный дизайн также будет иметь изрядное количество меньших вспомогательных таблиц, которые могут содержать только два или три столбца данных. Фактически, в некоторых случаях вы можете создать таблицы, которые состоят только из внешних ссылок на другие таблицы. Создание новой таблицы - единственное, что вам нужно для определения новой структуры данных или контейнера данных, поэтому каждый раз, когда вам понадобится новый контейнер, он будет указывать на новую таблицу.

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

Общие структуры и отношения

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

Отношения один-к-одному

Самым основным видом межтабличных отношений является взаимно однозначное отношение (one-to-one relationship). Как вы можете догадаться, этот тип отношений устанавливает ссылку из одной строки в одной таблице на одну строку в другой таблице. Чаще всего взаимно-однозначные отношения представлены тем, что внешний ключ в одной таблице ссылается на первичный ключ в другой таблице. Если столбец внешнего ключа сделан уникальным, будет разрешена только одна ссылка. Как показано на рис. 6-3, уникальный внешний ключ создает взаимно однозначную связь между строками двух таблиц.

Рисунок 6-3. Во взаимно-однозначном отношении таблица B имеет внешний ключ, который ссылается на первичный ключ таблицы A. Это связывает каждый внешний ключ, отличный от NULL в таблице B, с некоторой строкой в таблице A.

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

Отношения «один-к-одному» обычно используются для создания подробных таблиц (detail tables). Как следует из названия, подробная таблица обычно содержит детали, которые связаны с записями в более заметной таблице. Таблицы подробностей могут использоваться для хранения данных, относящихся только к небольшому подразделу приложения базы данных. Отделение подробных данных от основных таблиц позволяет различным разделам структуры базы данных развиваться и расти гораздо более независимо.

Подробные таблицы также могут быть полезны, когда расширенные данные применяются только к ограниченному количеству записей. Например, на веб-сайте может быть таблица sales_items, в которой перечислена общая информация (цена, инвентарь, вес и т. д.) для всех доступных товаров. Затем данные, относящиеся к конкретному типу, могут храниться в подробных таблицах, например cd_info для компакт-дисков (имя исполнителя, название альбома и т. д.) или dvd_info (режисеры, студия и т. д.) для DVD. Хотя таблица sales_items будет иметь уникальное взаимно однозначное отношение с каждой таблицей информации, зависящей от типа, на каждую отдельную строку в таблице sales_item будет ссылаться только одна подробная таблица.

Отношения «один к одному» также можно использовать для изоляции очень больших элементов данных, например BLOBов. Рассмотрим базу данных сотрудников, содержащую изображения каждого сотрудника. Из-за накладных расходов на хранение данных и ввод-вывод может быть неразумно включать столбец photo непосредственно в таблицу employee, но легко создать таблицу фотографий, которая ссылается на таблицу employee. Рассмотрим эти две таблицы:

CREATE TABLE employee (
    employee_id   INTEGER   NOT NULL   PRIMARY KEY,
    name          TEXT      NOT NULL
    /* ...etc... */
);

CREATE TABLE employee_photo (
    employee_id   INTEGER   NOT NULL   PRIMARY KEY
                  REFERENCES employee,
    photo_data    BLOB
    /* ...etc... */
);

Этот пример немного уникален, потому что столбец employee_photo.employee_id является как первичным ключом для таблицы employee_photo, так и внешним ключом для таблицы employee. Поскольку нам нужны отношения «один к одному», имеет смысл просто объединить первичные ключи в пары. Поскольку этот внешний ключ не допускает использование ключей NULL, каждая строка employee_photo должна соответствовать определенной строке employee. Однако база данных не гарантирует, что у каждого employee будет соответствующая фотография employee_photo.

Отношения один-ко-многим

Отношения «один ко многим» устанавливают связь между одной строкой в одной таблице и несколькими строками в другой таблице. Это достигается путем расширения отношения «один-к-одному», что позволяет одной стороне содержать повторяющиеся ключи. Отношения «один ко многим» часто используются для связывания списков или массивов элементов с одной строкой. Например, несколько адресов доставки могут быть связаны с одной учетной записью. Если не указано иное, «многие» обычно означает «ноль или более».

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

Рис 6-4 иллюстрирует эту взаимосвязь более подробно.

Рисунок 6-4. При построении отношения «один ко многим» первичные ключи должны быть уникальными, но столбцы внешнего ключа могут содержать повторяющиеся значения. Это означает, что отношение «один ко многим» должно иметь внешний ключ на стороне «многие». Здесь мы видим, что значение внешнего ключа в таблице B относится к первичному ключу в таблице A.

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

Если вам нужно представить список или массив, попробуйте использовать отношение «один ко многим».

То же самое верно всякий раз, когда вы начинаете рассматривать наборы столбцов, такие как item0, item1, item2 и т. д., которые все предназначены для хранения экземпляров одного и того же типа значения. Таким конструкциям присущи ограничения, и процесс вставки, обновления и удаления становится довольно сложным. Гораздо проще просто разбить данные в отдельную таблицу и установить правильную взаимосвязь.

Одним из примеров отношений «один ко многим» являются музыкальные альбомы и песни, которые они содержат. В каждом альбоме есть список песен, связанных с этим альбомом. Например:

CREATE TABLE albums (
        album_id INTEGER   NOT NULL   PRIMARY KEY,
        album_name TEXT );

CREATE TABLE tracks (
        track_id INTEGER   NOT NULL   PRIMARY KEY,
        track_name TEXT,
        track_number INTEGER,
        track_length INTEGER, -- in seconds
        album_id INTEGER   NOT NULL   REFERENCES albums );

У каждого альбома и трека есть уникальный идентификатор. У каждого трека также есть ссылка на свой альбом по внешнему ключу. Рассмотреть возможность:

INSERT INTO albums VALUES ( 1, "The Indigo Album" );
INSERT INTO tracks VALUES ( 1, "Metal Onion", 1, 137, 1 );
INSERT INTO tracks VALUES ( 2, "Smooth Snake", 2, 212, 1 );
INSERT INTO tracks VALUES ( 3, "Turn A", 3, 255, 1 );

INSERT INTO albums VALUES ( 2, "Morning Jazz" );
INSERT INTO tracks VALUES ( 4, "In the Bed", 1, 214, 2 );
INSERT INTO tracks VALUES ( 5, "Water All Around", 2, 194, 2 );
INSERT INTO tracks VALUES ( 6, "Time Soars", 3, 265, 2 );
INSERT INTO tracks VALUES ( 7, "Liquid Awareness", 4, 175, 2 );

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

sqlite> SELECT album_name, track_name, track_number
   ...>   FROM albums JOIN tracks USING ( album_id )
   ...>   ORDER BY album_name, track_number;

album_name    track_name  track_number
------------  ----------  ------------
Morning Jazz  In the Bed  1
Morning Jazz  Water All   2
Morning Jazz  Time Soars  3
Morning Jazz  Liquid Awa  4
The Indigo A  Metal Onio  1
The Indigo A  Smooth Sna  2
The Indigo A  Turn A      3

Мы также можем управлять группировкой треков:

sqlite> SELECT album_name, sum( track_length ) AS runtime, count(*) AS tracks
   ...>   FROM albums JOIN tracks USING ( album_id )
   ...>   GROUP BY album_id;

album_name        runtime     tracks
----------------  ----------  ----------
The Indigo Album  604         3
Morning Jazz      848         4

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

Отношения многие-ко-многим

Следующий шаг - отношения «многие ко многим». Отношение многие ко многим (many-to-many relationship) связывает одну строку в первой таблице со многими строками во второй таблице, одновременно позволяя связывать отдельные строки во второй таблице с несколькими строками в первой таблице. В некотором смысле отношения «многие ко многим» - это на самом деле два отношения «один ко многим», построенные друг на друге.

На рис. 6-5 показан классический пример людей и групп «многие ко многим». Один человек может принадлежать к разным группам, в то время как каждая группа состоит из множества разных людей. Общие операции - найти все группы, к которым принадлежит человек, или найти всех людей в группе.

Рисунок 6-5. Отношения «многие ко многим» подобны двум отношениям «один ко многим», построенным друг над другом. В этом примере каждый отдельный человек может быть членом одной или нескольких групп, а каждая группа может состоять из одного или нескольких человек.

Отношения «многие ко многим» немного сложнее, чем другие отношения. Хотя таблицы имеют отношение «многие ко многим» друг с другом, записи в обеих таблицах должны оставаться уникальными. Мы не можем дублировать строки с персоналом или сгруппированные строки с целью сопоставления ключей. Это проблема, поскольку каждый внешний ключ может ссылаться только на одну строку. Это делает невозможным для внешнего ключа одной таблицы (например, группы) ссылаться на несколько строк другой таблицы (например, людей).

Чтобы решить эту проблему, мы вернемся к предыдущему совету: если вам нужно добавить список в строку, разбейте этот список на отдельную таблицу и установите связь «один ко многим» с новой таблицей. Вы не можете напрямую представить отношение «многие ко многим» только с двумя таблицами, но вы можете взять пару отношений «один ко многим» и связать их вместе. Для связи требуется небольшая таблица, известная как таблица связи (link table) или таблица мостов (bridge table), которая находится между двумя многочисленными таблицами. Для каждого отношения «многие ко многим» требуется уникальная таблица мостов.

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

Рисунок 6-6. Для реализации отношения «многие ко многим» требуется таблица моста. В этом примере каждая строка таблицы моста представляет членство одного человека в одной группе. Обратите внимание, что первичный ключ таблицы моста - это многоколоночный ключ (p_id, g_id). Это сохраняет уникальность членства.

Логическое представление отношения «многие ко многим» на самом деле является отношением «один ко многим к одному» с таблицей моста (действующей как запись членства) посередине. Каждый человек имеет отношение «один ко многим» с членством, так же как группы имеют отношение «один ко многим» с членством. В дополнение к связыванию двух таблиц вместе, таблица моста может также содержать дополнительную информацию о самом членстве, например дату первого соединения или дату истечения срока действия.

Вот три таблицы. Таблицы people и groups достаточно очевидны. Таблица p_g_bridge действует как таблица моста между таблицей people и таблицей groups. Оба столбца являются ссылками на внешние ключи: один для people, а другой - для groups. Установление первичного ключа для обоих столбцов внешнего ключа гарантирует, что членство останется уникальным:

CREATE TABLE people ( pid INTEGER   PRIMARY KEY, name TEXT, ... );
CREATE TABLE groups ( gid INTEGER   PRIMARY KEY, name TEXT, ... );
CREATE TABLE p_g_bridge(
        pid INTEGER   NOT NULL   REFERENCES people,
        gid INTEGER   NOT NULL   REFERENCES groups,
        PRIMARY KEY ( pid, gid )
);

В этом запросе будут перечислены все группы, к которым принадлежит человек:

SELECT groups.name AS group_name
  FROM people JOIN p_g_bridge USING ( pid ) JOIN groups USING ( gid )
  WHERE people.name = search_person_name;

Запрос просто связывает people с groups с помощью таблицы мостов, а затем отфильтровывает соответствующие строки.

Нам не всегда нужны все три таблицы. Этот запрос подсчитывает всех членов группы без использования таблицы people:

SELECT name AS group_name, count(*) AS members
  FROM groups JOIN p_g_bridge USING ( gid )
  GROUP BY gid;

Мы можем выполнить множество других запросов, например найти все группы, в которых нет участников:

SELECT name AS group_name
  FROM groups LEFT OUTER JOIN p_g_bridge USING ( gid )
  WHERE pid IS NULL;

Этот запрос выполняет внешнее соединение таблицы groups с таблицей p_g_bridge. Любые несовпадающие групповые строки будут дополнены NULL в столбце p_g_bridge.pid. Поскольку этот столбец помечен как NOT NULL, мы знаем, что единственный возможный способ сделать строку в этом столбце NULL - это внешнее соединение, то есть строка не соответствует ни одному членству. Очень похожий запрос можно использовать для поиска людей, у которых нет членства.

Иерархии и деревья

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

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

Существует два распространенных метода представления древовидной связи с использованием таблиц базы данных. Первая - это модель смежности (adjacency model), которая использует простое представление, которое легко изменить, но сложно запросить. Другое распространенное представление - это вложенный набор (nested set), который позволяет выполнять относительно простые запросы, но за счет более сложного представления, изменение которого может быть дорогостоящим.

Модель смежности

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

Например, вот базовая таблица модели смежности:

CREATE TABLE tree (
    node   INTEGER   NOT NULL   PRIMARY KEY,
    name   TEXT,
    parent INTEGER   REFERENCES tree );

Каждый узел в дереве будет иметь уникальный идентификатор узла. Каждый узел также будет иметь ссылку на свой родительский узел. Корень дерева может просто иметь ссылку на родительскую ссылку NULL. Это позволяет хранить несколько деревьев в одной таблице, поскольку несколько узлов могут быть определены как корневой узел разных деревьев.

Если мы хотим представить это дерево:

A
  A.1
    A.1.a
  A.2
    A.2.a
    A.2.b
  A.3

Мы бы использовали следующие данные:

INSERT INTO tree VALUES ( 1, 'A',     NULL );
INSERT INTO tree VALUES ( 2, 'A.1',   1 );
INSERT INTO tree VALUES ( 3, 'A.1.a', 2 );
INSERT INTO tree VALUES ( 4, 'A.2',   1 );
INSERT INTO tree VALUES ( 5, 'A.2.a', 4 );
INSERT INTO tree VALUES ( 6, 'A.2.b', 4 );
INSERT INTO tree VALUES ( 7, 'A.3',   1 );

Следующий запрос даст список узлов и родителей, присоединив к себе древовидную таблицу:

sqlite> SELECT n.name AS node, p.name AS parent
   ...>   FROM tree AS n JOIN tree AS p ON n.parent = p.node;

node        parent
----------  ----------
A.1         A
A.1.a       A.1
A.2         A
A.2.a       A.2
A.2.b       A.2
A.3         A

Вставка или удаление узлов довольно проста, как и перемещение поддеревьев к разным родителям.

Что непросто, так это древовидные операции, такие как подсчет всех узлов в поддереве или вычисление глубины конкретного узла (часто используется для форматирования вывода). Вы можете выполнять ограниченный обход (например, поиск прародителя), присоединяя древовидную таблицу к самой себе несколько раз, но вы не можете написать один запрос, который может вычислить ответ на эти типы вопросов на дереве произвольного размера. Единственный выбор - написать подпрограммы приложения, которые проходят по разным уровням дерева, вычисляя ответы, которые вы ищете.

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

Вложенный набор

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

Вложенная таблица множеств может выглядеть так:

CREATE TABLE nest (
    name TEXT,
    lower INTEGER   NOT NULL   UNIQUE,
    upper INTEGER   NOT NULL   UNIQUE,
    CHECK ( lower < upper ) );

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

A( A.1( A.1.a( ) ), A.2( A.2.a( ), A.2.b( )  ), A.3(  )  )
 1    2      3 4 5     6      7 8       9 10 11    12 13 14

Или в SQL:

INSERT INTO nest VALUES ( 'A',      1, 14 );
INSERT INTO nest VALUES ( 'A.1',    2,  5 );
INSERT INTO nest VALUES ( 'A.1.a',  3,  4 );
INSERT INTO nest VALUES ( 'A.2',    6, 11 );
INSERT INTO nest VALUES ( 'A.2.a',  7,  8 );
INSERT INTO nest VALUES ( 'A.2.b',  9, 10 );
INSERT INTO nest VALUES ( 'A.3',   12, 13 );

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

SELECT name FROM nest WHERE lower + 1 = upper;

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

sqlite> SELECT n.name AS name, count(*) AS depth
   ...>   FROM nest AS n JOIN nest AS p
   ...>   ON p.lower <= n.lower AND p.upper >= n.upper
   ...>   GROUP BY n.name;

name        depth
----------  ----------
A           1
A.1         2
A.1.a       3
A.2         2
A.2.a       3
A.2.b       3
A.3         2

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

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

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

Больше информации

Это всего лишь краткий обзор того, как представлять древовидные отношения. Если вам нужно реализовать дерево, я предлагаю вам выполнить несколько поисков в Интернете по модели смежности (adjacency model) или вложенному набору (nested set). Многие из более крупных книг по SQL, упомянутых в разделе «Заключение», также содержат разделы, посвященные дереву и иерархиям.

Нормальная форма

Вы не углубитесь в большинство книг по дизайну баз данных, не прочитав обсуждение нормализации и нормальных форм. Нормальные формы (Normal Forms) - это серия форм или спецификаций дизайна таблиц, которые описывают лучший способ размещения данных в базе данных. Чем выше нормальная форма, тем больше нормализована база данных. Каждая форма основывается на предыдущей, добавляя дополнительные правила или условия, которые необходимо соблюдать. Нормализация (Normalization) - это процесс устранения дублирования данных, более четкого определения ключевых отношений и в целом перехода к более идеализированной форме базы данных. Различные таблицы в одной базе данных могут находиться на разных уровнях.

Большинство людей распознают пять нормальных форм, которые просто называются от первой нормальной формы до пятой нормальной формы. Часто они обозначаются сокращенно от 1NF до 5NF. Есть также несколько именованных форм, например, Нормальная форма Бойса-Кодда (BCNF). Большинство этих других форм примерно эквивалентны одной из пронумерованных форм. Например, BCNF - это небольшое расширение Третьей нормальной формы. Некоторые люди также признают более высокие уровни нормализации, такие как Шестая нормальная форма и выше, но эти крайние уровни нормализации выходят далеко за рамки практических задач большинства разработчиков баз данных.

Нормализация

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

Другое преимущество, которое проявляется в большей степени во время выполнения, состоит в том, что целостность данных намного проще обеспечить и поддерживать в нормализованной базе данных. Хотя общий дизайн базы данных часто более сложен (т. е. чем таблиц), отдельные части обычно намного проще и выполняют более четко определенные роли. Это часто приводит к повышению производительности INSERT, UPDATE и DELETE, поскольку изменения часто меньше и более локализованы.

Локализация данных - основная цель нормализации данных. Большинство обычных форм имеют дело с устранением избыточных или реплицированных данных, так что каждый уникальный токен данных сохраняется один - и только один раз - в базе данных. Все остальное просто ссылается на эту окончательную копию. Это упрощает обновление, поскольку есть только одно место, где нужно применить обновление, но также делает данные более согласованными, поскольку невозможно, чтобы несколько копий данных не были синхронизированы друг с другом. При работе над дизайном схемы вы должны постоянно задавать себе вопрос: «Если этот фрагмент данных изменится, в скольких разных местах мне нужно будет внести это изменение?» Если ответ отличный от единицы, скорее всего, вы не в нормальной форме.

Денормализация

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

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

Первая нормальная форма

Первая нормальная форма (First Normal Form) или 1NF - это самый низкий уровень нормализации. В первую очередь это связано с тем, чтобы таблица была в правильном формате. Есть три условия, которые должны быть выполнены, чтобы таблица находилась в 1NF.

Первое условие касается оформления порядка. Чтобы быть в 1NF, отдельные строки таблицы не могут иметь какой-либо значимый или неотъемлемый порядок. Каждая строка должна быть изолированной отдельной записью. Значение данных в одной строке не может зависеть ни от одного из значений данных из соседних строк, ни от порядка вставки, ни от некоторого порядка сортировки. Это условие обычно легко выполнить, поскольку SQL не гарантирует какой-либо порядок строк.

Второе условие - уникальность. Каждая строка в таблице 1NF должна быть уникальной и должна быть уникальной для тех столбцов, которые содержат значимые данные для приложения. Например, если единственной разницей между двумя строками является столбец ROWID, поддерживаемый базой данных, то эти строки на самом деле не уникальны. Однако вполне нормально рассматривать произвольный идентификатор последовательности (такой как INTEGER PRIMARY KEY) как часть данных приложения. Это условие устанавливает, что таблица должна иметь PRIMARY KEY определенного типа, состоящий из одного или нескольких столбцов, которые создают уникальное определение того, что представляет собой таблица.

Третье и последнее условие для 1NF требует, чтобы каждый столбец каждой строки содержал одно (и только одно) логическое значение, которое не может быть разбито дальше. Проблема не в составных типах, таких как даты (которые могут быть разбиты на целые числа дня, месяца и года), а в массивах или списках логических значений. Например, вы не должны записывать текстовое значение, которое содержит список логических независимых значений, разделенных запятыми. Массивы или списки следует разбивать на собственные отношения «один ко многим».

Вторая нормальная форма

Вторая нормальная форма (Second Normal Form) или 2NF, касается составных ключей (многоколоночных ключей) и того, как другие столбцы связаны с такими ключами. 2NF имеет только одно условие: каждый столбец, не являющийся частью первичного ключа, должен относиться к первичному ключу в целом, а не только к его частям.

Рассмотрим таблицу, в которой перечислены все конференц-залы в большом корпоративном кампусе. Как минимум, в таблице conf_room есть столбцы для building_numb и room_numb. Взятые вместе, эти две колонки однозначно идентифицируют любой конференц-зал на территории всего кампуса, так что это будет наш составной первичный ключ.

Затем рассмотрим столбец, например seating_capacity. Значения в этом столбце напрямую зависят от каждого конкретного конференц-зала. Это, по определению, делает столбец зависимым как от номера здания, так и от номера комнаты. Включение столбца seating_capacity не нарушит 2NF.

Теперь рассмотрим столбец типа building_address. Этот столбец зависит от столбца building_numb, но не зависит от столбца room_numb. Поскольку building_address зависит только от части первичного ключа, включение этого столбца в таблицу conf_room нарушит 2NF.

Поскольку 2NF специально предназначена для ключей с несколькими столбцами, любая таблица с первичным ключом с одним столбцом, которая находится в 1NF, автоматически попадает в 2NF.

Чтобы распознать столбец, который может нарушать 2NF, поищите столбцы с повторяющимися значениями. Если повторяющиеся значения имеют тенденцию совпадать с повторяющимися значениями в одном из столбцов первичного ключа, это явный признак проблемы. Например, столбец building_address будет иметь несколько повторяющихся значений (при условии, что в большинстве зданий есть более одного конференц-зала). Повторяющиеся значения адреса могут быть сопоставлены с повторяющимися значениями в столбце building_numb. Это выравнивание показывает, как столбец адреса привязан только к столбцу building_numb, а не ко всему первичному ключу.

Третья нормальная форма

Третья нормальная форма (Third Normal Form) или 3NF, расширяет 2NF, устраняя транзитивные ключевые зависимости. Транзитивная зависимость - это когда A зависит от B, а B зависит от C, и поэтому A зависит от C. 3NF требует, чтобы каждый столбец непервичного ключа имел прямую (нетранзитивную) зависимость от первичного ключа.

Например, рассмотрим базу данных инвентаризации, которая используется для отслеживания ноутбуков в малом бизнесе. Таблица laptop будет иметь первичный ключ, который однозначно идентифицирует каждый ноутбук, например, контрольный номер инвентаря. Скорее всего, в таблице будут другие столбцы, в которых будут указаны марка и модель машины, серийный номер и, возможно, дата покупки. В нашем примере таблица laptop также будет включать столбец responsible_person_id. Когда сотруднику назначается ноутбук, в этот столбец помещается его идентификационный номер сотрудника.

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

Теперь посмотрим, что происходит, когда мы добавляем столбец, например, responsible_person_email. Это столбец, содержащий адрес электронной почты ответственного лица. Значение этого столбца по-прежнему зависит от первичного ключа таблицы laptop. У каждого отдельного ноутбука есть определенное поле responsible_person_email, которое так же уникально, как и поле responsible_person_id.

Проблема в том, что значения в столбце responsible_person_email не зависят напрямую от конкретного ноутбука. Скорее, столбец электронной почты привязан к responsible_person_id, а responsible_person_id, в свою очередь, зависит от конкретного ноутбука. Эта транзитивная зависимость нарушает 3NF, указывая на то, что столбец responsesible_person_email не принадлежит ей.

В таблице employee мы также найдем столбец person_id и столбец email. Это вполне приемлемо, если person_id является первичным ключом (вероятно). Это сделало бы столбец email напрямую зависимым от первичного ключа, сохраняя таблицу в 3NF.

Хороший способ распознать столбцы, которые могут нарушить 3NF, - это поиск пар или наборов несвязанных столбцов, которые необходимо синхронизировать друг с другом. Рассмотрим таблицу laptop. Если система была переназначена новому человеку, вы всегда должны обновлять как столбец responsible_person_id, так и столбец responsible_person_email. Необходимость синхронизировать столбцы друг с другом - явный признак зависимости друг от друга, а не от первичного ключа.

Высшие нормальные формы

Мы не собираемся вдаваться в подробности BCNF, четвертой или пятой (или последующих) нормальных форм, кроме как упомянуть, что четвертая и пятая нормальные формы начинают иметь дело с межтабличными отношениями и тем, как различные таблицы взаимодействуют друг с другом. Большинство разработчиков баз данных прилагают серьезные усилия, чтобы все было реализовано в 3NF, а затем перестают об этом беспокоиться. Оказывается, что если вы разбираетесь в вещах и создаете дизайн таблиц, которые относятся к 3NF, весьма высоки шансы, что ваши таблицы также будут соответствовать условиям для 4NF и 5NF, если не выше. В значительной степени высшие нормальные формы представляют собой формальные способы решения некоторых крайних случаев, которые несколько необычны, особенно в более простых проектах.

Хотя условия нормальных форм основываются друг на друге, типичный процесс проектирования на самом деле не повторяется по отдельным формам. Вы не сядете с новым дизайном и не измените его, пока все не станет 1NF, просто чтобы развернуться и погадать с дизайном, пока все не станет 2NF, и так далее, изолированно, шаг за шагом. Как только вы поймете идеи и концепции, лежащие в основе Первой, Второй и Третьей нормальных форм, проектирование непосредственно в 3NF станет вашей второй натурой. Преодоление условий по одному может помочь вам отсеять особенно сложные проблемные места, но вам не понадобится много времени, чтобы понять, когда дизайн выглядит чистым, а когда что-то «просто не так».

Основная концепция, которую следует помнить, заключается в том, что каждая таблица должна пытаться представить одну и только одну вещь. Первичный ключ (ключи) для этой таблицы должен однозначно и по сути определять концепцию, лежащую в основе таблицы. Все остальные столбцы должны содержать вспомогательные данные, относящиеся к этой единственной концепции. Говоря о первых трех нормальных формах в статье CACM 1982 года, Уильям Кент написал, что каждый неключевой столбец «...должен предоставить факты о ключе, только о ключе и ни о чем, кроме ключа». Если вы включите в свои проекты только один формальный аспект теории баз данных, это будет отличным началом.

Индексы

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

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

Однако индексы не бесплатны. Каждый индекс должен поддерживать взаимно однозначное соответствие между записями индекса и строками таблицы. Если новая строка вставляется, обновляется или удаляется из таблицы, то же изменение должно быть внесено во все связанные индексы. Каждый новый индекс будет добавлять дополнительные служебные данные к командам INSERT, UPDATE и DELETE. Индексы также занимают место как на диске, так и в кэше страниц SQLite. Правильный, удачно размещенный индекс стоит своих затрат, но неразумно просто создавать случайные индексы в надежде, что один из них окажется полезным. Плохо размещенный индекс по-прежнему влечет за собой все издержки и может фактически замедлить выполнение запросов. Так что нужно постараться не сделать хуже, чем было.

Как они работают

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

Внутри нормальной таблицы строки хранятся в индексированной структуре. SQLite использует для этой цели B-Tree, которое представляет собой особый тип сбалансированного дерева с несколькими дочерними элементами. Детали не важны, кроме понимания того, что по мере того, как строки вставляются в дерево, строки сортируются, организуются и оптимизируются, так что строку с конкретным известным ROWID можно получить относительно напрямую и быстро.

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

Если взять такую таблицу:

CREATE TABLE tbl ( a, b, c, d );

А затем создать в ней индекс, который выглядит так:

CREATE INDEX idx_tbl_a_b ON tbl ( a, b );

База данных создает внутреннюю структуру данных, концептуально аналогичную:

SELECT a, b, ROWID FROM tbl ORDER BY a, b;

Если SQLite нужно быстро найти все строки, где, например, a = 45, он может использовать отсортированный индекс, чтобы быстро перейти к этому диапазону значений и извлечь соответствующие записи индекса. Если он ищет значение b, он может просто извлечь его из индекса и сделать это. Если нам нужно какое-либо другое значение в строке, оно должно получить всю строку. Это делается путем поиска ROWID. Последнее значение любой записи индекса - это ROWID соответствующей строки таблицы. Как только SQLite имеет список значений ROWID для всех строк, где a = 45, он может эффективно искать эти строки в исходной таблице и извлекать всю строку. Если все работает правильно, процесс поиска небольшого набора записей индекса, а затем их использование для поиска небольшого набора строк таблицы будет намного быстрее и эффективнее, чем выполнение полного сканирования таблицы.

Должен быть разнообразным

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

Точка безубыточности для показателей индекса находится где-то в диапазоне от 10% до 20%. Любой запрос, который извлекает больше строк из таблицы, лучше справится со сканированием таблицы, в то время как любой запрос, который извлекает меньше строк, получит улучшение за счет использования индекса. Если индекс не может изолировать строки до этого уровня, нет причин для его присутствия. Фактически, если оптимизатор запросов по ошибке использует индекс, который возвращает большой процент строк, индекс может снизить производительность и замедлить работу.

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

Во-вторых, даже если запрос запрашивает конкретное значение, это все равно может привести к более высокому проценту извлеченных строк, если индексированные столбцы не являются достаточно уникальными. Например, если столбец имеет только четыре уникальных значения, успешный запрос никогда не получит менее примерно 25% строк (при условии разумного распределения значений или запросов). Добавление индекса к этому типу столбца не улучшит производительность. Создание индекса для столбца true/false было бы еще хуже.

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

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

Ключи INTEGER PRIMARY KEY

Когда вы объявляете один или несколько столбцов PRIMARY KEY, система базы данных автоматически создает уникальный индекс по этим столбцам. Основная цель этого индекса - обеспечить выполнение ограничения UNIQUE, которое подразумевается для каждого PRIMARY KEY. Также бывает, что во многих операциях с базой данных обычно используется первичный ключ, например, естественные объединения или условный поиск в командах UPDATE или DELETE. Даже если индекс не требовался для принудительного применения ограничения UNIQUE, велики шансы, что вы все равно захотите индексировать эти столбцы.

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

Столбцы INTEGER PRIMARY KEY также обеспечивают лучшую производительность, чем стандартные индексы. Ключи INTEGER PRIMARY KEY имеют прямой доступ к полному набору данных строки. Обычный индекс просто ссылается на значение ROWID, которое затем ищется в корне таблицы. КлючиINTEGER PRIMARY KEY могут обеспечивать индексированный поиск, но пропустить этот второй шаг поиска и получить прямой доступ к данным таблицы.

Если вы используете общие значения идентификаторов строк, стоит попытаться определить их как столбцы INTEGER PRIMARY KEY. Это не только уменьшит размер базы данных, но и сделает ваши запросы более быстрыми и эффективными.

Значение порядка

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

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

Попробуйте поискать имя в телефонной книге. Вы можете использовать телефонную книгу, чтобы быстро найти номер телефона «Jennifer T. Smith». Сначала вы ищите фамилию «Smith». Затем вы уточняете поиск, ищите имя «Jennifer» и, наконец, букву «T» в середине (если требуется). Эта последовательность должна позволить вам очень быстро сосредоточиться на конкретной записи.

Телефонная книга не очень полезна, если позволяет быстро найти лишь всех людей с именем «Jennifer». Несмотря на то, что первое имя является частью индекса телефонной книги, это не первый столбец индекса. Если наш запрос не может предоставить конкретное условие для первого столбца многоколоночного индекса, индекс не может быть использован, и наш единственный вариант - выполнить полное сканирование. В этот момент обычно более эффективно выполнить полное сканирование самой таблицы, а не индекса.

Нет необходимости использовать каждый столбец, достаточно использовать столбцы по порядку. Если вы ищете в телефонной книге очень уникальное имя, возможно, вам не придется искать по имени или отчеству. Или, возможно, вы хотите найти каждого «Smith». В этом случае условиям запроса удовлетворяет только первый столбец.

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

Один за раз

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

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

Как правило, оптимизатор запросов может использовать только один индекс для каждого экземпляра таблицы для каждого запроса. Несколько индексов в одной таблице имеют смысл только в том случае, если у вас есть разные запросы, которые извлекают строки с использованием разных столбцов (или имеют несколько ограничений UNIQUE). Различные запросы, использующие разные столбцы, могут выбрать наиболее подходящий индекс, но каждый отдельный запрос будет использовать только один индекс для каждого экземпляра таблицы. Если вы хотите, чтобы в одном запросе использовалось несколько индексированных столбцов, вам понадобится многоколоночный индекс.

Основное исключение - серия условий OR. Если предложение WHERE имеет цепочку условий OR для одного или нескольких столбцов одной и той же таблицы, тогда бывают случаи, когда SQLite может продолжить и использовать несколько индексов для одной и той же таблицы в одном запросе.

Сводка индекса

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

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

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

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

Так как же поддерживать производительность? Для начала - солидный дизайн. Хорошо созданная и нормализованная база данных будет иметь большое значение для определения ваших шаблонов доступа. Если вы потратите время на правильную идентификацию и определение ваших первичных ключей, это позволит системе баз данных создать соответствующие индексы и даст оптимизатору запросов (и будущим разработчикам) некоторое представление о вашем дизайне и намерениях.

Также помните, что (за исключением ограничений UNIQUE) база данных будет работать правильно без каких-либо индексов. Это может быть медленным, но все ответы будут правильными. Это позволяет вам беспокоиться в первую очередь о дизайне, а потом о производительности. Если у вас есть рабочий дизайн, всегда можно добавлять индексы постфактум, без необходимости изменять структуру таблицы или команды запроса.

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

Начните отсюда. Измерьте производительность различных типов запросов, которые использует ваше приложение, и найдите проблемные области. Глядя на эти запросы, попытайтесь найти столбцы, в которых индекс может повысить производительность. Ищите любые ограничения и условия, которым может быть полезен индекс, особенно в условиях соединения, которые ссылаются на неключевые столбцы. Используйте EXPLAIN и EXPLAIN QUERY PLAN, чтобы понять, как SQLite обращается к данным. Эти команды также можно использовать для проверки того, использует ли запрос индекс или нет. Для получения дополнительной информации см. EXPLAIN в приложении C. Вы также можете использовать функцию sqlite3_stmt_status(), чтобы получить более взвешенное представление об эффективности оператора. См. sqlite3_stmt_status() в приложении G для получения более подробной информации.

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

Передача опыта проектирования

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

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

Таблицы - это типы

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

Таблицы следует рассматривать как определения типов. Никогда не следует использовать именованную таблицу в качестве организатора данных или группировки записей. Скорее, каждое определение таблицы следует рассматривать как определение структуры данных или определение класса. Команды SQL DDL, такие как CREATE TABLE, концептуально похожи на те файлы заголовков C/C++, которые определяют структуры данных и классы приложения. Сама таблица должна рассматриваться как глобальный пул управления для всех экземпляров этого типа. Если вам нужен новый экземпляр этого типа, вы просто вставляете новую строку. Если вам нужно сгруппировать или каталогизировать наборы экземпляров, делайте это с помощью ассоциаций ключей, а не путем создания новых таблиц.

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

Ключи - это обратные указатели

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

Хитрость в том, что ссылки на базу данных идут в обратном порядке. Вместо указателей, указывающих на владение («Я управляю этим»), внешние ключи указывают тип владения («Мной управляет это»).

В C или C++, если основная запись данных управляет списком подзаписей, основная запись данных будет иметь своего рода список указателей. Каждый указатель будет ссылаться на конкретную подзапись, связанную с этой основной записью. Если вы имеете дело с основной записью данных и вам нужен список подзаписей, вы просто посмотрите на список указателей.

Базы данных делают наоборот. В отношении «один ко многим» основная запись (строка «один») будет просто иметь первичный ключ. Все подзаписи (сторона строк «многие») будут иметь внешние ключи, указывающие на основную запись. Если вы имеете дело с основной записью и вам нужен список подзаписей, вы просите систему базы данных посмотреть глобальный пул всех экземпляров подзаписи и вернуть только те подзаписи, которыми управляет эта основная запись.

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

Сделай одно дело

Мой последний совет по дизайну более общий. Как и в случае со структурами данных или классами, фундаментальная идея таблицы заключается в том, что она должна представлять экземпляры одной единственной идеи или «вещи». Она может представлять собой набор существительных или вещей, таких как люди, или она может представлять глаголы или действия, такие как журнал транзакций. Таблицы могут даже представлять менее конкретные вещи, такие как таблица моста «многие ко многим», в которой записывается членство. Но независимо от того, что это такое, каждая таблица должна иметь одну и только одну четко определенную роль в базе данных. Обычно проблема не в большом количестве таблиц, а в их слишком малом количестве.

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

Итоги

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

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

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

Глава 7.
Интерфейс программирования C

Утилита командной строки sqlite3 предназначена для предоставления конечным пользователям интерактивного интерфейса. Это чрезвычайно полезно для разработки и тестирования SQL-запросов, отладки файлов базы данных и экспериментов с новыми функциями SQLite, но она никогда не предназначалась для взаимодействия с другими приложениями. Хотя утилиту командной строки можно использовать для очень простых сценариев и автоматизированных задач, если вы хотите написать приложение, которое серьезно использует библиотеку SQLite, ожидается, что вы будете использовать программный интерфейс.

Собственный интерфейс программирования SQLite основан на языке C, и это интерфейс, который будет рассмотрен в этой главе. Если вы работаете над чем-то другим, существуют оболочки и расширения для многих других языков, включая наиболее популярные языки сценариев. За исключением интерфейса Tcl, все эти оболочки предоставляются третьими сторонами и не являются частью продукта SQLite. Дополнительную информацию о языковых оболочках см. в разделе «Языки сценариев и другие интерфейсы».

Использование C API позволяет вашему приложению напрямую взаимодействовать с библиотекой SQLite и ядром базы данных. Вы можете связать статическую или динамическую сборку библиотеки SQLite с вашим приложением или просто включить исходный файл объединения в процесс сборки вашего приложения. Лучший выбор зависит от вашей конкретной ситуации. См. «Параметры сборки и установки» для получения дополнительных сведений.

C API довольно обширен и обеспечивает полный доступ ко всем функциям SQLite. Фактически, утилита командной строки sqlite3 написана с использованием общедоступного C API. В этой главе рассматриваются основные функции API, а в следующих главах рассматриваются более сложные функции.

Обзор API

Даже при использовании программного интерфейса основным способом взаимодействия с вашими данными является отправка команд SQL ядру базы данных. В этой главе основное внимание уделяется ядру API, который используется для передачи командных строк SQL механизму базы данных. Важно понимать, что не существует общедоступных функций для просмотра внутренней структуры таблицы или, например, доступа к древовидной структуре индекса. Вы должны использовать SQL для запроса данных из базы данных. Чтобы добиться успеха с SQLite API, вам нужно не только понимать C API, но также необходимо знать SQL, чтобы формировать осмысленные и эффективные запросы.

Структура

C API для SQLite 3 включает более дюжины структур данных, изрядное количество констант и более сотни различных вызовов функций. Хотя API несколько велик, его использование не должно быть сложным. Значительное количество функций являются узкоспециализированными и нечасто используются большинством разработчиков. Многие из оставшихся функций являются простыми вариациями одной и той же базовой операции. Например, существует дюжина вариантов функции sqlite3_value_xxx(), таких как sqlite3_value_int(), sqlite3_value_double() и sqlite3_value_text(). Все эти функции выполняют одну и ту же базовую операцию и могут считаться простыми разновидностями одного и того же базового интерфейса.

Когда я говорю о целой категории функций в тексте или псевдокоде, я буду просто называть их функциями sqlite3_value_xxx(). Большая часть документации SQLite ссылается на них как на sqlite3_value_*(), но я предпочитаю использовать нотацию xxx, чтобы избежать путаницы с указателями. Фактических функций SQLite3 с буквенной последовательностью xxx в имени не существует.

Все вызовы функций и типы данных общедоступного API имеют префикс sqlite3_, указывающий, что они являются частью версии 3.x продукта SQLite. Большинство констант, таких как коды ошибок, используют префикс SQLITE_. Различия в дизайне и API между SQLite 2.x и 3.x были достаточно значительными, чтобы гарантировать полное изменение всех имен и структур API. Глубина этих изменений потребовала, чтобы любой, кто обновился с SQLite 2 до SQLite 3, изменил свое приложение, поэтому изменение имен функций API только помогло сохранить различия между именами и решить любые вопросы о версии. Разные имена также позволяли приложениям, находившимся в процессе перехода, связываться с обеими библиотеками одновременно.

Помимо префикса sqlite3_, вызовы общедоступных функций можно идентифицировать по строчным буквам и знакам подчеркивания в их именах. В частных функциях используются слова с заглавной буквы (также известные как CamelCase). Например, sqlite3_create_function() - это общедоступная функция API (используется для регистрации определяемой пользователем функции SQL), а sqlite3CreateFunc() - это внутренняя функция, которую нельзя вызывать напрямую. Внутренние функции отсутствуют в общедоступном файле заголовка, не документированы и могут быть изменены в любое время.

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

Если требуется исправленная версия вызова функции, новая функция обычно будет представлена с суффиксом _v2. Например, когда была представлена более гибкая версия существующей функции sqlite3_open(), старая версия функции была сохранена как есть, и была представлена новая, улучшенная sqlite3_open_v2(). Хотя в настоящее время не существует функций _v3 (или выше), возможно, они будут введены в будущем.

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

Строки и Юникод

Существует ряд функций API, которые имеют вариант с числом 16 в конце имени. Например, доступны как функция sqlite3_column_text(), так и функция sqlite3_column_text16(). Первый запрашивает текстовое значение в формате UTF-8, а второй - текстовое значение в UTF-16.

Все строки в файле базы данных SQLite хранятся с использованием одной и той же кодировки. Файлы базы данных SQLite поддерживают кодировки UTF-8, UTF-16LE и UTF-16BE. Кодировка базы данных определяется при создании базы данных.

Независимо от базы данных вы можете вставлять или запрашивать текстовые значения в UTF-8 или UTF-16. SQLite автоматически преобразует текстовые значения между кодировкой базы данных и кодировкой API. Кодировка UTF-16, передаваемая 16 API, всегда будет в собственном порядке байтов машины. Буферы UTF-16 используют тип данных void* C. Тип данных wchar_t не используется, поскольку его размер не фиксирован, и не все платформы определяют 16-битный тип.

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

Все длины строк указываются в байтах, а не в символах, даже если в строке используется многобайтовая кодировка, например UTF-16.

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

Коды ошибок

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

Во всех случаях, если функция завершается успешно, она возвращает константу SQLITE_OK, которая имеет нулевое значение. Если что-то пошло не так, функции API вернут один из стандартных кодов ошибки, чтобы указать характер ошибки.

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

Ситуация достаточно сложна, чтобы ее обсудить позже в этой главе. Будет намного проще объяснить различные коды ошибок, когда вы увидите, как работает API. См. «Коды результатов и коды ошибок» для получения более подробной информации.

Я также должен сделать стандартную оговорку «делай, как я говорю, а не как я делаю» о правильной проверке кодов ошибок и возвращении результатов. Примеры кода в этой главе и в других местах этой книги имеют тенденцию к чрезвычайно сжатой (как и почти никакой) проверке ошибок. Это сделано для того, чтобы примеры были краткими и понятными. Излишне говорить, что это не лучший подход для производственного кода. При работе с собственным кодом поступайте правильно и проверяйте коды ошибок.

Структуры и распределения

Хотя собственный SQLite API часто называют C/C++ API, технически интерфейс доступен только на C. Как упоминалось в разделе «Сборка», исходный код SQLite строго основан на C, и поэтому может быть скомпилирован компилятором C. После компиляции библиотеку можно легко связать и вызвать из кода C и C++, а также из любого другого языка, который следует соглашениям о связывании C для вашей платформы.

Хотя API написан на C, он имеет отчетливый объектный привкус. Большая часть состояния программы содержится в серии непрозрачных структур данных, которые действуют как объекты. Наиболее распространенные структуры данных - это соединения с базой данных и подготовленные операторы. Вы никогда не должны напрямую обращаться к полям этих структур данных. Вместо этого предоставляются функции для создания, уничтожения и управления этими структурами во многом так же, как методы объектов используются для управления экземплярами объектов. В результате получается дизайн API, похожий на объектно-ориентированный дизайн. Фактически, если вы загрузите одну из сторонних оболочек C++, вы заметите, что оболочки имеют тенденцию быть довольно тонкими из-за большей части своей структуры базовых функций C и структур данных.

Важно, чтобы вы позволяли SQLite выделять собственные структуры данных и управлять ими. Дизайн API означает, что вы никогда не должны вручную выделять одну из этих структур или помещать эти структуры в стек. API предоставляет вызовы для внутреннего выделения необходимых структур данных, их инициализации и возврата. Точно так же для каждой функции, которая выделяет структуру данных, есть функция, которая используется для ее очистки и освобождения. Как и в случае с управлением памятью, вы должны быть уверены, что эти вызовы сбалансированы и что каждая созданная структура данных в конечном итоге будет освобождена.

Больше информации

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

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

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

Инициализация библиотеки

Прежде чем приложение сможет использовать библиотеку SQLite для каких-либо действий, необходимо сначала инициализировать библиотеку. Этот процесс выделяет некоторые стандартные ресурсы и устанавливает любые специфичные для ОС структуры данных. По умолчанию большинство основных вызовов функций API автоматически инициализируют библиотеку, если она еще не была инициализирована. Однако рекомендуется инициализировать библиотеку вручную.

int sqlite3_initialize( )
Инициализирует библиотеку SQLite. Эта функция должна вызываться перед любой другой функцией в SQLite API. Вызов этой функции после того, как библиотека уже инициализирована, безвреден. Эту функцию можно вызвать после завершения работы для повторной инициализации библиотеки. Возвращаемое значение SQLITE_OK указывает на успех.

Когда приложение завершит работу с библиотекой SQLite, библиотеку следует закрыть.

int sqlite3_shutdown( )
Освобождает все ресурсы, выделенные sqlite3_initialize(). Вызов этой функции до инициализации библиотеки или после того, как библиотека уже выключена, безвреден. Возвращаемое значение SQLITE_OK указывает на успех.

Из-за функций автоматической инициализации многие приложения никогда не вызывают ни одну из этих функций. Скорее они вызывают sqlite3_open() или одну из других основных функций и зависят от автоматической инициализации библиотеки. В большинстве случаев это достаточно безопасно, но для максимальной совместимости лучше всего вызывать эти функции явно.

Подключения к базе данных

Прежде чем мы сможем подготовить или выполнить операторы SQL, мы должны установить соединение с базой данных. Чаще всего это делается путем открытия или создания файла базы данных SQLite3. Когда вы закончите соединение с базой данных, его необходимо закрыть. Это проверяет отсутствие невыполненных операторов или выделенных ресурсов перед закрытием файла базы данных.

Открытие

Подключения к базе данных выделяются и устанавливаются с помощью одной из команд sqlite3_open_xxx(). Они передают соединение с базой данных в виде структуры данных sqlite3. Есть три варианта:

int sqlite3_open( const char *filename, sqlite3 **db_ptr ) int sqlite3_open16( const void *filename, sqlite3 **db_ptr )

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

Первый вариант предполагает, что имя файла базы данных закодировано в UTF-8, а второй предполагает, что имя файла базы данных закодировано в UTF-16.

int sqlite3_open_v2( const char *filename, sqlite3 **db_ptr, int flags, const char *vfs_name )

Вариант _v2 предлагает больший контроль над созданием и открытием файла базы данных. Первые два параметра совпадают. Предполагается, что имя файла находится в кодировке UTF-8. Варианта UTF-16 этой функции не существует.

Третий параметр - это набор флагов битового поля. Эти флаги позволяют указать, должен ли SQLite пытаться открыть базу данных для чтения/записи (SQLITE_OPEN_READWRITE) или только для чтения (SQLITE_OPEN_READONLY). Если вы запрашиваете доступ для чтения/записи, но доступен только доступ только для чтения, база данных будет открыта в режиме только для чтения.

Этот вариант открытия не создаст новый файл с неизвестным именем файла, если вы явно не разрешите это с помощью флага SQLITE_OPEN_CREATE. Это работает, только если база данных открывается в режиме чтения/записи.

Есть также ряд других флагов, касающихся управления потоками и кешем. См. Sqlite3_open() в приложении G для получения более подробной информации. Стандартная версия open эквивалентна значениям флагов (SQLITE_READWRITE | SQLITE_CREATE).

Последний параметр позволяет вам назвать модуль VFS (виртуальная файловая система (Virtual File System)), который будет использоваться с этим подключением к базе данных. Система VFS действует как уровень абстракции между библиотекой SQLite и любой базовой системой хранения (например, файловой системой). Почти во всех случаях вы захотите использовать модуль VFS по умолчанию и можете просто передать указатель NULL.

Для нового кода рекомендуется использовать вызов sqlite3_open_v2(). Новый вызов позволяет лучше контролировать то, как база данных открывается и обрабатывается.

Использование двойного указателя поначалу может немного сбивать с толку, но идея, лежащая в его основе, достаточно проста. Указатель на указатель на самом деле не более чем указатель, который передается по ссылке. Это позволяет вызову функции изменять переданный указатель. Например:

sqlite3   *db = NULL;
rc = sqlite3_open_v2( "database.sqlite3", &db, SQLITE_OPEN_READWRITE, NULL );
/* hopefully, db now points to a valid sqlite3 structure */

Обратите внимание, что db - это указатель sqlite3 (sqlite3*), а не фактическая структура sqlite3. Когда мы вызываем sqlite3_open_xxx() и передаем ссылку на указатель, функция open выделяет новую структуру данных sqlite3, инициализирует ее и устанавливает наш указатель, указывающий на нее.

Этот подход, включая использование ссылки на указатель, является общей темой в API-интерфейсах SQLite, которые используются для создания или инициализации чего-либо. Все они в основном работают одинаково, и, как только вы освоите их, они станут довольно простыми и удобными в использовании.

Стандартного расширения файла для файла базы данных SQLite3 не существует, хотя .sqlite3, .db и .db3 являются популярными вариантами. Следует избегать расширения .sdb, поскольку это расширение имеет особое значение на некоторых платформах Microsoft Windows и может значительно снизить производительность ввода-вывода.

Кодировка строки, используемая файлом базы данных, определяется функцией, которая используется для создания файла. Использование sqlite3_open() или sqlite3_open_v2() приведет к созданию базы данных с кодировкой UTF-8 по умолчанию. Если sqlite3_open16() используется для создания базы данных, кодировка строки по умолчанию будет UTF-16 в собственном порядке байтов машины. Вы можете изменить кодировку строк по умолчанию с помощью команды SQL PRAGMA encoding. См. encoding в приложении F для более подробной информации.

Особые случаи

Помимо распознавания стандартных имен файлов, SQLite распознает несколько специализированных строк имен файлов. Если данное имя файла является указателем NULL или пустой строкой (""), то создается анонимная временная база данных на диске. Доступ к анонимной базе данных возможен только через соединение с базой данных, которое ее создало. Каждый вызов создает новый уникальный экземпляр базы данных. Как и все временные элементы, эта база данных будет уничтожена при закрытии соединения.

Если используется имя файла :memory:, то создается временная база данных в оперативной памяти. Базы данных в памяти находятся в кэше базы данных и не имеют резервного хранилища. Этот стиль базы данных чрезвычайно быстр, но требует достаточного объема оперативной памяти для хранения всего образа базы данных. Как и в случае с анонимными базами данных, каждый открытый вызов создает новую уникальную базу данных в памяти, что делает невозможным доступ к данной базе данных более чем одному соединению.

Базы данных в памяти являются отличными структурированными кешами. Невозможно напрямую создать образ базы данных в памяти на диск, но вы можете скопировать содержимое базы данных в памяти на диск (или с диска в память) с помощью API резервного копирования базы данных. См. раздел sqlite3_backup_init() в приложении G для получения более подробной информации.

Закрытие

Чтобы закрыть и разорвать соединение с базой данных, вызовите sqlite3_close().

int sqlite3_close( sqlite3 *db )
Закрывает соединение с базой данных и освобождает все связанные структуры данных. Все временные элементы, связанные с этим подключением, будут удалены. Для успешного выполнения все подготовленные операторы, связанные с этим подключением к базе данных, должны быть завершены. См. «Сброс и финализация» для получения более подробной информации.

Любой указатель, возвращаемый вызовом sqlite3_open_xxx(), включая указатель NULL, можно передать sqlite3_close(). Эта функция проверяет, нет ли незавершенных изменений в базе данных, затем закрывает файл и освобождает структуру данных sqlite3. Если в базе данных все еще есть нефинализированные операторы, будет возвращена ошибка SQLITE_BUSY. В этом случае вам нужно исправить проблему и снова вызвать sqlite3_close().

В большинстве случаев sqlite3_open_xxx() возвращает указатель на структуру sqlite3, даже если код возврата указывает на проблему. Это позволяет вызывающему абоненту получить сообщение об ошибке с помощью sqlite3_errmsg(). (См. «Коды результатов и коды ошибок».) В этих ситуациях вы все равно должны вызвать sqlite3_close(), чтобы освободить структуру sqlite3.

Пример

Вот схема программы, которая открывает базу данных, выполняет некоторые операции, а затем закрывает ее. Большинство других примеров в этой главе построены на этом примере путем вставки кода в середину:

#include "sqlite3.h"
#include <stdlib.h>

int main( int argc, char **argv )
{
    char            *file = ""; /* default to temp db */
    sqlite3         *db = NULL;
    int             rc = 0;

    if ( argc > 1 )
        file = argv[1];

    sqlite3_initialize( );
    rc = sqlite3_open_v2( file, &db, SQLITE_OPEN_READWRITE |
                                     SQLITE_OPEN_CREATE, NULL );
    if ( rc != SQLITE_OK) {
        sqlite3_close( db );
        exit( -1 );
    }

    /* perform database operations */

    sqlite3_close( db );
    sqlite3_shutdown( );
}

Имя файла по умолчанию - пустая строка. Если передать в sqlite3_open_xxx(), это приведет к созданию временной базы данных на диске, которая будет удалена, как только соединение с базой данных будет закрыто. Если указан хотя бы один аргумент, первый аргумент будет использоваться как имя файла. Если база данных не существует, она будет создана, а затем открыта для чтения/записи. Затем она немедленно закрывается.

Если этот пример запускается с новым именем файла, он не создаст допустимый файл базы данных. Библиотека SQLite откладывает запись заголовка базы данных до тех пор, пока не будет выполнена какая-либо фактическая операция с данными. Эта «ленивая» инициализация дает приложению возможность настроить любые соответствующие прагмы, такие как кодировка текста, размер страницы и формат файла базы данных, прежде чем файл базы данных будет полностью создан.

Подготовленный оператор

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

Жизненный цикл оператора

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

Жизненный цикл типичного sqlite3_stmt выглядит примерно так (в псевдокоде):

/* create a statement from an SQL string */
sqlite3_stmt *stmt = NULL;
sqlite3_prepare_v2( db, sql_str, sql_str_len, &stmt, NULL );

/* use the statement as many times as required */
while( ... )
{
    /* bind any parameter values */
    sqlite3_bind_xxx( stmt, param_idx, param_value... );
    ...

    /* execute statement and step over each row of the result set */
    while ( sqlite3_step( stmt ) == SQLITE_ROW )
    {
        /* extract column values from the current result row */
        col_val = sqlite3_column_xxx( stmt, col_index );
        ...
    }

    /* reset the statement so it may be used again */
    sqlite3_reset( stmt );
    sqlite3_clear_bindings( stmt ); /* optional */
}

/* destroy and release the statement */
sqlite3_finalize( stmt );
stmt = NULL;

Процесс подготовки преобразует командную строку SQL в подготовленный оператор. Затем этот оператор может иметь значения, связанные с любыми параметрами оператора. Затем оператор выполняется или «перешагивает». В случае запроса каждый шаг будет делать новую строку результатов доступной для обработки. Затем значения столбцов текущей строки могут быть извлечены и обработаны. Оператор выполняется поэтапно, строка за строкой, пока не закончатся доступные строки.

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

Подготовка

Чтобы преобразовать командную строку SQL в подготовленный оператор, используйте одну из функций sqlite3_prepare_xxx():

int sqlite3_prepare( sqlite3 *db, const char *sql_str, int sql_str_len, sqlite3_stmt **stmt, const char **tail ) int sqlite3_prepare16( sqlite3 *db, const void *sql_str, int sql_str_len, sqlite3_stmt **stmt, const void **tail )

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

int sqlite3_prepare_v2( sqlite3 *db, const char *sql_str, int sql_str_len, sqlite3_stmt **stmt, const char **tail ) int sqlite3_prepare16_v2( sqlite3 *db, const void *sql_str, int sql_str_len, sqlite3_stmt **stmt, const void **tail )

Преобразует командную строку SQL в подготовленный оператор. Первый параметр - это соединение с базой данных. Второй параметр - это команда SQL, закодированная в строке UTF-8 или UTF-16. Третий параметр указывает длину командной строки в байтах. Четвертый параметр - это ссылка на указатель оператора. Это используется для передачи указателя на новую структуру sqlite3_stmt.

Пятый параметр - это ссылка на строку (указатель на символ). Если командная строка содержит несколько операторов SQL и этот параметр не равен NULL, указатель будет установлен на начало следующего оператора в командной строке.

Эти вызовы _v2 принимают те же параметры, что и исходные версии, но внутреннее представление создаваемой структуры sqlite3_stmt несколько отличается. Это дает возможность расширенной и автоматической обработки ошибок. Эти различия обсуждаются позже в разделе «Коды результатов и коды ошибок».

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

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

Привязка параметров - это несколько более глубокая тема, поэтому мы вернемся к ней в следующем разделе. См. «Связанные параметры» для получения более подробной информации.

Шаг

Подготовка оператора SQL приводит к синтаксическому анализу командной строки и преобразованию в набор команд с байтовым кодом. Этот байт-код загружается в Virtual Database Engine (VDBE) SQLite для выполнения. Перевод не является последовательным индивидуальным делом. В зависимости от структуры базы данных (например, индексов) оптимизатор запросов может генерировать очень разные последовательности команд VDBE для аналогичных команд SQL. Размер и гибкость библиотеки SQLite во многом можно отнести к архитектуре VDBE.

Для выполнения кода VDBE вызывается функция sqlite3_step(). Эта функция проходит через текущую последовательность команд VDBE до тех пор, пока не встретится какой-либо тип прерывания программы. Это может произойти, когда становится доступной новая строка или когда программа VDBE достигает своего конца, указывая, что больше нет доступных данных.

В случае запроса SELECT sqlite3_step() вернет один раз для каждой строки в наборе результатов. Каждый последующий вызов sqlite3_step() будет продолжать выполнение оператора до тех пор, пока не станет доступна следующая строка или пока оператор не достигнет своего конца.

Определение функции довольно простое:

int sqlite3_step( sqlite3_stmt *stmt )
Попытки выполнить предоставленный подготовленный оператор. Если строка набора результатов становится доступной, функция вернет значение SQLITE_ROW. В этом случае отдельные значения столбцов можно извлечь с помощью функций sqlite3_column_xxx(). Дополнительные строки можно вернуть, выполнив дальнейшие вызовы sqlite3_step(). Если выполнение оператора подходит к концу, будет возвращен код SQLITE_DONE. Как только это произойдет, sqlite3_step() нельзя будет снова вызвать с этим подготовленным оператором, пока оператор не будет сначала сброшен с помощью sqlite3_reset().

Если первый вызов sqlite3_step() возвращает SQLITE_DONE, это означает, что оператор был успешно выполнен, но не было данных о результатах, которые можно было бы сделать доступными. Это типичный случай для большинства команд, кроме SELECT. Если sqlite3_step() вызывается повторно, команда SELECT вернет SQLITE_ROW для каждой строки набора результатов, прежде чем окончательно вернуть SQLITE_DONE. Если команда SELECT не возвращает строк, она вернет SQLITE_DONE при первом вызове sqlite3_step().

Есть также несколько команд PRAGMA, которые возвращают значение. Даже если возвращаемое значение является простым скалярным, это значение будет возвращено как набор результатов из одной строки и одного столбца. Это означает, что первый вызов sqlite3_step() вернет SQLITE_ROW, указывая, что данные результата доступны. Кроме того, если PRAGMA count_changes имеет значение true, команды INSERT, UPDATE и DELETE будут возвращать количество строк, которые они изменили, в виде однострочного целочисленного значения с одним столбцом.

Каждый раз, когда sqlite3_step() возвращает SQLITE_ROW, данные новой строки доступны для обработки. Значения строк можно проверить и извлечь из оператора с помощью функций sqlite3_column_xxx(), которые мы рассмотрим далее. Чтобы возобновить выполнение инструкции, просто вызовите sqlite3_step() еще раз. Обычно sqlite3_step() вызывается в цикле, обрабатывая каждую строку до тех пор, пока не будет возвращен SQLITE_DONE.

Строки возвращаются, как только они вычисляются. Во многих случаях это распределяет затраты на обработку между всеми вызовами sqlite3_step() и позволяет достаточно быстро вернуть первую строку. Однако, если в запросе есть предложение GROUP BY или ORDER BY, оператор может быть вынужден сначала собрать все строки в наборе результатов, прежде чем он сможет завершить окончательную обработку. В этих случаях для того, чтобы первая строка стала доступной, может потребоваться значительное время, но последующие строки должны быть возвращены очень быстро.

Столбцы результатов

Каждый раз, когда sqlite3_step() возвращает код SQLITE_ROW, в операторе становится доступна новая строка набора результатов. Вы можете использовать функции sqlite3_column_xxx() для проверки и извлечения значений столбцов из этой строки. Многие из этих функций требуют параметра индекса столбца (cidx). Как и массивы C, первый столбец в наборе результатов всегда имеет нулевой индекс, начиная слева.

int sqlite3_column_count( sqlite3_stmt *stmt )
Возвращает количество столбцов в результате запроса. Если инструкция не возвращает значения, будет возвращено нулевое значение счетчика. Допустимые индексы столбцов - от нуля до счетчика минус один. (N столбцов имеют индексы от 0 до N-1).
const char* sqlite3_column_name( sqlite3_stmt *stmt, int cidx ) const void* sqlite3_column_name16( sqlite3_stmt *stmt, int cidx )

Возвращает имя указанного столбца в виде строки в кодировке UTF-8 или UTF-16. Возвращенная строка - это имя, указанное в предложении AS в заголовке SELECT. Например, эта функция вернет person_id для нулевого столбца оператора SQL SELECT pid AS person_id,.... Если выражение AS не было задано, имя технически не определено и может изменяться от одной версии SQLite к другой. Это особенно верно для столбцов, которые состоят из выражения.

Возвращенные указатели будут оставаться действительными до тех пор, пока одна из этих функций не будет снова вызвана для того же индекса столбца или пока оператор не будет уничтожен с помощью sqlite3_finalize(). Указатели будут оставаться действительными (и неизмененными) при вызовах sqlite3_step() и sqlite3_reset(), поскольку имена столбцов не меняются от одного выполнения к другому. Эти указатели не следует передавать в sqlite3_free().

int sqlite3_column_type( sqlite3_stmt *stmt, int cidx )

Возвращает собственный тип (класс хранения) значения, найденного в указанном столбце. Допустимые коды возврата могут быть SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB или SQLITE_NULL. Чтобы получить правильный собственный тип данных, эту функцию следует вызывать перед любой попыткой извлечения данных.

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

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

const void* sqlite3_column_blob( sqlite_stmt *stmt, int cidx )
Возвращает указатель на значение BLOB из заданного столбца. Указатель может быть недействительным, если длина BLOB равна нулю. Указатель также может иметь значение NULL, если требовалось преобразование типа.
double sqlite3_column_double( sqlite_stmt *stmt, int cidx )
Возвращает 64-битное значение с плавающей запятой из заданного столбца.
int sqlite3_column_int( sqlite_stmt *stmt, int cidx )
Возвращает 32-разрядное целое число со знаком из заданного столбца. Значение будет усечено (без предупреждения), если столбец содержит целочисленное значение, которое невозможно представить в 32-битном формате.
sqlite3_int64 sqlite3_column_int64( sqlite_stmt *stmt, int cidx )
Возвращает 64-разрядное целое число со знаком из заданного столбца.
const unsigned char* sqlite3_column_text( sqlite_stmt *stmt, int cidx ) const void* sqlite3_column_text16( sqlite_stmt *stmt, int cidx )
Возвращает указатель на строку в кодировке UTF-8 или UTF-16 из заданного столбца. Строка всегда будет завершаться нулевым символом в конце, даже если это пустая строка. Обратите внимание, что возвращаемый указатель char не имеет знака и, вероятно, потребует приведения. Указатель также может иметь значение NULL, если требовалось преобразование типа.
sqlite3_value* sqlite3_column_value( sqlite_stmt *stmt, int cidx )
Возвращает указатель на незащищенную структуру sqlite3_value. Незащищенные структуры sqlite3_value не могут безопасно подвергаться преобразованию типов, поэтому не следует пытаться извлечь примитивное значение из этой структуры с помощью функций sqlite3_value_xxx(). Если вам нужно примитивное значение, вы должны использовать одну из других функций sqlite3_column_xxx(). Единственное безопасное использование возвращенного указателя - это вызвать sqlite3_bind_value() или sqlite3_result_value(). Первый используется для привязки значения к другому подготовленному оператору, а второй используется для возврата значения в пользовательской функции SQL (см. «Привязка значений» или «Возврат результатов и ошибок»).

Нет функции sqlite3_column_null(). В ней нет необходимости. Если собственный тип данных - NULL, дополнительных значений или сведений о состоянии для извлечения не требуется.

Любые указатели, возвращаемые этими функциями, становятся недействительными, если другой вызов любой функции sqlite3_column_xxx() выполняется с использованием того же индекса столбца или при следующем вызове sqlite3_step(). Указатели также станут недействительными, если оператор будет сброшен или завершен. SQLite позаботится обо всем управлении памятью, связанной с этими указателями.

Если вы запрашиваете тип данных, который отличается от собственного значения, SQLite попытается преобразовать это значение. Таблица 7-1 описывает правила преобразования, используемые SQLite.

Таблица 7-1. Правила преобразования типов SQLite.

Исходный тип Запрошенный тип Преобразованное значение
NULL Integer 0
NULL Float 0.0
NULL Text NULL указатель
NULL BLOB NULL указатель
Integer Float Преобразовано в float
Integer Text ASCII число
Integer BLOB То же, что и текст
Float Integer Округлено до нуля
Float Text ASCII число
Float BLOB То же, что и текст
Text Integer Внутренний atoi()
Text Float Внутренний atof()
Text BLOB Без изменений
BLOB Integer Преобразовано в текст, atoi()
BLOB Float Преобразовано в текст, atof()
BLOB Text Добавлен терминатор

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

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

int sqlite3_column_bytes( sqlite3_stmt *stmt, int cidx )
Возвращает количество байтов в BLOB или текстовом значении в кодировке UTF-8. При возврате размера текстового значения размер будет включать терминатор.
int sqlite3_column_bytes16( sqlite3_stmt *stmt, int cidx )
Возвращает количество байтов в текстовом значении в кодировке UTF-16, включая терминатор.

Имейте в виду, что эти функции могут вызывать преобразование данных в текстовые значения. Это преобразование может сделать недействительным любой ранее возвращенный указатель. Например, если вы вызываете sqlite3_column_text() для получения указателя на строку в кодировке UTF-8, а затем вызываете sqlite3_column_bytes16() для того же столбца, внутреннее значение столбца будет преобразовано из строки в кодировке UTF-8 в строку в кодировке UTF-16. Это сделает недействительным указатель на символ, который изначально был возвращен sqlite3_column_text().

Точно так же, если вы сначала вызываете sqlite3_column_bytes16(), чтобы получить размер строки в кодировке UTF-16, а затем вызываете sqlite3_column_text(), внутреннее значение будет преобразовано в строку UTF-8 до того, как будет возвращен указатель на строку. Это сделает недействительным значение длины, которое было изначально возвращено.

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

/* correctly extract a blob */
buf_ptr = sqlite3_column_blob( stmt, n );
buf_len = sqlite3_column_bytes( stmt, n );

/* correctly extract a UTF-8 encode string */
buf_ptr = sqlite3_column_text( stmt, n );
buf_len = sqlite3_column_bytes( stmt, n );

/* correctly extract a UTF-16 encode string */
buf_ptr = sqlite3_column_text16( stmt, n );
buf_len = sqlite3_column_bytes16( stmt, n );

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

Вы всегда должны использовать sqlite3_column_bytes() для определения размера BLOB.

Сброс и завершение

Когда вызов sqlite3_step() возвращает SQLITE_DONE, оператор успешно завершил выполнение. На этом этапе вы больше ничего не можете сделать с оператором. Если вы хотите снова использовать оператор, его необходимо сначала сбросить.

int sqlite3_reset( sqlite3_stmt *stmt )
Сбрасывает подготовленный оператор, чтобы он был готов к следующему выполнению. Оператор должен быть сброшен, как только вы закончите его использовать. Это обеспечит снятие всех блокировок.

Функцию sqlite3_reset() можно вызвать в любое время после вызова sqlite3_step(). Допустимо вызывать sqlite3_reset() до завершения выполнения оператора (то есть до того, как sqlite3_step() вернет SQLITE_DONE или индикатор ошибки). Вы не можете отменить запущенный вызов sqlite3_step() таким образом, но можете сократить возврат дополнительных значений SQLITE_ROW.

Например, если вам нужны только первые шесть строк набора результатов, вполне допустимо вызвать sqlite3_step() только шесть раз, а затем сбросить инструкцию, даже если sqlite3_step() продолжит возвращать SQLITE_ROW.

Функция sqlite3_reset() просто сбрасывает оператор, но не освобождает его. Чтобы уничтожить подготовленный оператор и освободить его память, оператор должен быть завершен.

int sqlite3_finalize( sqlite3_stmt *stmt )
Уничтожает подготовленный оператор и освобождает все связанные ресурсы.

Функцию sqlite3_finalize() можно вызвать в любое время для любого успешно подготовленного оператора. Все подготовленные операторы, связанные с подключением к базе данных, должны быть завершены до закрытия подключения к базе данных.

Хотя обе эти функции могут возвращать ошибки, они всегда выполняют свою функцию. Любая возвращенная ошибка была вызвана последним вызовом sqlite3_step(). См. «Коды результатов и коды ошибок» для получения более подробной информации.

Рекомендуется сбросить или завершить оператор, как только вы закончите его использовать. Вызов sqlite3_reset() или sqlite3_finalize() гарантирует, что оператор снимет любые блокировки, которые он может удерживать, и освободит все ресурсы, связанные с предыдущим выполнением оператора. Если приложение хранит операторы в течение длительного периода времени, они должны оставаться в состоянии сброса, готовые к привязке и выполнению.

Переходы операторов

Подготовленные операторы имеют значительный объем состояния. Помимо привязанных в данный момент значений параметров и других деталей, каждый подготовленный оператор всегда находится в одном из трех основных состояний. Первое - это состояние «ready». Любая свежеприготовленная инструкция или инструкция сброса будут «ready». Это указывает на то, что оператор готов к выполнению, но еще не был запущен. Второе состояние - «running», что указывает на то, что оператор начал выполняться, но еще не завершился. Конечное состояние - «done», что указывает на завершение выполнения инструкции.

Важно знать текущее состояние оператора. Хотя некоторые функции API можно вызывать в любое время (например, sqlite3_reset()), другие функции API можно вызывать только тогда, когда оператор находится в определенном состоянии. Например, функции sqlite3_bind_xxx() можно вызывать только тогда, когда оператор находится в состоянии «ready». На рис. 7-1 показаны различные состояния и то, как оператор переходит из одного состояния в другое.

Рисунок 7-1. Ппереходы подготовленных операторов. Оператор может находиться в одном из трех состояний. В зависимости от текущего состояния действительны только некоторые функции API. Вызов функции в неподходящем состоянии приведет к ошибке SQLITE_MISUSE.

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

Примеры

Вот два примера использования подготовленных операторов. В первом примере выполняется оператор CREATE TABLE, сначала подготавливая строку SQL, а затем вызывая функцию sqlite3_step() для выполнения оператора:

sqlite3_stmt  *stmt = NULL;

/* ... open database ... */

rc = sqlite3_prepare_v2( db, "CREATE TABLE tbl ( str TEXT )", -1, &stmt, NULL );
if ( rc != SQLITE_OK) exit( -1 );

rc = sqlite3_step( stmt );
if ( rc != SQLITE_DONE ) exit ( -1 );

sqlite3_finalize( stmt );

/* ... close database ... */

Оператор CREATE TABLE - это DDL-команда, которая не возвращает значения какого-либо типа, и ее необходимо выполнить только один раз, чтобы полностью выполнить команду. Не забудьте сбросить или завершить операторы, как только они закончат выполнение. Также помните, что все операторы, связанные с подключением к базе данных, должны быть полностью завершены, прежде чем соединение можно будет закрыть.

Второй пример немного сложнее. Этот код выполняет SELECT и перебирает sqlite3_step(), извлекая все строки в таблице. Каждое значение отображается по мере извлечения:

const char     *data = NULL;
sqlite3_stmt   *stmt = NULL;

/* ... open database ... */

rc = sqlite3_prepare_v2( db, "SELECT str FROM tbl ORDER BY 1", -1, &stmt, NULL );
if ( rc != SQLITE_OK) exit( -1 );

while( sqlite3_step( stmt ) == SQLITE_ROW ) {
    data = (const char*)sqlite3_column_text( stmt, 0 );
    printf( "%s\n", data ? data : "[NULL]" );
}

sqlite3_finalize( stmt );

/* ... close database ... */

В этом примере не проверяется тип значения столбца. Поскольку значение будет отображаться в виде строки, код зависит от внутреннего процесса преобразования SQLite и всегда запрашивает текстовое значение. Единственная сложность заключается в том, что указатель на строку может иметь значение NULL, поэтому нам нужно быть готовыми справиться с этим в операторе printf().

Связанные параметры

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

Токены параметров

SQLite поддерживает пять различных стилей параметров оператора. Эти короткие строковые токены помещаются непосредственно в командную строку SQL, которую затем можно передать одной из функций sqlite3_prepare_xxx(). После подготовки оператора ссылки на отдельные параметры указываются по индексу.

?
Анонимный параметр с автоматическим индексом. По мере обработки оператора каждому анонимному параметру присваивается уникальное последовательное значение индекса, начиная с единицы.
?<index>
Параметр с явным числовым индексом. Повторяющиеся индексы позволяют связывать одно и то же значение в нескольких местах в одном операторе.
:<name>
Именованный параметр с автоматическим индексом. Повторяющиеся имена позволяют связывать одно и то же значение в нескольких местах в одном операторе.
@<name>
Именованный параметр с автоматическим индексом. Повторяющиеся имена позволяют связывать одно и то же значение в нескольких местах в одном операторе. Работает точно так же, как параметр двоеточия.
$<name>
Именованный параметр с автоматическим индексом. Повторяющиеся имена позволяют связывать одно и то же значение в нескольких местах в одном операторе. Это расширенный синтаксис для поддержки переменных Tcl. Если вы не занимаетесь программированием на Tcl, я предлагаю вам использовать формат двоеточия.

Чтобы понять, как они работают, рассмотрим этот оператор INSERT:

INSERT INTO people (id, name) VALUES ( ?, ? );

Два параметра инструкции представляют вставляемые значения id и name. Индексы параметров начинаются с единицы, поэтому первый параметр, представляющий значение id, имеет индекс, равный единице, а параметр, используемый для ссылки на значение name, имеет индекс, равный двум.

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

Параметры оператора не следует заключать в кавычки. Обозначение '?' обозначает односимвольное текстовое значение, а не параметр.

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

Вы также можете использовать явные значения индекса:

INSERT INTO people (id, name) VALUES ( ?1, ?2 );

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

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

Этот уровень абстракции можно продвинуть еще дальше, используя именованные параметры. В этом случае вы позволяете SQLite присваивать значения индексов параметров по своему усмотрению аналогично анонимным параметрам. Разница в том, что вы можете попросить SQLite сообщить вам значение индекса для определенного параметра на основе имени, которое вы ему дали. Рассмотрим это утверждение:

INSERT INTO people (id, name) VALUES ( :id, :name );

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

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

SELECT * FROM ?;   -- INCORRECT: Cannot use a parameter as an identifier

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

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

Лично я предпочитаю использовать параметры стиля двоеточие-имя. Использование именованных параметров устраняет необходимость знать какие-либо конкретные значения индекса, позволяя вам просто ссылаться на имя во время выполнения. Использование коротких значимых имен также может облегчить понимание как ваших операторов SQL, так и кода связывания.

Значения привязки

Когда вы впервые подготавливаете оператор с параметрами, все параметры начинаются с присвоенного им значения NULL. Перед выполнением оператора вы можете привязать определенные значения к каждому параметру, используя семейство функций sqlite3_bind_xxx().

Доступно девять функций sqlite3_bind_xxx(), а также ряд служебных функций. Эти функции можно вызывать в любое время после подготовки оператора, но до первого вызова sqlite3_step(). После вызова sqlite3_step() эти функции нельзя будет вызвать снова, пока оператор не будет сброшен.

Все функции sqlite3_bind_xxx() имеют одинаковые первый и второй параметры и возвращают одинаковый результат. Первый параметр всегда является указателем на sqlite3_stmt, а второй - индекс параметра для привязки. Помните, что для анонимных параметров первое значение индекса начинается с единицы. По большей части третий параметр - это значение для привязки. Четвертый параметр, если он присутствует, указывает длину значения данных в байтах. Пятый параметр, если он присутствует, является указателем функции на обратный вызов управления памятью.

Помните, что значения индекса привязки начинаются с единицы (1), в отличие от индексов столбца результатов, которые начинаются с нуля (0).

Все функции связывания возвращают целочисленный код ошибки, который в случае успеха равен SQLITE_OK.

Функции привязки:

int sqlite3_bind_blob( sqlite3_stmt *stmt, int pidx, const void *data, int data_len, mem_callback )
Связывает BLOB двоичных данных произвольной длины.
int sqlite3_bind_double( sqlite3_stmt *stmt, int pidx, double data )
Связывает 64-битное значение с плавающей запятой.
int sqlite3_bind_int( sqlite3_stmt *stmt, int pidx, int data )
Связывает 32-разрядное целое число со знаком.
int sqlite3_bind_int64( sqlite3_stmt *stmt, int pidx, sqlite3_int64 )
Связывает 64-битное целое число со знаком.
int sqlite3_bind_null( sqlite3_stmt *stmt, int pidx )
Связывает тип данных NULL.
int sqlite3_bind_text( sqlite3_stmt *stmt, int pidx, const char *data, int data_len, mem_callback )
Связывает текстовое значение в кодировке UTF-8 произвольной длины. Длина указывается в байтах, а не в символах. Если параметр длины отрицательный, SQLite вычислит длину строки до нулевого терминатора, но не включая его. Рекомендуется, чтобы длина, вычисляемая вручную, не включала терминатор (терминатор будет включен при возврате значения).
int sqlite3_bind_text16( sqlite3_stmt *stmt, int pidx, const void *data, int data_len, mem_callback )
Связывает текстовое значение в кодировке UTF-16 произвольной длины. Длина указывается в байтах, а не в символах. Если параметр длины отрицательный, SQLite вычислит длину строки до нулевого терминатора, но не включая его. Рекомендуется, чтобы длина, вычисляемая вручную, не включала терминатор (терминатор будет включен при возврате значения).
int sqlite3_bind_zeroblob( sqlite3_stmt *stmt, int pidx, int len )
Связывает BLOB двоичных данных произвольной длины, где каждый байт равен нулю (0x00). Единственный дополнительный параметр - это значение длины в байтах. Эта функция особенно полезна для создания больших BLOB-объектов, которые затем можно обновить с помощью интерфейса инкрементных BLOB-объектов. См. sqlite3_blob_open() в приложении G для получения более подробной информации.

В дополнение к этим специфичным для типа функциям связывания существует также специализированная функция:

int sqlite3_bind_value( sqlite3_stmt *stmt, int pidx, const sqlite3_value *data_value )
Связывает тип и значение структуры sqlite3_value. Структура sqlite3_value может содержать данные любого формата.

Текстовые и BLOB-варианты sqlite3_bind_xxx() требуют, чтобы вы передали указатель буфера для значения данных. Обычно этот буфер и его содержимое должны оставаться действительными до тех пор, пока новое значение не будет привязано к этому параметру или пока оператор не будет завершен. Поскольку это может произойти в коде позже, у этих функций связывания есть пятый параметр, который управляет обработкой и, возможно, освобождением буферной памяти.

Если пятым параметром является либо NULL, либо константа SQLITE_STATIC, SQLite возьмет на себя автоматизированный подход и предположит, что буферная память либо статическая, либо код вашего приложения заботится об обслуживании и освобождении любой памяти.

Если пятым параметром является константа SQLITE_TRANSIENT, SQLite сделает внутреннюю копию буфера. Это позволяет вам немедленно освободить буфер (или позволить ему выйти из области видимости, если он окажется в стеке). SQLite автоматически освободит внутренний буфер в подходящее время.

Последний вариант - передать действительный указатель на функцию void mem_callback(void * ptr). Этот обратный вызов будет вызываться, когда SQLite завершит работу с буфером и хочет его освободить. Если буфер был выделен с помощью sqlite3_malloc() или sqlite3_realloc(), вы можете напрямую передать ссылку на sqlite3_free(). Если вы выделили буфер с другим набором вызовов управления памятью, вам необходимо передать ссылку на функцию-оболочку, которая вызывает соответствующую функцию освобождения памяти.

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

Чтобы помочь вам определить, какой индекс параметра использовать, есть три служебные функции:

int sqlite3_bind_parameter_count( sqlite3_stmt *stmt )
Возвращает целое число, указывающее наибольший индекс параметра. Если не используются явные числовые индексы ( ?<number> ), это будет количество уникальных параметров, которые появляются в операторе. Если используются явные числовые индексы, в числовой последовательности могут быть пробелы.
int sqlite3_bind_parameter_index( sqlite3_stmt *stmt, const char *name )
Возвращает индекс именованного параметра. Имя должно включать любой начальный символ (например, «:») и должно быть указано в UTF-8, даже если оператор был подготовлен из UTF-16. Если не удается найти параметр с совпадающим именем, возвращается ноль.
const char* sqlite3_bind_parameter_name( sqlite3_stmt *stmt, int pidx )
Возвращает полное текстовое представление определенного параметра. Текст всегда имеет кодировку UTF-8 и включает ведущий символ.

Используя функцию sqlite3_bind_parameter_index(), вы можете легко найти и привязать именованные параметры. Функции sqlite3_bind_xxx() правильно обнаружат недопустимый диапазон индекса, что позволит вам найти индекс и связать значение в одной строке:

sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":pid"), pid);

Если вы хотите очистить все привязки до их начальных значений по умолчанию NULL, вы можете использовать функцию sqlite3_clear_bindings():

int sqlite3_clear_bindings( sqlite3_stmt *stmt )
Удаляет все привязки параметров в инструкции. После вызова ко всем параметрам будет привязан NULL. Это приведет к тому, что обратный вызов управления памятью будет вызываться для любого текста или значений BLOB, которые были связаны с допустимым указателем функции. В настоящее время эта функция всегда возвращает SQLITE_OK.

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

Безопасность и производительность

Использование связанных параметров дает значительные преимущества с точки зрения безопасности. Часто люди будут манипулировать строками SQL, чтобы заменить значения, которые они хотят использовать. Например, рассмотрите возможность создания оператора SQL на C с помощью строковой функции snprintf():

snprintf(buf, buf_size,
         "INSERT INTO people( id, name ) VALUES ( %d, '%s' );",
         id_val, name_val);

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

id_val = 23;
name_val = "Fred";

То получим следующий оператор SQL в нашем буфере:

INSERT INTO people( id, name ) VALUES ( 23, 'Fred');

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

id_val = 23;
name_val = "Fred' ); DROP TABLE people;";

Это приведет к тому, что наш snprintf() создаст следующую последовательность команд SQL, при этом отдельные команды будут разделены на отдельные строки для ясности:

INSERT INTO people( id, name ) VALUES ( 23, 'Fred' );
DROP TABLE people;
' );

Хотя это последнее утверждение не имеет смысла, второе утверждение вызывает беспокойство.

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

Однако имейте в виду, что интерфейсы, предоставляемые многими языками сценариев, будут делать именно это и автоматически обрабатывать несколько переданных операторов SQL за один вызов. Удобные функции SQLite, включая sqlite3_exec(), также будут автоматически обрабатывать несколько команд SQL, переданных через одну строку. Что делает sqlite3_exec() особенно опасным, так это то, что вспомогательные функции не позволяют использовать связанные значения, вынуждая вас программно создавать операторы команд SQL и открывая вам проблемы. Позже в этой главе мы более подробно рассмотрим sqlite3_exec() и то, почему обычно это не лучший выбор.

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

Fred', 'extra junk

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

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

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

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

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

Помимо предотвращения атак с использованием инъекций, параметры также могут быть быстрее и использовать меньше памяти, чем операции со строками. Для использования такой функции, как snprintf(), требуется шаблон команды SQL и достаточно большой выходной буфер. Функции обработки строк также нуждаются в рабочей памяти, плюс вам могут потребоваться дополнительные буферы для копирования и очистки значений. Кроме того, ряд типов данных, таких как целые числа и числа с плавающей запятой (и особенно BLOBы), часто занимают значительно больше места в строковом представлении. Это еще больше увеличивает использование памяти. Наконец, после создания последнего буфера команд все данные необходимо передать через синтаксический анализатор SQL, где значения буквальных данных преобразуются обратно в их собственный формат и сохраняются в дополнительных буферах.

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

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

Пример

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

Оператор сначала готовится с параметром инструкции. Затем значение данных связывается с параметром оператора, прежде чем мы выполним подготовленный оператор:

char            *data = ""; /* default to empty string */
sqlite3_stmt    *stmt = NULL;
int             idx = -1;

/* ... set "data" pointer ... */
/* ... open database ... */

rc = sqlite3_prepare_v2( db, "INSERT INTO tbl VALUES ( :str )", -1, &stmt, NULL );
if ( rc != SQLITE_OK) exit( -1 );

idx = sqlite3_bind_parameter_index( stmt, ":str" );
sqlite3_bind_text( stmt, idx, data, -1, SQLITE_STATIC );

rc = sqlite3_step( stmt );
if (( rc != SQLITE_DONE )&&( rc != SQLITE_ROW )) exit ( -1 );

sqlite3_finalize( stmt );

/* ... close database ... */

В этом случае мы ищем либо возвращаемое значение SQLITE_DONE, либо SQLITE_ROW. Возможны оба варианта. Хотя сам INSERT будет полностью выполнен при первом вызове sqlite3_step(), если PRAGMA count_changes включен, тогда оператор может вернуть значение. В этом случае мы хотим игнорировать любое возможное возвращаемое значение, не вызывая ошибки, поэтому мы должны проверить оба возможных кода возврата. Для получения дополнительной информации см. count_changes в приложении F.

Возможные ловушки

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

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

В случае оператора INSERT невозможно принудительно использовать значение по умолчанию. Например, если у вас есть следующее утверждение:

INSERT INTO membership ( pid, gid, type ) VALUES ( :pid, :gid, :type );

Даже если для столбца типа доступно значение по умолчанию, этот оператор не может его использовать. Если вы не можете привязать значение к параметру :type, будет вставлено NULL, а не значение по умолчанию. Единственный способ вставить значение по умолчанию в столбец типа - использовать оператор, который на него не ссылается, например:

INSERT INTO membership ( pid, gid ) VALUES ( :pid, :gid );

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

Другая область, где параметры могут вызывать сюрпризы, - это сравнения NULL. Например, рассмотрим утверждение:

SELECT * FROM employee WHERE manager = :manager;

Это работает для обычных значений, но если NULL привязан к параметру :manager, никакие строки не будут возвращены. Если вам нужна возможность проверить наличие NULL в столбце диспетчера, убедитесь, что вы используете оператор IS:

SELECT * FROM employee WHERE manager IS :manager;

Для получения дополнительной информации см. IS в приложении D.

Такое поведение также усложняет «складывание» условных выражений. Например, если у вас есть инструкция:

SELECT * FROM employee WHERE manager = :manager AND project = :project;

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

...WHERE ( manager = :manager OR :manager IS NULL )
     AND ( project = :project OR :project IS NULL );

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

Функции удобства

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

Хотя они не полностью устарели, есть ряд причин, по которым их использование не совсем рекомендуется. Во-первых, поймите, что под капотом ничего особенного нет. Обе эти функции в конечном итоге вызывают одни и те же вызовы sqlite3_prepare_xxx(), sqlite3_step() и sqlite3_finalize(), которые и так доступны в API. Эти функции не работают ни быстрее, ни эффективнее.

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

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

Несмотря на все их недостатки, остается тот простой факт, что эти функции очень удобны. Если вы просто пытаетесь собрать быстрый и грязный фрагмент кода, эти функции предоставляют простые средства для этого. Они также вполне приемлемы для команд DDL, таких как CREATE TABLE. Для любого типа команды DML, особенно для тех, которые включают значения из неподтвержденных источников, я настоятельно рекомендую использовать обычные процедуры подготовки, шага и завершения. В итоге вы получите более безопасный код и лучшую производительность.

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

int sqlite3_exec( sqlite3 *db, const char *sql, callback_ptr, void *userData, char **errMsg )

Подготавливает и выполняет один или несколько операторов SQL, вызывая дополнительный обратный вызов для каждой строки набора результатов для каждого оператора. Первый параметр - это действительное соединение с базой данных. Второй параметр - это строка в кодировке UTF-8, состоящая из одного или нескольких операторов SQL. Третий параметр - указатель на функцию обратного вызова. Ниже представлен прототип этой функции. Указатель на функцию может иметь значение NULL. Четвертый параметр - это указатель пользовательских данных, который будет передан функции обратного вызова. Значение может быть любым, включая NULL. Пятый параметр - это ссылка на указатель символа. Если сгенерирована ошибка и этот параметр не равен NULL, sqlite3_exec() выделит строковый буфер и вернет его. Если переданный указатель не равен NULL, вы несете ответственность за освобождение буфера с помощью sqlite3_free() после того, как закончите с ним.

Если строка SQL состоит из нескольких операторов SQL, разделенных точкой с запятой, каждый оператор будет выполняться по очереди.

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

Функция sqlite3_exec() достаточно всеобъемлющая и может использоваться для выполнения любого оператора SQL. Если вы выполняете табличный запрос и хотите получить доступ к набору результатов, вам потребуется обеспечить указатель на функцию, который ссылается на определяемый пользователем обратный вызов. Этот обратный вызов будет вызываться один раз для каждой возвращаемой строки. Если вы выполняете инструкцию SQL, которая обычно не возвращает никакого значения базы данных, нет необходимости предоставлять функцию обратного вызова. Успех или неудача команды SQL будет указан в возвращаемом значении.

Функция sqlite3_exec() делает любые результаты базы данных доступными через определяемую пользователем функцию обратного вызова. Когда вычисляется каждая строка результата, вызывается обратный вызов, чтобы сделать данные строки доступными для вашего кода. По сути, каждый внутренний вызов sqlite3_step(), который приводит к возвращаемому значению SQLITE_ROW, приводит к обратному вызову.

Формат обратного вызова выглядит так:

int user_defined_exec_callback( void *userData, int numCol, char **colData, char **colName )

Эта функция не является частью SQLite API. Скорее, это показывает требуемый формат для определяемого пользователем обратного вызова sqlite3_exec(). Первый параметр - это указатель пользовательских данных, переданный в качестве четвертого параметра sqlite3_exec(). Второй параметр указывает, сколько столбцов существует в этой строке. И третий, и четвертый параметры возвращают массив строк (указатели на символы). Третий параметр содержит значения данных для этой строки, а четвертый параметр содержит имена столбцов. Все значения возвращаются в виде строк. Нет информации о типе.

Обычно обратный вызов должен возвращать нулевое значение. Если возвращается ненулевое значение, выполнение останавливается, и sqlite3_exec() возвращает SQLITE_ABORT.

Второй, третий и четвертый параметры действуют очень похоже на традиционные переменные C argc и argv (и дополнительный argv) в main (int argc, char ** argv), традиционном запуске каждой программы на C. Массивы значений и имен столбцов всегда будут одного и того же размера для любого заданного обратного вызова, но конкретный размер массивов и имена столбцов могут измениться в процессе обработки строки SQL с несколькими операторами. Нет необходимости выпускать какие-либо из этих значений. Как только ваша функция обратного вызова вернется, sqlite3_exec() будет обрабатывать все управление памятью.

Если вы предпочитаете не связываться с обратным вызовом, вы можете использовать sqlite3_get_table() для одновременного извлечения всей таблицы. Однако имейте в виду, что это может потреблять большой объем памяти и должно использоваться осторожно.

Хотя технически sqlite3_get_table() можно вызвать с любой командной строкой SQL, она специально разработана для работы с операторами SELECT.

int sqlite3_get_table( sqlite3 *db, const char *sql, char ***result, int *numRow, int *numCol, char **errMsg );

Подготавливает и выполняет командную строку SQL, состоящую из одного или нескольких операторов SQL. Полное содержимое набора результатов возвращается в виде массива строк UTF-8.

Первый параметр - это соединение с базой данных. Второй параметр - это командная строка SQL в кодировке UTF-8, которая состоит из одного или нескольких операторов SQL. Третий параметр - это ссылка на одномерный массив строк (указатели на символы). Результаты запроса передаются по этой ссылке. Четвертый и пятый параметры - это целочисленные ссылки, которые передают количество строк и количество столбцов, соответственно, в массиве результатов. Шестой и последний параметр является ссылкой на символьную строку и используется для возврата любого сообщения об ошибке.

Массив результатов состоит из (numCol * (numRow + 1)) записей. Записи от нуля до numCol - 1 содержат имена столбцов. Каждый дополнительный набор записей numCol содержит одну строку данных.

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

void sqlite3_free_table( char **result )
Правильно освобождает память, выделенную успешным вызовом sqlite3_get_table(). Не пытайтесь освободить эту память самостоятельно.

Как указано, вы должны освободить результат вызова sqlite3_get_table() с вызовом sqlite3_free_table(). Это правильно высвободит отдельные распределения, использованные для построения значения результата. Как и в случае с sqlite3_exec(), вы должны вызывать sqlite3_free() для любого возвращаемого значения errMsg.

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

/* offset to access column C of row R of **result */
int offset = ((R + 1) * numCol) + C;
char *value = result[offset];

«+ 1», используемый для вычисления смещения строки, необходим для пропуска имен столбцов, которые хранятся в первой строке результата. Это предполагает, что первая строка и столбец будут доступны с нулевым индексом.

В качестве вспомогательной функции в sqlite3_get_table() нет ничего особенного. Фактически, это просто оболочка для sqlite3_exec(). Он не предлагает дополнительных преимуществ в производительности по сравнению с интерфейсами подготовки, шага и завершения. Фактически, между всеми преобразованиями типов, присущими sqlite3_exec(), и всем выделением памяти sqlite3_get_table() имеет значительные накладные расходы по сравнению с другими методами.

Поскольку sqlite3_get_table() является оболочкой для sqlite3_exec(), можно передать командную строку SQL, состоящую из нескольких операторов SQL. Однако в случае sqlite3_get_table() это нужно делать с осторожностью.

Если передано более одного оператора SELECT, невозможно определить, где заканчивается один набор результатов и начинается следующий. Все результирующие строки объединяются в один большой массив результатов. Все операторы должны возвращать одинаковое количество столбцов, иначе вся команда sqlite3_get_table() завершится ошибкой. Кроме того, только первый оператор вернет имена столбцов. Чтобы избежать этих проблем, лучше всего вызывать sqlite3_get_table() с отдельными командами SQL.

Есть ряд причин, по которым эти удобные функции могут быть не лучшим выбором. Их использование требует создания оператора SQL-команды с использованием функций обработки строк, и этот процесс имеет тенденцию к ошибкам. Однако, если вы настаиваете, лучше всего использовать одну из встроенных функций построения строк SQLite: sqlite3_mprintf(), sqlite3_vmprintf() или sqlite3_snprintf(). См. приложение G для более подробной информации.

Коды результатов и коды ошибок

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

Стандартные коды

Прежде чем мы перейдем к тому, когда что-то пойдет не так, давайте быстро посмотрим, когда все идет хорошо. Как правило, любой вызов API, который просто должен указать, «что сработало», вернет константу SQLITE_OK. Однако не все коды возврата, отличные от SQLITE_OK, являются ошибками. Напомним, что sqlite3_step() возвращает SQLITE_ROW или SQLITE_DONE, чтобы указать конкретное состояние возврата.

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

Таблица 7-2. Стандартные коды возврата SQLite

Константа кода возврата Значение кода возврата
SQLITE_OK Операция прошла успешно
SQLITE_ERROR Общая ошибка
SQLITE_INTERNAL Внутренняя ошибка библиотеки SQLite
SQLITE_PERM В разрешении на доступ отказано
SQLITE_ABORT Пользовательский код или SQL запросили прерывание
SQLITE_BUSY Файл базы данных заблокирован (обычно его можно восстановить)
SQLITE_LOCKED Таблица заблокирована
SQLITE_NOMEM Не удалось выделить память
SQLITE_READONLY Попытка записи в базу данных, доступную только для чтения
SQLITE_INTERRUPT sqlite3_interrupt() была вызвана
SQLITE_IOERR Какой-то тип ошибки ввода-вывода
SQLITE_CORRUPT Файл базы данных неверно сформирован
SQLITE_FULL База данных заполнена
SQLITE_CANTOPEN Не удалось открыть запрошенный файл базы данных
SQLITE_EMPTY Файл базы данных пуст
SQLITE_SCHEMA Схема базы данных изменилась
SQLITE_TOOBIG ТЕКСТ или BLOB превышают лимит
SQLITE_CONSTRAINT Прерывание из-за нарушения ограничений
SQLITE_MISMATCH Несоответствие типов данных
SQLITE_MISUSE API используется неправильно
SQLITE_NOLFS Хостовая ОС не может обеспечить требуемую функциональность
SQLITE_AUTH В авторизации отказано
SQLITE_FORMAT Ошибка формата вспомогательной базы данных
SQLITE_RANGE Неверный параметр привязки index
SQLITE_NOTADB Файл не является базой данных

Многие из этих ошибок довольно специфичны. Например, SQLITE_RANGE будет возвращен только одной из функций sqlite3_bind_xxx(). Другие коды, такие как SQLITE_ERROR, почти не предоставляют информации о том, что пошло не так.

Особый интерес для разработчика представляет SQLITE_MISUSE. Это указывает на попытку использовать структуру данных или вызов API неверным или иным образом недопустимым способом. Например, попытка привязать новое значение к подготовленному оператору, который находится в середине последовательности sqlite3_step(), приведет к ошибке неправильного использования. Иногда вы получаете SQLITE_MISUSE, который возникает из-за того, что не удалось должным образом обработать предыдущую ошибку, но во многих случаях это хороший показатель того, что существует еще несколько базовых концептуальных недоразумений относительно того, как библиотека предназначена для работы.

Расширенные коды

Расширенные коды были добавлены позже в цикле разработки SQLite. Они предоставляют более подробную информацию о причине ошибки. Однако, поскольку они могут изменить значение, возвращаемое определенным условием ошибки, по умолчанию они отключены. Вам необходимо явно включить их для старых вызовов API, указав библиотеке SQLite, что вы знаете о расширенных кодах ошибок и готовы их принять.

Все стандартные коды ошибок помещаются в младший байт целочисленного значения, возвращаемого большинством вызовов API. Все расширенные коды основаны на одном из стандартных кодов ошибок, но предоставляют дополнительную информацию в байтах более высокого порядка. Таким образом, расширенные коды могут предоставить более конкретную информацию о причине ошибки. В настоящее время большинство расширенных кодов ошибок предоставляют конкретные сведения о результате SQLITE_IOERR. Вы можете найти полный список расширенных кодов ошибок на http://sqlite.org/c3ref/c_ioerr_access.html.

Функции обработки ошибок

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

int sqlite3_extended_result_codes( sqlite3 *db, int onoff )
Включает или отключает расширенные коды результатов и ошибок для этого соединения с базой данных. Для подключений к базе данных, возвращаемых любой версией sqlite3_open_xxx(), по умолчанию расширенные коды отключены. Вы можете включить их, передав ненулевое значение во втором параметре. Эта функция всегда возвращает SQLITE_OK - нет способа извлечь текущее состояние кода результата.
int sqlite3_errcode( sqlite3 *db )
Если операция с базой данных возвращает состояние, отличное от SQLITE_OK, последующий вызов этой функции вернет код ошибки. По умолчанию возвращается только стандартный код ошибки, но если включены расширенные коды результатов, он также может возвращать один из расширенных кодов.
int sqlite3_extended_errcode( sqlite3 *db )
По сути то же самое, что и sqlite3_errcode(), за исключением того, что всегда возвращаются расширенные результаты.
const char* sqlite3_errmsg( sqlite3 *db ) const void* sqlite3_errmsg16( sqlite3 *db )
Возвращает понятную человеку строку ошибки на английском языке с завершающим нулем, закодированную в UTF-8 или UTF-16. Любые дополнительные вызовы API SQLite, использующие это соединение с базой данных, могут привести к тому, что эти указатели станут недействительными, поэтому вы должны либо использовать строку перед попыткой любых других операций, либо вам следует сделать частную копию. Эти функции также могут возвращать нулевой указатель, поэтому проверьте значение результата перед его использованием. Расширенные коды ошибок не используются.

Допустимо не включать расширенные коды ошибок и смешивать вызовы sqlite3_errcode() и sqlite3_extended_errcode().

Поскольку состояние ошибки хранится в соединении с базой данных, в многопоточном приложении легко получить условные обозначения гонки. Если вы разделяете соединение с базой данных между потоками, лучше всего обернуть ваш основной вызов API и код проверки ошибок в критическом разделе. Вы можете получить блокировку мьютекса соединения с базой данных с помощью sqlite3_db_mutex(). См. sqlite3_db_mutex() в приложении G для получения более подробной информации.

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

Подготовленные операторы v2

Помимо стандартного и расширенного кодов, новые версии _v2 sqlite3_prepare_xxx() изменяют способ обработки ошибок подготовленных операторов. Хотя более новая и исходная версии sqlite3_prepare_xxx() используют одни и те же параметры, sqlite3_stmt, возвращаемый версиями _v2, немного отличается.

Наиболее заметная разница в том, как обрабатываются ошибки sqlite3_step(). Для операторов, подготовленных с помощью исходной версии sqlite3_prepare_xxx(), большинство ошибок в sqlite3_step() вернет довольно общий SQLITE_ERROR. Чтобы выяснить специфику ситуации, вам нужно вызвать sqlite3_reset() или sqlite3_finalize() для извлечения более подробного кода ошибки. Это, конечно, приведет к сбросу или завершению инструкции, что ограничит ваши возможности восстановления.

Все работает немного иначе, если оператор был подготовлен с версией _v2. В этом случае sqlite3_step() вернет конкретную ошибку напрямую. Вызов sqlite3_step() может возвращать стандартный код или расширенный код, в зависимости от того, включены ли расширенные коды или нет. Это позволяет разработчику напрямую извлечь ошибку и предоставляет больше возможностей для восстановления.

Другое важное отличие заключается в том, как обрабатываются изменения схемы базы данных. Если выдается какая-либо команда языка определения данных (например, DROP TABLE), есть вероятность, что подготовленный оператор больше не действителен. Например, подготовленный оператор может ссылаться на таблицу или индекс, которых больше нет. Единственный способ решить любые возможные проблемы - это перестроить операцию.

Версии _v2 sqlite3_prepare_xxx() делают копию оператора SQL, используемого для подготовки оператора. (Этот SQL можно извлечь. Подробнее см. sqlite3_sql() в приложении G.) Сохраняя внутреннюю копию SQL, оператор может представлять себя при изменении схемы базы данных. Это делается автоматически каждый раз, когда SQLite обнаруживает необходимость перестроить оператор.

Операторы, созданные с помощью исходной версии prepare, не сохранили копию команды SQL, поэтому они не смогли восстановить себя. В результате при каждом изменении схемы вызов API, включающий любой ранее подготовленный оператор, будет возвращать SQLITE_SCHEMA. Затем программа должна будет воспроизвести оператор, используя исходный SQL, и повторить попытку. Если изменение схемы было достаточно значительным, чтобы SQL больше не действовал, sqlite3_prepare_xxx() вернет соответствующую ошибку, когда программа попытается повторно подготовить команду SQL.

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

Вот параллельное сравнение основных различий между исходной версией prepare и версией _v2:

Оператор, подготовленный с использованием исходной версии Оператор, подготовленный с помощью версии v2
Создано с помощью sqlite3_prepare() или sqlite3_prepare16(). Создано с помощью sqlite3_prepare_v2() или sqlite3_prepare16_v2().
Большинство ошибок в sqlite3_step() возвращают SQLITE_ERROR. sqlite3_step() напрямую возвращает конкретные ошибки.
Чтобы получить полную ошибку? необходимо вызвать sqlite3_reset() или sqlite3_finalize(). Могут быть возвращены стандартные или расширенные коды ошибок. Больше ничего называть не нужно. sqlite3_step() может возвращать стандартный или расширенный код ошибки.
Изменения схемы заставят любую операторную функцию вернуть SQLITE_SCHEMA. Приложение должно вручную завершить и заново составить отчет. Изменения схемы заставят оператор заново подготовиться.
Если предоставленный приложением SQL более недействителен, подготовка завершится ошибкой. Если внутренний SQL больше не действителен, любая операторная функция вернет SQLITE_SCHEMA. Это фатальная ошибка оператора, и единственный выход - завершить оператор.
Исходный SQL не связан с оператором. Оператор хранит копию SQL, используемого для подготовки. SQL можно восстановить с помощью sqlite3_sql().
Ограниченная отладка. Можно использовать sqlite3_trace().

Поскольку обработка ошибок _v2 намного проще и из-за возможности автоматического восстановления после изменений схемы, настоятельно рекомендуется, чтобы все новые разработки использовали sqlite3_prepare_xxx() версии _v2.

Транзакции и ошибки

Транзакции и контрольные точки добавляют уникальный поворот к процессу исправления ошибок. Обычно SQLite работает в режиме автоматической фиксации. В этом режиме SQLite автоматически помещает каждую команду SQL в отдельную транзакцию. С точки зрения API, это время с момента первого вызова sqlite3_step() до возврата SQLITE_DONE функцией sqlite3_step() (или когда вызывается sqlite3_reset() или sqlite3_finalize()).

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

После выполнения команды BEGIN TRANSACTION SQLite больше не находится в режиме автоматической фиксации. Транзакция открывается и остается открытой до тех пор, пока не будет дана команда END TRANSACTION или COMMIT TRANSACTION. Это позволяет объединить несколько команд в одну транзакцию. Хотя это полезно для группировки серии дискретных команд в атомарное изменение, это также ограничивает возможности SQLite для восстановления после ошибок.

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

Ошибки, которые, скорее всего, приведут к откату: SQLITE_FULL (база данных или диск заполнена), SQLITE_IOERR (ошибка ввода-вывода на диске или заблокированный файл), SQLITE_BUSY (база данных заблокирована), SQLITE_NOMEM (нехватка памяти) и SQLITE_INTERRUPT (прерывание, запрошенное приложением). Если вы обрабатываете явную транзакцию и получаете одну из этих ошибок, вам необходимо иметь дело с возможностью отката транзакции.

Чтобы выяснить, какое действие было выполнено SQLite, вы можете использовать функцию sqlite3_get_autocom mit().

int sqlite3_get_autocommit( sqlite3 *db )
Возвращает текущее состояние фиксации. Ненулевое возвращаемое значение указывает, что база данных находится в режиме автоматической фиксации, а не в явной транзакции. Нулевое значение указывает, что база данных в настоящее время находится внутри явной транзакции.

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

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

Блокировка базы данных

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

Система блокировки включает несколько разных уровней блокировок, которые используются для уменьшения конфликтов и предотвращения взаимоблокировок. Детали несколько сложны, но система позволяет нескольким соединениям читать файл базы данных параллельно, но любая операция записи требует полного, монопольного доступа ко всему файлу базы данных. Если вам нужна полная информация, см. http://www.sqlite.org/lockingv3.html.

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

Однако, если более одного соединения пытаются получить доступ к одной и той же базе данных одновременно, рано или поздно они столкнутся друг с другом. Обычно, если операция требует блокировки, которую соединение с базой данных не может получить, SQLite возвращает ошибку SQLITE_BUSY или, в некоторых более крайних случаях, SQLITE_IOERR (расширенный код SQLITE_IOERR_BLOCKED). Все функции sqlite3_prepare_xxx(), sqlite3_step(), sqlite3_reset() и sqlite3_finalize() могут возвращать SQLITE_BUSY. Функции sqlite3_backup_step() и sqlite3_blob_open() также могут возвращать SQLITE_BUSY, поскольку эти функции внутренне используют sqlite3_prepare_xxx() и sqlite3_step(). Наконец, sqlite3_close() может возвращать SQLITE_BUSY, если есть незавершенные операторы, связанные с подключением к базе данных, но это не связано с блокировкой.

Чтобы получить доступ к необходимому замку, нужно просто дождаться, пока текущий держатель закончит работу и снимет блокировку. В большинстве случаев это не очень длительный период времени. Ожидание может выполняться либо приложением, которое может ответить на SQLITE_BUSY, просто пытаясь повторно обработать оператор и повторить попытку, либо это может быть выполнено с помощью обработчика занятости.

Обработчики занятости

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

SQLite включает внутренний обработчик занятости, который использует таймер. Если вы установите период тайм-аута, SQLite будет продолжать попытки получить требуемые блокировки, пока не истечет время таймера.

int sqlite3_busy_timeout( sqlite3 *db, int millisec )
Устанавливает для данного соединения с базой данных использование внутреннего обработчика занятости на основе таймера. Если второй параметр больше нуля, обработчик настроен на использование значения тайм-аута в миллисекундах (тысячных долях секунды). Если второй параметр равен нулю или отрицателен, любой обработчик занятости будет очищен.

Если вы хотите написать свой собственный обработчик занятости, вы можете напрямую установить функцию обратного вызова:

int sqlite3_busy_handler( sqlite3 *db, callback_func_ptr, void *udp )
Устанавливает обработчик занятости для данной базы данных. Второй параметр - это указатель функции на обработчик занятости, а третий параметр - указатель пользовательских данных, который передается в функцию обратного вызова. Установка указателя функции NULL удалит обработчик занятости.
int user_defined_busy_handler_callback( void *udp, int incr )

Это не вызов библиотеки SQLite, а формат определяемого пользователем обработчика занятости. Первый параметр - это указатель пользовательских данных, переданный в sqlite3_busy_handler() при установке обратного вызова. Второй параметр - это счетчик, который увеличивается каждый раз, когда вызывается обработчик занятости в ожидании определенной блокировки.

Возвращаемое значение нуля приведет к тому, что SQLite откажется и вернет ошибку SQLITE_BUSY, в то время как ненулевое возвращаемое значение заставит SQLite продолжать попытки получить блокировку. Если блокировка успешно установлена, обработка команды продолжится. Если блокировка не получена, обработчик занятости будет вызван снова.

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

Тупиковые ситуации

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

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

Избегайте SQLITE_BUSY

При разработке кода для системы, требующей любой степени параллелизма с базами данных, самый простой подход - использовать sqlite3_busy_timeout() для установки значения тайм-аута, приемлемого для вашего приложения. Начните с примерно 250-2000 миллисекунд и оттуда отрегулируйте. Это поможет уменьшить количество кодов ответов SQLITE_BUSY, но не устранит их.

Единственный способ полностью избежать использования SQLITE_BUSY - гарантировать, что к базе данных никогда не будет подключаться более одного соединения с базой данных. Это можно сделать, установив для PRAGMA lock_mode значение EXCLUSIVE.

Если это неприемлемо, приложение может использовать транзакции, чтобы упростить обработку кода возврата SQLITE_BUSY. Если приложение может успешно начать транзакцию с BEGIN EXCLUSIVE TRANSACTION, это исключит возможность получения SQLITE_BUSY. Сам BEGIN может вернуть SQLITE_BUSY, но в этом случае приложение может просто сбросить оператор BEGIN с помощью sqlite3_reset() и повторить попытку. Недостатком BEGIN EXCLUSIVE является то, что он может быть запущен только тогда, когда никакое другое соединение не обращается к базе данных, включая любые транзакции только для чтения. После запуска эксклюзивной транзакции она также блокирует доступ к базе данных для всех других подключений, включая транзакции только для чтения.

Чтобы обеспечить больший параллелизм, приложение может использовать BEGIN IMMEDIATE TRANSACTION. Если транзакция IMMEDIATE запущена успешно, очень маловероятно, что приложение получит SQLITE_BUSY, пока не будет выполнен оператор COMMIT. Во всех случаях (включая COMMIT), если встречается SQLITE_BUSY, приложение может сбросить инструкцию, подождать и повторить попытку. Как и в случае с BEGIN EXCLUSIVE, инструкция BEGIN IMMEDIATE может возвращать SQLITE_BUSY, но приложение может просто сбросить инструкцию BEGIN и повторить попытку. Транзакция BEGIN IMMEDIATE может быть запущена, пока другие соединения считывают данные из базы данных. После запуска новые соединения на запись не будут разрешены, но соединения только для чтения могут продолжать обращаться к базе данных до тех пор, пока немедленная транзакция не будет вынуждена изменить файл базы данных. Обычно это происходит, когда транзакция фиксируется. Если все соединения с базой данных используют BEGIN IMMEDIATE для всех транзакций, которые изменяют базу данных, то взаимоблокировка невозможна, и все ошибки SQLITE_BUSY (как для модулей записи IMMEDIATE, так и для других считывателей) могут быть обработаны повторной попыткой.

Наконец, если приложение может успешно начать транзакцию любого типа (включая транзакцию по умолчанию, DEFERRED), оно никогда не должно получать SQLITE_BUSY (или рисковать тупиковой ситуацией), если оно не пытается изменить базу данных. Сам BEGIN может вернуть SQLITE_BUSY, но приложение может сбросить инструкцию BEGIN и повторить попытку.

Попытки изменить базу данных в рамках транзакции BEGIN DEFERRED (или в рамках автоматической фиксации) - единственные ситуации, когда база данных может заблокироваться, и единственные ситуации, когда ответ на SQLITE_BUSY должен выходить за рамки простого ожидания и повторной попытки (или разрешения занятый обработчик справится с этим). Если приложение выполняет изменения в отложенной транзакции, оно должно быть готово к возможной тупиковой ситуации.

Избежание тупиковых ситуаций

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

Во-первых, простые. Функции sqlite3_prepare_xxx(), sqlite3_backup_step() и sqlite3_blob_open() не могут вызвать тупик. Если код SQLITE_BUSY возвращается из одной из этих функций в любое время, просто подождите и вызовите функцию еще раз.

Если функция sqlite3_step(), sqlite3_reset() или sqlite3_finalize() возвращает SQLITE_BUSY из отложенной транзакции, приложение должно отступить и повторить попытку. Для операторов, которые не являются частью явной транзакции, подготовленный оператор можно просто сбросить и повторно выполнить. Для операторов, которые находятся внутри явно отложенной транзакции, вся транзакция должна быть откатана и запущена с самого начала. В большинстве случаев это произойдет при первой попытке изменить базу данных. Просто помните, что вся причина отката заключается в том, что какое-то другое соединение с базой данных должно изменить базу данных. Если приложение выполнило несколько операций чтения для подготовки операции записи, было бы лучше перечитать эту информацию в новой транзакции, чтобы убедиться, что данные все еще действительны.

Что бы вы ни делали, не игнорируйте ошибки SQLITE_BUSY. Они могут быть редкими, но они также могут стать источником большого разочарования при неправильном обращении.

Когда BUSY становится BLOCKED

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

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

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

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

Чтобы избежать этой ситуации, лучше начинать большие транзакции, которые изменяют множество строк с явным BEGIN EXCLUSIVE. Этот вызов может завершиться ошибкой с SQLITE_BUSY, но приложение может просто повторять команду до тех пор, пока она не завершится успешно. После запуска монопольной транзакции транзакция записи будет иметь полный доступ к базе данных, что исключает возможность SQLITE_IOERR_BLOCKED, даже если транзакция выльется из кеша до фиксации. Также может помочь увеличение размера кеша базы данных.

Служебные функции

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

Управление версиями

Для запроса версии библиотеки SQLite доступно несколько функций. Каждому вызову API соответствует макрос #define, объявляющий одно и то же значение.

SQLITE_VERSION const char* sqlite3_libversion( )
Возвращает версию библиотеки SQLite в виде строки UTF-8.
SQLITE_VERSION_NUMBER int sqlite3_libversion_number( )
Возвращает версию библиотеки SQLite в виде целого числа. Формат - MNNNPPP, где M - мажорная версия (в данном случае 3), N - минорная версия, а P - патч. Этот формат допускает выпуски до 3.999.999. Если выпущен подпункт патча, он не будет указан в этом номере версии.
SQLITE_SOURCE_ID const char* sqlite3_sourceid( )
Возвращает отметку о регистрации кода, используемого в этом выпуске. Строка состоит из даты, отметки времени и хэша SHA1 источника из исходного репозитория.

Если вы создаете собственное приложение, вы можете использовать макросы #define и вызовы функций, чтобы убедиться, что вы используете правильный заголовок для доступной библиотеки. Значения #define берутся из файла заголовка и устанавливаются при компиляции вашего приложения. Вызов функций возвращает те же значения, которые были заложены в библиотеку при ее компиляции.

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

Если вы хотите проверить достоверность динамической библиотеки, возможно, лучше сделать что-то вроде этого:

if ( SQLITE_VERSION_NUMBER > sqlite3_libversion_number( ) ) {
    /* library too old; report error and exit. */
}

Помните, что макрос будет содержать версию, использованную при компиляции вашего кода, в то время как вызов функции вернет версию библиотеки SQLite. В этом случае мы сообщаем об ошибке, если библиотека более старая (меньшая версия), чем та, которая использовалась для компиляции и сборки кода приложения.

Управление памятью

Когда SQLite необходимо динамически выделять память, он обычно вызывает обработчик памяти по умолчанию базовой операционной системы. Это заставляет SQLite выделять свою память из кучи приложения. Однако SQLite также можно настроить для управления собственной внутренней памятью (см. sqlite3_config() в приложении G). Это особенно важно для встроенных и портативных устройств, где память ограничена, а превышение доступности может привести к проблемам со стабильностью.

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

void* sqlite3_malloc( int numBytes )
Выделяет и возвращает буфер указанного размера. Если память не может быть выделена, возвращается NULL-указатель. Память всегда будет выровнена по 8 байт (64 бита). Это замена стандартной функции malloc() библиотеки C.
void* sqlite3_realloc( void *buffer, int numBytes )

Используется для изменения размера выделения памяти. Буферы могут быть больше или меньше. Учитывая буфер, ранее возвращенный sqlite3_malloc(), и количество байтов, *_realloc() выделит новый буфер указанного размера и скопирует столько старого буфера, сколько поместится в новый буфер. Затем он освободит старый буфер и вернет новый. Если новый буфер не может быть выделен, возвращается NULL, а исходный буфер не освобождается.

Если указатель буфера равен NULL, вызов эквивалентен вызову sqlite3_malloc(). Если параметр numBytes равен нулю или отрицателен, вызов эквивалентен вызову sqlite3_free().

Это замена стандартной функции realloc() библиотеки C.

void sqlite3_free( void *buffer )

Освобождает буфер памяти, ранее выделенный sqlite3_malloc() или sqlite3_realloc(). Также используется для освобождения результатов или буферов ряда функций API SQLite, которые вызывают внутри себя sqlite3_malloc().

Это замена стандартной функции free() библиотеки C.

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

Резюме

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

В следующих главах рассматриваются более сложные функции SQLite C API. Это включает в себя возможность определять свои собственные функции SQL. Это позволит вам расширить SQL, используемый SQLite, с помощью простых функций, а также агрегаторов (используемых с GROUP BY) и сортировки параметров сортировки. В дополнительных главах будет рассказано, как реализовать виртуальные таблицы и другие расширенные функции.

Глава 8.
Дополнительные функции и API

В этой главе затрагивается ряд различных областей, в основном связанных с функциями и интерфейсами, выходящими за рамки базового механизма базы данных. В первом разделе рассматриваются функции времени и даты SQLite, которые представлены в виде небольшого набора скалярных функций. Мы также кратко рассмотрим некоторые стандартные расширения, поставляемые с SQLite, такие как расширение интернационализации ICU, модуль текстового поиска FTS3 и модуль R*Tree. Мы также рассмотрим некоторые альтернативные интерфейсы, доступные для SQLite на разных языках сценариев и в других средах. В заключение мы кратко обсудим некоторые моменты, на которые следует обращать внимание при разработке на мобильных или встроенных системах.

Дата и время

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

Этот подход хорошо согласуется с простыми и гибкими целями проектирования SQLite. Даты и время могут быть очень сложными. Нечетные часовые пояса и изменение правил перехода на летнее время (DST) могут усложнить значения времени, в то время как даты за пределами последних нескольких сотен лет подлежат календарным системам, которые менялись и модифицировались на протяжении всей истории. Создание собственного типа потребует выбора определенной календарной системы и определенного набора правил преобразования, которые могут подходить или не подходить для поставленной задачи. Это одна из причин, по которой типичная база данных имеет так много разных типов данных времени и даты.

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

Требования к приложению

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

Во-первых, вам нужно выяснить, что вы пытаетесь сохранить. Это может быть время дня, например, отдельное значение часа, минуты, секунды без какой-либо связанной даты. Вам может потребоваться запись даты, которая относится к определенному дню, месяцу, году, но не имеет связанного времени. Многим приложениям требуются метки времени, которые включают дату и время для обозначения определенного момента времени. Некоторым приложениям необходимо записывать определенный день года, но не конкретный год (например, праздник). Многие приложения также используют длительности (временные дельты). Даже если приложение не хранит длительности или смещения, они часто вычисляются для целей отображения, таких как промежуток времени между «сейчас» и определенным событием.

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

Представления

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

Юлианский день

Самое простое и компактное представление - это Юлианский день. Это единственное значение с плавающей запятой, используемое для подсчета количества дней, прошедших с полудня по гринвичскому времени 24 ноября 4714 г. до н.э. SQLite использует пролептический григорианский календарь для этого представления. Полночь 1 января 2010 года по юлианскому календарю составляет 2455197,5. При хранении в виде 64-битного значения с плавающей запятой современные даты имеют точность чуть лучше одной миллисекунды.

Многие разработчики никогда не сталкивались с календарем по юлианскому календарю, но концептуально он не сильно отличается от более знакомого значения POSIX time() - просто используется другое значение (дни, а не секунды) и другая отправная точка.

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

Текстовые значения

Другое популярное представление - форматированное текстовое значение. Обычно они используются для хранения значения даты, значения времени или их комбинации. Хотя SQLite распознает несколько форматов, чаще всего даты указываются в формате YYYY-MM-DD, а время - в формате HH:MM:SS с использованием значения часа от 00 до 23. Если требуется полная временная метка, эти значения можно комбинировать. Например, YYYY-MM-DD HH:MM:SS. Хотя этот стиль даты может быть не самым естественным представлением, эти форматы основаны на международном стандарте ISO 8601 для представления даты и времени. У них также есть преимущество сортировки в хронологическом порядке с помощью простой строковой сортировки.

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

Основным недостатком текстовых значений является то, что любая операция (кроме отображения) требует значительного преобразования данных. Преобразование времени и даты требует сложной математики и может существенно повлиять на производительность некоторых приложений. Например, для перемещения даты на одну неделю вперед требуется преобразование исходной даты в некоторый обобщенный формат, смещение значения и его обратное преобразование в соответствующее текстовое значение. Расчет продолжительности также требует значительного количества конверсий. Стоимость преобразования может быть незначительной для простого обновления или вставки, но она может иметь очень заметное значение, если будет найдено в условном поиске.

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

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

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

Вы могли заметить, что ни один из этих форматов не поддерживает поле часового пояса. SQLite предполагает, что вся информация о времени и дате хранится в формате UTC или всемирном координированном времени. UTC - это, по сути, среднее время по Гринвичу, хотя есть некоторые незначительные технические различия.

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

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

Аналогичным образом, на UTC не влияет переход на летнее время (Daylight Saving Time (DST)). Нет никаких сдвигов, скачков или повторов значений UTC. Правила перехода на летнее время чрезвычайно сложны и могут легко отличаться в зависимости от местоположения, времени года или даже самого года, поскольку время перехода смещается и перемещается. DST по существу добавляет второй, зависящий от календаря часовой пояс для любого местоположения, усугубляя проблемы с преобразованием местоположения и местного времени. Все эти проблемы могут вызвать много головной боли.

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

Функции времени и даты

Почти вся функциональность времени и даты в SQLite обеспечивается пятью функциями SQL. Одна из этих функций по сути является универсальным переводчиком, предназначенным для преобразования практически любого формата времени или даты в любой другой формат. Остальные четыре функции действуют как удобные оболочки, которые обеспечивают фиксированный, предопределенный формат вывода.

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

Функция преобразования

Основная утилита для управления значениями времени и даты - это SQL-функция strftime():

strftime( format, time, modifier, modifier... )

Функция SQL strftime() смоделирована после функции C POSIX strftime(). Он использует маркеры форматирования стиля printf() для указания выходной строки. Первый параметр - это строка формата, которая определяет формат возвращаемого текстового значения. Второй параметр - это значение времени источника, которое представляет базовое время ввода. За ним следует ноль или более модификаторов, которые можно использовать для сдвига или преобразования входного значения перед его форматированием. Обычно все эти параметры являются текстовыми выражениями или текстовыми литералами, хотя значение времени может быть числовым.

Помимо любых буквальных символов, строка формата может содержать любой из следующих маркеров:

Например, формат времени HH:MM:SS.sss (включая доли секунды) может быть представлен строкой формата '%H:%M:%f'.

SQLite понимает ряд входных значений. Если формат строки времени не распознается и не может быть декодирован, strftime() вернет NULL. Будут распознаваться все следующие форматы ввода:

В случае второго, третьего и четвертого форматов существует единственный буквальный символ пробела между частью даты и частью времени. Пятый, шестой и седьмой форматы содержат букву T между датой и временем. Этот формат определен рядом стандартов ISO, включая стандартный формат временных меток XML. Предполагается, что последние два формата являются юлианским днем или (с модификатором) значением времени POSIX. Эти последние два не требуют определенного количества цифр и могут быть переданы как числовые значения.

Внутри strftime() всегда будет вычислять полную временную метку, которая содержит как дату, так и время. Любые поля, которые не указаны во входной строке времени, будут принимать значения по умолчанию. Значения часа, минуты и секунды по умолчанию - ноль или полночь, а дата по умолчанию - 1 января 2000 года.

Помимо выполнения переводов между форматами и представлениями, функция strftime() также может использоваться для управления и изменения значений времени перед их форматированием и возвратом. Могут быть предоставлены ноль или более модификаторов:

Первые семь модификаторов просто добавляют или вычитают указанное количество времени. Это делается путем перевода времени и даты в отдельное представление и последующего добавления или вычитания указанного значения. Однако это может привести к неверным датам. Например, применение модификатора '+1 month' к дате '2010-01-31' приведет к дате '2010-02-31', которой не существует. Чтобы избежать этой проблемы, после применения каждого модификатора значения даты и времени возвращаются к допустимым датам. Например, гипотетическая дата '2010-02-31' будет заменена на '2010-03-03', поскольку ненормализованной датой было три дня после конца февраля.

Тот факт, что нормализация выполняется после применения каждого модификатора, означает, что порядок модификаторов может быть очень важным. Следует тщательно продумать, как применяются модификаторы, иначе вы можете столкнуться с неожиданными результатами. Например, применение модификатора '+1 month' с последующим '-1 month' к дате '2010-01-31' приведет к дате '2010-02-03', что на три дня меньше исходного. значение. Это связано с тем, что первый модификатор нормализуется до '2010-03-03', который затем возвращается к '2010-02-03'. Если модификаторы применяются в обратном порядке, '-1 month' преобразует нашу дату сначала в '2009-12-31', а модификатор '+1 month' затем преобразует дату обратно в исходную дату начала в '2010-01-31'.

Три модификатора start of... сдвигают текущую дату назад во времени к указанной точке, а модификатор weekday сдвигает дату вперед с нуля на шесть дней, чтобы найти дату, которая приходится на указанный день недели. Допустимые значения weekday: 0-6, для воскресенья - 0.

Модификатор unixepoch может использоваться только как начальный модификатор числового значения времени. В этом случае предполагается, что значение представляет время POSIX, а не юлианский день, и переводится соответствующим образом. Хотя модификатор unixepoch должен отображаться как первый модификатор, дополнительные модификаторы все же могут применяться.

Последние два модификатора используются для перевода между представлением времени в формате UTC и местным временем. Имя модификатора описывает назначение перевода, поэтому localtime предполагает ввод в формате UTC и производит локальный вывод. И наоборот, модификатор utc предполагает ввод местного времени и производит вывод в формате UTC. SQLite зависит от локальной операционной системы (и ее часового пояса и конфигурации летнего времени) для этих переводов. В результате эти модификаторы подвержены любым ошибкам и ошибкам, которые могут присутствовать в библиотеках времени и даты операционной системы хоста.

Функции удобства

Чтобы помочь стандартизированным текстовым форматам избежать ошибок и обеспечить более удобный способ преобразования дат и времени в их наиболее распространенные представления, SQLite имеет ряд удобных функций. По сути, это функции-оболочки вокруг strftime(), которые выводят дату или время в фиксированном формате. Все четыре функции принимают один и тот же набор параметров, который по сути совпадает с параметрами, используемыми strftime(), за вычетом исходной строки формата.

date( timestring, modifier, modifier... )
Переводит строку времени, применяет любые модификаторы и выводит дату в формате YYYY-MM-DD. Эквивалентно строке формата '%Y-%m-%d'.
time( timestring, modifier, modifier... )
Преобразует строку времени, применяет любые модификаторы и выводит дату в формате HH:MM:SS. Эквивалентно строке формата '%H:%M:%S'.
datetime( timestring, modifier, modifier... )
Преобразует строку времени, применяет любые модификаторы и выводит дату в формате YYYY-MM-DD HH:MM:SS. Эквивалентно строке формата '%Y-%m-%d %H:%M:%S'.
julianday( timestring, modifier, modifier... )
Переводит строку времени, применяет любые модификаторы и выводит юлианский день. Эквивалентно строке формата '%J'. Эта функция немного отличается от функции strftime(), поскольку strftime() вернет юлианский день в виде текстового представления числа с плавающей запятой, а эта функция вернет фактическое число с плавающей запятой.

Все четыре функции распознают одну и ту же строку времени и значения модификатора, которые использует strftime().

Литералы времени

SQLite распознает три буквальных выражения. Когда вычисляется выражение, содержащее один из этих литералов, литерал принимает соответствующее текстовое представление текущей даты или времени в формате UTC.

CURRENT_TIME
Предоставляет текущее время в формате UTC. Формат будет HH:MM:SS со значением часа от 00 до 23 включительно. Это то же самое, что и выражение SQL time( 'now' ).
CURRENT_DATE
Предоставляет текущую дату в формате UTC. Формат будет YYYY-MM-DD. Это то же самое, что и выражение SQL date( 'now' ).
CURRENT_TIMESTAMP
Предоставляет текущую дату и время в формате UTC. Формат будет YYYY-MM-DD HH:MM:SS. Между сегментами даты и времени есть один пробел. Это то же самое, что и в выражении SQL datetime( 'now' ). Обратите внимание, что имя функции SQL - datetime(), а литерал - _TIMESTAMP.

Поскольку эти литералы возвращают соответствующее значение в формате UTC, такое выражение, как SELECT CURRENT_TIMESTAMP; может не вернуть ожидаемый результат. Чтобы получить дату и время в локальном представлении, вам нужно использовать такое выражение, как:

SELECT datetime( CURRENT_TIMESTAMP, 'localtime' );

В этом случае буквальный CURRENT_TIMESTAMP также может быть заменен на 'now'.

Примеры

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

Вот пример того, как получить локальную метку времени и сохранить ее как значение по юлианскому времени в формате UTC:

julianday( input_value, 'utc' )

Этот тип выражения может появиться в инструкции INSERT. Чтобы вставить текущее время, его можно упростить до значения 'now', которое всегда указывается в формате UTC:

julianday( 'now' )

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

datetime( jul_date, 'localtime' )

Также может быть уместным поместить подобное выражение в представление.

Если вы хотите представить дату в формате, более удобном для читателей из США, вы можете сделать что-то вроде этого:

strftime( '%m/%d/%Y', '2010-01-31', 'localtime' );

Это отобразит дату как 01/31/2010. Второй параметр также может быть юлианским значением или любым другим распознанным форматом даты.

Чтобы получить текущее значение времени POSIX (всегда в формате UTC):

strftime( '%s', 'now' )

Или для отображения местной даты и времени с учетом значения времени POSIX:

datetime( time_value, 'unixepoch', 'localtime' )

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

julianday( :year || '-' || :month || '-' || :day )

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

Расширение интернационализации ICU

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

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

Это проблема сопоставления с образцом, сортировки или всего, что зависит от сравнения текстовых значений. Например, большинство систем сортировки текста игнорируют различия в регистре слов. Некоторые языки также игнорируют определенные знаки ударения, но часто эти правила зависят от конкретного знака ударения и символа. Иногда правила и соглашения, используемые в языке, меняются от места к месту. По умолчанию SQLite понимает только 7-битную систему символов ASCII. Любая кодировка символов 128 или выше будет рассматриваться как двоичное значение без учета соглашений об использовании заглавных букв или эквивалентности. Хотя для английского языка этого часто бывает достаточно, для других языков обычно недостаточно.

Для более полной поддержки интернационализации вам необходимо создать SQLite с включенным расширением ICU. Проект International Components for Unicode - это библиотека с открытым исходным кодом, которая реализует огромное количество языковых функций. Эти функции настроены для разных регионов. Расширение SQLite ICU позволяет SQLite использовать различные аспекты библиотеки ICU, обеспечивая сортировку и сравнение с учетом локали, а также версии функций upper() и lower() с учетом локали.

Чтобы использовать расширение ICU, вы должны сначала загрузить и собрать библиотеку ICU. Исходный код библиотеки вместе с инструкциями по сборке можно загрузить с веб-сайта проекта http://www.icu-project.org/. Затем вы должны создать SQLite с включенным расширением ICU и связать его с библиотекой ICU. Чтобы включить расширение ICU в сборке объединения, определите директиву компилятора SQLITE_ENABLE_ICU.

Вы захотите взглянуть на исходный документ README. В нем объясняется, как использовать расширение для создания сопоставлений и операторов, зависящих от локали. Вы можете найти копию файла README в дистрибутиве с полным исходным кодом (в каталоге ext/icu) или в Интернете по адресу http://www.sqlite.org/src/artifact?ci=trunk&filename=ext/icu/README.txt.

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

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

Модуль полнотекстового поиска

SQLite включает движок полнотекстового поиска (FTS). Текущая версия известна как FTS3. Механизм FTS3 предназначен для каталогизации и индексации больших объемов текста. После проиндексирования движок FTS3 может быстро искать документы на основе различных типов поиска по ключевым словам. Хотя исходный код FTS3 в настоящее время поддерживается командой SQLite, части движка изначально были предоставлены членами группы инженеров Google.

Движок FTS3 представляет собой модуль виртуальной таблицы. Виртуальные таблицы похожи на представления в том, что они обертывают источник данных, чтобы он выглядел и действовал как обычная таблица. Представления получают свои данные из оператора SELECT, в то время как виртуальные таблицы зависят от определяемых пользователем функций C. Все функции, необходимые для реализации виртуальной таблицы, заключены в расширение, известное как модуль. Дополнительные сведения о том, как работают модули SQLite и виртуальные таблицы, см. в главе 10.

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

Механизм FTS3 включен во все стандартные дистрибутивы исходного кода SQLite (включая объединение), но по умолчанию отключен. Чтобы включить базовую функциональность FTS, определите директиву компилятора SQLITE_ENABLE_FTS3 при построении библиотеки SQLite. Чтобы включить более продвинутый синтаксис сопоставления, также определите SQLITE_ENABLE_FTS3_PARENTHESIS.

Создание и заполнение таблиц FTS

После того, как SQLite был скомпилирован с включенным движком FTS3, вы можете создать таблицу документа с оператором SQL, подобным этому:

CREATE VIRTUAL TABLE table_name USING FTS3 ( col1,... );

Помимо указания имени таблицы, вы можете определить ноль или более имен столбцов. Имя столбца - единственная информация, которая будет фактически использоваться. Любая информация о типе или ограничения столбца будут проигнорированы. Если имена столбцов не указаны, FTS автоматически создаст один столбец с именем content.

Таблицы FTS часто используются для хранения целых документов, и в этом случае им нужен только один столбец. В других случаях они используются для хранения различных категорий связанной информации и требуют нескольких столбцов. Например, если вы хотите хранить сообщения электронной почты в таблице FTS, может иметь смысл создать отдельные столбцы для строк "SUBJECT:", "FROM:", "TO:" и тела сообщения. Это позволит вам ограничить поиск определенным столбцом (и данными, которые он содержит). Спецификация столбца для таблицы FTS во многом определяется тем, как вы хотите искать данные. FTS также предоставляет оптимизированный способ поиска поискового запроса по всем проиндексированным столбцам.

Вы можете использовать стандартные операторы INSERT, UPDATE и DELETE для управления данными в таблице FTS. Как и в традиционных таблицах, в таблицах FTS есть столбец ROWID, который содержит уникальное целое число для каждой записи в таблице. На этот столбец также можно ссылаться, используя псевдоним DOCID. В отличие от традиционных таблиц, ROWID таблицы FTS стабилен благодаря вакууму (VACUUM в приложении C), поэтому на него можно надежно ссылаться через внешний ключ. Кроме того, в таблицах FTS есть внутренний столбец с тем же именем, что и имя таблицы. Этот столбец используется для специальных операций. Вы не можете вставлять или обновлять данные в этом столбце.

Любую виртуальную таблицу, включая таблицы FTS, можно удалить с помощью стандартной команды DROP TABLE.

Поиск в таблицах FTS

Таблицы FTS разработаны таким образом, что любой оператор SELECT будет работать правильно. Вы даже можете искать определенные текстовые значения или шаблоны напрямую с помощью операторов == или LIKE. Они будут работать, хотя будут несколько медленными, поскольку стандартные операторы потребуют полного сканирования таблицы.

Настоящая мощь системы FTS исходит от настраиваемого оператора MATCH. Этот оператор может использовать индексы, построенные вокруг отдельных текстовых значений, что позволяет очень быстро выполнять поиск по большим объемам текста. Как правило, поиск выполняется с использованием запроса, подобного следующему:

SELECT * FROM fts_table WHERE fts_column MATCH search_term;

Термин поиска, используемый оператором MATCH, имеет семантику, очень похожую на те, которые используются в поисковой системе в Интернете. Термины поиска разбиваются и сопоставляются со словами и терминами, найденными в текстовых значениях таблицы FTS. Как правило, оператор FTS MATCH не чувствителен к регистру и сопоставляется только с целыми словами. Например, поисковый запрос 'data' будет соответствовать 'research data', но не 'database'. Порядок поисковых запросов не имеет значения. Термины 'cat dog' и 'dog cat' будут соответствовать одному и тому же набору строк.

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

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

Подробнее

Модуль FTS является довольно продвинутым и предлагает большое количество опций поиска и оптимизаций индекса. Если вы планируете использовать движок FTS в своем приложении, я настоятельно рекомендую вам потратить некоторое время на чтение онлайн-документации (http://www.sqlite.org/ fts3.html). Официальная документация довольно обширна и подробно описывает более сложные функции поиска с примерами.

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

Для тех, кто хочет копнуть еще глубже, следующие разделы документа объясняют некоторые внутренние механизмы индекса FTS. Предоставляется информация о теневых таблицах, используемых механизмом FTS, а также о процессе, используемом для создания индекса токена. Этот уровень знаний не требуется для использования системы FTS, но он чрезвычайно полезен, если вы хотите изменить код или если вам просто интересно, что происходит под капотом.

Модули R*Trees и Spatial Indexing

Модуль R*Tree - это стандартное расширение SQLite, которое предоставляет структуру индекса, оптимизированную для многомерных ранжированных данных. Имя R*Tree относится к внутреннему алгоритму, используемому для организации и запроса сохраненных данных. Например, в двумерном R*Tree строки могут содержать прямоугольники в форме минимального и максимального значения долготы, а также минимальной и максимальной широты. Можно выполнять запросы, чтобы быстро найти все строки, которые содержат или перекрывают определенное геологическое местоположение или область. Добавление дополнительных параметров, например высоты, позволяет выполнять более сложный и конкретный поиск.

R*Tree не ограничиваются только пространственной информацией, но могут использоваться с любым типом данных числового диапазона, который включает пары минимальных и максимальных значений. Например, таблица R*Tree может использоваться для индексации времени начала и окончания событий. Затем индекс может быстро вернуть все события, которые были активны в определенный момент или в определенный период времени.

Реализация R*Tree, включенная в SQLite, может индексировать до пяти измерений данных (пять наборов пар min/max). Таблицы состоят из целочисленного столбца первичного ключа, за которым следуют от одной до пяти пар столбцов с плавающей запятой. В результате получится таблица с нечетным числом от 3 до 11 столбцов. Значения данных всегда должны указываться парами. Если вы хотите сохранить точку, просто используйте одно и то же значение как для минимального, так и для максимального компонента.

Как правило, R*Tree действуют как подробные таблицы для более традиционных таблиц. В традиционной таблице могут храниться любые данные, необходимые для определения рассматриваемого объекта, включая ключевую ссылку на данные R*Tree. Таблица R*Tree используется только для хранения размерных данных.

Многомерные R*Tree, особенно те, которые используются для хранения ограничивающих прямоугольников или ограничивающих объемов, часто являются приближениями к индексируемым ими записям. В этих случаях R*Tree не всегда могут предоставить точный набор результатов, но используются для эффективного предоставления первого приближения. По сути, R*Tree используется в качестве начального фильтра для быстрого и эффективного отсеивания всех строк, кроме небольшого процента. Для получения окончательного набора результатов можно применить более конкретное (и часто более дорогое) выражение фильтра. В большинстве случаев оптимизатор запросов понимает, как лучше всего использовать R*Tree, поэтому оно применяется перед любыми другими условными выражениями.

R*Tree довольно мощные, но они служат очень специфическим потребностям. Из-за этого мы не будем тратить время на раскрытие всех деталей. Если индекс R*Tree похож на то, что ваше приложение может использовать в своих интересах, я рекомендую вам проверить онлайн-документацию (http://www.sqlite.org/rtree.html). Это предоставит полное описание того, как создавать, заполнять и использовать индекс R*Tree.

Языки сценариев и другие интерфейсы

Как и большинство продуктов баз данных, SQLite имеет привязки, которые позволяют получить доступ к функциям API C из ряда различных языков сценариев и других сред. Во многих случаях эти расширения пытаются следовать стандартизированному интерфейсу базы данных, разработанному языковым сообществом. В других случаях драйвер или расширение сценария пытается точно представить собственный API SQLite.

За исключением расширения Tcl, все эти пакеты были разработаны сообществом пользователей SQLite. Таким образом, они не являются частью основного проекта SQLite и не поддерживаются командой разработчиков SQLite. Если у вас возникли проблемы с установкой или настройкой одного из этих драйверов, обращение за поддержкой в основной список рассылки пользователя SQLite может дать ограниченные результаты. Возможно, вам повезет больше в списке рассылки проекта или, если это не удастся, на более общем форуме поддержки, ориентированном на конкретный язык.

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

Perl

Предпочтительным модулем Perl является DBD::SQLite, он доступен на CPAN (http://www.cpan.org). Этот пакет предоставляет стандартизированный, совместимый с DBI интерфейс для SQLite, а также ряд настраиваемых функций, которые обеспечивают поддержку специфических функций SQLite.

DBI предоставляет стандартный интерфейс для обработки команд SQL. Пользовательские функции обеспечивают дополнительное покрытие API SQLite и дают возможность определять функции, агрегаты и сопоставления SQL с помощью Perl. Хотя пользовательские функции не обеспечивают полного охвата API SQLite, большинство наиболее распространенных операций включены.

PHP

По мере развития языка PHP меняются и методы доступа SQLite. PHP5 включает несколько различных расширений SQLite, которые предоставляют как интерфейсы, зависящие от производителя, так и драйверы для стандартизованного интерфейса PDO (объекты данных PHP).

Есть два расширения, зависящих от производителя. Расширение sqlite было включено и включено по умолчанию с PHP 5.0 и обеспечивает поддержку библиотеки SQLite v2. Расширение sqlite3 было включено и задействовано по умолчанию с PHP 5.3.0 и, как вы могли догадаться, предоставляет интерфейс для текущей библиотеки SQLite 3. Библиотека sqlite3 предоставляет довольно простой интерфейс классов для командных API SQL. Она также поддерживает создание функций и агрегатов SQL с помощью PHP.

PHP 5.1 представил интерфейсы PDO. Расширение PDO - это последнее решение проблемы предоставления унифицированных механизмов доступа к базе данных. PDO действует как замена интерфейсов PEAR-DB и MDB2, имеющихся в других версиях PHP. Расширение PDO_SQLITE предоставляет драйвер PDO для текущей библиотеки SQLite v3. Помимо поддержки стандартных методов доступа к PDO, этот драйвер также предоставляет пользовательские методы для создания функций и агрегатов SQL с использованием PHP.

Учитывая, что существует очень небольшая функциональная разница между библиотекой поставщика SQLite 3 и библиотекой PDO SQLite 3, я предлагаю в новой разработке использовать драйвер PDO.

Python

Доступны два популярных интерфейса Python. Каждая оболочка соответствует разному набору потребностей и требований. На момент написания оба модуля находились в активной разработке.

Модуль PySQLite (http://code.google.com/p/pysqlite/) предлагает стандартизированный интерфейс, совместимый с Python DB-API 2.0, для механизма SQLite. PySQLite позволяет приложениям разрабатывать относительно независимый от базы данных интерфейс. Это очень полезно для систем, которым необходимо поддерживать более одной базы данных. Использование стандартизованного интерфейса также позволяет быстро создавать прототипы с помощью SQLite, оставляя при этом возможность перехода к более крупным и сложным системам баз данных. Начиная с Python 2.5, PySQLite стал частью стандартной библиотеки Python.

Модуль APSW ((Another Python SQLite Wrapper; http://code.google.com/p/apsw/) преследует совсем другую цель разработки. APSW предоставляет очень минимальный уровень абстракции, который разработан, чтобы максимально имитировать собственный SQLite C API. APSW не пытается обеспечить совместимость с любым другим продуктом баз данных, но обеспечивает очень широкий охват библиотеки SQLite, включая многие низкоуровневые функции. Это обеспечивает очень точный контроль, включая возможность создавать определяемые пользователем функции SQL, агрегаты и сопоставления в Python. APSW можно даже использовать для написания реализации виртуальной таблицы на Python.

Оба модуля имеют свои сильные стороны. Какой модуль подходит для вашего приложения, зависит от ваших потребностей. Если ваша база данных достаточно проста и вам нужен стандартизованный интерфейс, который позволяет выполнять миграцию в будущем, то PySQLite лучше подходит. Если вас не интересуют другие движки баз данных, но вам нужен очень подробный контроль над SQLite, то стоит взглянуть на APSW.

Java

Для языка Java доступен ряд интерфейсов. Некоторые из них являются оболочками для собственного C API, а другие соответствуют стандартизированному API совместимости с базами данных Java (JDBC).

Одной из старых оболочек является Java SQLite (http://www.ch-werner.de/javasqlite/), которая обеспечивает поддержку как SQLite 2, так и SQLite 3. Ядро этой библиотеки использует собственный интерфейс Java (JNI) для создания интерфейс, основанный на собственном интерфейсе C. Библиотека также содержит интерфейс JDBC. Это хороший выбор, если вам нужен прямой доступ к SQLite API.

Более современным драйвером только для JDBC является пакет SQLiteJDBC (http://www.xerial.org/trac/Xerial/wiki/SQLiteJDBC). Это довольно хороший дистрибутив, поскольку файл JAR содержит как классы Java, так и собственные библиотеки SQLite для Windows, Mac OS X и Linux на базе Intel. Это упрощает кроссплатформенное распространение. Драйвер также активно используется Xerial, поэтому его обычно поддерживают в хорошем состоянии.

Tcl

SQLite имеет прочную историю с языком Tcl. Фактически, то, что мы теперь знаем как SQLite, начало свою жизнь как расширение Tcl. Многие инструменты тестирования и разработки для SQLite написаны на Tcl. Помимо собственного C API, расширение Tcl - единственный API, поддерживаемый основной командой SQLite.

Чтобы включить привязки Tcl, загрузите дистрибутив TEA (Tcl Extension Architecture) исходного кода SQLite с веб-сайта SQLite (http://www.sqlite.org/download.html). Эта версия кода, по сути, представляет собой объединенный дистрибутив с привязками Tcl, добавленными в конец. Это будет встроено в расширение Tcl, которое затем можно будет импортировать в любую среду Tcl. Документацию по интерфейсу Tcl можно найти по адресу http://www.sqlite.org/tclsqlite.html.

ODBC

Спецификация ODBC (Open Database Connectivity) предоставляет стандартизированный API базы данных для широкого спектра продуктов баз данных. Как и многие языковые расширения, драйверы ODBC действуют как мост между библиотекой ODBC и конкретным API базы данных. Использование ODBC позволяет разработчикам писать в один API, который затем может использовать любое количество коннекторов для взаимодействия с широким спектром продуктов баз данных. Многие универсальные инструменты баз данных используют ODBC для поддержки широкого спектра систем баз данных.

Самый известный коннектор для SQLite - это SQLiteODBC (http://www.ch-werner.de/sqliteodbc/). SQLiteODBC протестирован с рядом библиотек ODBC, что гарантирует его совместимость с большинством инструментов и приложений, использующих поддержку ODBC.

.NET

Существует ряд независимых проектов SQLite, использующих технологии .NET. Некоторые из них являются простыми оболочками C#, которые делают немного больше, чем предоставляют контекст объекта для API SQLite. Другие проекты пытаются интегрировать SQLite в более крупные структуры, такие как ADO (объекты данных ActiveX).

Одним из наиболее известных проектов с открытым исходным кодом является пакет System.Data.SQLite (http:// sqlite.phxsoftware.com/). Этот пакет обеспечивает широкую поддержку ADO, а также поддержку LINQ.

Также доступны коммерческие драйверы ADO и LINQ. Дополнительную информацию см. в wiki по SQLite.

C++

Хотя к API SQLite C можно напрямую обращаться из приложений C++, некоторые люди предпочитают более объектно-ориентированный интерфейс. Если вы предпочитаете использовать существующую библиотеку, существует несколько доступных оболочек. Вы можете проверить веб-сайт SQLite или выполнить поиск в Интернете, если вам интересно.

Имейте в виду, что некоторые из этих библиотек содержатся в хорошем состоянии. Возможно, вам будет лучше просто написать и поддерживать свои собственные классы-оболочки. SQLite API имеет довольно объектно-зависимый дизайн, при этом большинство функций выполняют какие-либо манипуляции или действия с определенной структурой данных SQLite. В результате большинство оболочек C++ несколько тонкие и обеспечивают не более чем синтаксический перевод. Поддержание частной оболочки обычно не является серьезным бременем.

Просто помните, что основная библиотека SQLite - это C, а не C++, и ее нельзя скомпилировать с большинством компиляторов C++. Даже если вы решите обернуть API SQLite в интерфейс на основе классов C++, вам все равно потребуется скомпилировать базовую библиотеку SQLite с помощью компилятора C.

Другие языки

В дополнение к уже перечисленным языкам существуют оболочки, библиотеки и расширения для множества других языков и сред. В вики-разделе веб-сайта SQLite есть обширный список сторонних драйверов по адресу http://www.sqlite.org/cvstrac/wiki?p=SqliteWrappers. Многие из перечисленных драйверов больше не поддерживаются активно, поэтому обязательно изучите веб-сайты проектов, прежде чем вкладывать средства в конкретный драйвер. Те, которые заведомо заброшены, помечаются как таковые, но поддерживать такую информацию в актуальном состоянии сложно.

Мобильная и встраиваемая разработка

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

Во многих случаях требования приложений к хранению данных и управлению ими очень хорошо соответствуют реляционной модели, предоставляемой SQLite. SQLite - это довольно небольшой и очень ресурсоемкий продукт, поэтому он хорошо работает в ограниченных средах. Модель «база данных в файле» также упрощает и ускоряет копирование или резервное копирование хранилищ данных. Учитывая все эти факторы, неудивительно, что почти все основные SDK для смартфонов поддерживают SQLite из коробки или позволяют легко скомпилировать его для своей платформы.

Память

Большинство мобильных устройств имеют ограниченные ресурсы памяти. Приложения должны учитывать использование своей памяти и часто должны ограничивать ресурсы, которые могут быть потреблены. В большинстве случаев большая часть использования памяти SQLite происходит из кеша страниц. Выбирая разумный размер страницы и размер кеша, можно контролировать большую часть использования памяти. Помните, что каждая открытая или присоединенная база данных обычно имеет свой собственный независимый кеш. Размер страницы можно настроить при создании базы данных с помощью команды PRAGMA page_size, а размер кеша можно изменить в любое время с помощью PRAGMA cache_size. См. page_size и cache_size в приложении F для более подробной информации.

Имейте в виду, что большие размеры кеша не всегда значительно лучше. Поскольку некоторые типы флеш-систем хранения не имеют эффективного времени поиска, иногда можно использовать относительно небольшой кэш страниц, сохраняя при этом приемлемую производительность. Более быстрое время отклика системы хранения снижает стоимость извлечения страниц из памяти, уменьшая влияние кеша. То, насколько быстро система хранения может реагировать, во многом зависит от типов флеш-чипов и от того, как они настроены в устройстве, но в зависимости от вашей системы вы можете счесть приемлемым использование относительно небольшого кэша. Это должно помочь держать ваш объем памяти под контролем.

Если вы работаете в чрезвычайно ограниченной среде, вы можете предварительно выделить буферы и сделать их доступными для SQLite через интерфейс sqlite3_config(). Для кучи SQLite, временных буферов и кеша страниц могут быть назначены разные буферы. Если буферы настроены до инициализации библиотеки SQLite, вся память будет выделяться из этих буферов. Это позволяет хост-приложению точно контролировать ресурсы памяти, которые может использовать SQLite.

Большинство других проблем довольно очевидны. Например, использование баз данных в памяти обычно не рекомендуется на устройствах с привязкой к памяти, если только база данных не очень мала, а прирост производительности является значительным. Точно так же следует помнить о запросах, которые могут генерировать большие промежуточные наборы результатов, например о предложениях ORDER BY. В некоторых случаях может иметь смысл вытащить часть обработки или бизнес-логики из базы данных в ваше приложение, где у вас будет лучший контроль над использованием ресурсов.

Устройство хранения

Почти все мобильные устройства используют те или иные твердотельные носители. Хранилище может быть встроенным, или это может быть карта расширения, такая как карта SD (Secure Digital), или даже внешний флэш-накопитель. Хотя эти системы хранения обеспечивают те же базовые функции, что и их «реальные компьютерные» аналоги, эти устройства хранения часто имеют заметно отличающиеся рабочие характеристики от традиционных устройств массового хранения.

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

Обычно SQLite сильно зависит от блокировки файловой системы для обеспечения надлежащей поддержки параллелизма. К сожалению, эта функциональность может быть ограничена на мобильных и встроенных платформах. Во избежание проблем лучше отказаться от нескольких подключений к одному и тому же файлу базы данных даже из одного приложения. Если требуется несколько подключений, убедитесь, что операционная система обеспечивает правильную блокировку, или используйте альтернативную систему блокировки. Также рассмотрите возможность настройки соединений с базой данных для получения и удержания любых блокировок (используйте команду PRAGMA locking_mode; см. locking_mode в приложении F). Хотя это делает доступ эксклюзивным для одного соединения, это увеличивает производительность, но при этом обеспечивает защиту.

Может возникнуть соблазн отключить механизм синхронизации и ведения журнала SQLite, но вы должны учитывать любые возможные последствия отключения этих процедур. Хотя при отключении синхронизаций и файлов журнала часто наблюдается значительный прирост производительности, существует также значительная опасность безвозвратного повреждения данных.

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

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

Другие источники

Помимо специальных обработчиков памяти и устройств хранения, большинство других проблем сводятся к осознанию ограничений вашей платформы и контролю за использованием ресурсов. Если вы готовите пользовательскую сборку SQLite для своего приложения, вам следует потратить некоторое время, чтобы просмотреть все доступные директивы компилятора и посмотреть, есть ли какие-либо значения по умолчанию, которые вы хотите изменить, или какие-либо функции, которые вы, возможно, захотите отключить (см. приложение A) . Отключение неиспользуемых функций может еще больше уменьшить объем кода и памяти. Отключение некоторых функций также обеспечивает незначительное повышение производительности.

Также рекомендуется прочитать доступные операторы PRAGMA и посмотреть, есть ли какие-либо дополнительные параметры конфигурации для настройки поведения SQLite для вашей конкретной среды. Команды PRAGMA также можно использовать для динамической настройки использования ресурсов. Например, можно временно увеличить размер кэша для операции с интенсивным вводом-выводом, если это делается в то время, когда вы знаете, что доступно больше памяти. Затем размер кеш-памяти может быть уменьшен, что позволит использовать память в другом месте приложения.

Поддержка iPhone

Когда впервые были выпущены iPhone и iPod touch, Apple активно пропагандировала использование SQLite. Библиотека SQLite была предоставлена как системная структура и была хорошо документирована вместе с примерами кода в SDK.

С выпуском версии 3.0 Apple сделала свою систему Core Data доступной для iPhone OS. Core Data был доступен на платформе Macintosh в течение ряда лет и предоставляет высокоуровневую структуру абстракции данных, которая предлагает интегрированные инструменты проектирования и поддержку времени выполнения для решения сложных задач управления данными. В отличие от SQLite, модель Core Data не является строго реляционной по своей природе.

Теперь, когда на их мобильной платформе доступна библиотека более высокого уровня, Apple поощряет людей переходить на Core Data. Большая часть документации и примеров кода SQLite была удалена из SDK, и инфраструктура системного уровня больше не доступна. Однако, поскольку Core Data использует SQLite на уровне хранения, для использования по-прежнему доступна стандартная системная библиотека SQLite. Также относительно легко скомпилировать версии библиотеки SQLite для конкретных приложений. Это необходимо, если вы хотите воспользоваться некоторыми из новейших функций, поскольку системная версия SQLite часто отстает на несколько версий.

Core Data имеет ряд существенных преимуществ. Apple предоставляет инструменты разработки, которые позволяют разработчику быстро определять свои требования к данным и отношения. Это может сократить время разработки и сэкономить код. Пакет Core Data также хорошо интегрирован в текущие системы Mac OS X, что позволяет легко перемещать данные между платформами.

Несмотря на все преимущества, которые предоставляет Core Data, все же есть ситуации, когда имеет смысл использовать SQLite напрямую. Наиболее очевидное соображение - если ваши потребности в разработке выходят за рамки платформ Apple. В отличие от Core Data, библиотека SQLite доступна практически на любой платформе, что позволяет перемещать файлы данных и получать к ним доступ практически в любом месте на любой платформе. Core Data также использует другую модель хранения и извлечения, чем SQLite. Если ваше приложение особенно хорошо подходит для реляционной модели, прямой доступ SQL-запроса к уровню хранения данных может иметь преимущества. Непосредственное использование библиотеки SQLite также устраняет ряд уровней абстракции из дизайна приложения. Хотя это может привести к более подробному коду, это также может привести к повышению производительности, особенно с большими наборами данных.

Как и многие другие инженерные решения, оба подхода имеют свои преимущества и недостатки. Если предположить, что ограничения платформы не вызывают беспокойства, Core Data может предоставить очень быстрое решение для умеренно простых систем. С другой стороны, SQLite обеспечивает лучшую кроссплатформенную совместимость (как в коде, так и в данных) и позволяет напрямую манипулировать сложными моделями данных. Если вы не зависите от последней версии SQLite, вы даже можете уменьшить размер своего приложения, используя существующую системную библиотеку SQLite. Какой набор факторов имеет для вас большее значение, вероятно, будет зависеть от требований вашей платформы и сложности вашей модели данных.

Другие среды

Для ряда сред смартфонов требуется, чтобы разработка приложений выполнялась на Java или другом подобном языке. Эти системы часто не предоставляют компиляторов C и ограничивают возможность развертывания чего-либо, кроме байт-кода. Хотя большинство этих платформ предоставляют настраиваемые оболочки для системных библиотек SQLite, эти оболочки часто несколько ограничены. Обычно системные библиотеки отстают на несколько версий, а оболочки Java часто ограничиваются вызовами основных основных функций.

Хотя это может ограничить возможность настройки сборки SQLite и использования расширенных функций продукта SQLite, эти библиотеки по-прежнему обеспечивают полный доступ к уровню SQL и всем функциям, которые с ним связаны, включая ограничения, триггеры и транзакции. Чтобы обойти некоторые ограничения (например, отсутствие пользовательских функций), иногда может потребоваться перенести часть бизнес-логики в приложение. Лучше всего это сделать, разработав уровень доступа в приложении, который централизует все функции базы данных. Централизация позволяет коду приложения последовательно обеспечивать соблюдение любых ограничений конструкции базы данных, даже если база данных не в состоянии сделать это в полной мере. Также неплохо включить какой-либо тип функции проверки, которая может сканировать базу данных, выявляя (и, надеюсь, исправляя) любые проблемы.

Дополнительные расширения

Помимо описанных здесь интерфейсов и модулей, для SQLite доступно множество других расширений и сторонних пакетов. Некоторые из них представляют собой простые, но полезные расширения, например полный набор математических функций. Лучший способ найти их - поискать в Интернете или задать вопрос в списке рассылки пользователя SQLite. Вы также можете начать с просмотра http://sqlite.org/contrib/ списка некоторых из старых кодов.

В дополнение к расширениям баз данных доступно также несколько инструментов и менеджеров баз данных, специфичных для SQLite. В дополнение к оболочке командной строки существует несколько интерфейсов GUI. Одним из наиболее популярных является SQLite Manager, расширение Firefox, доступное по адресу http://code.google.com/p/sqlite-manager/.

Также существует небольшое количество коммерческих расширений для SQLite. Как уже говорилось, для некоторых драйверов баз данных требуется коммерческая лицензия. Hwaci, Inc. (компания, ответственная за разработку SQLite) предлагает два коммерческих расширения. Расширение SQLite Encryption Extension (SEE) шифрует страницы базы данных по мере их записи на диск, эффективно шифруя любую базу данных. Расширение Compressed and Encrypted Read-Only Database (CEROD) идет дальше, сжимая страницы базы данных. Сжатие уменьшает размер файла базы данных, но также делает базу данных доступной только для чтения. Это расширение может быть полезно для распространения лицензионных архивов данных или справочных материалов. Для получения дополнительной информации об этих расширениях см. http://www.sqlite.org/support.html.

Глава 9
Функции и расширения SQL

SQLite позволяет разработчику расширять среду SQL, создавая собственные функции SQL. Хотя эти функции используются в операторах SQL, код для реализации функции написан на C.

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

Второй тип функций - это агрегатная функция или агрегатор. Это функции SQL, такие как sum() или avg(), которые используются вместе с предложениями GROUP BY для суммирования или иного агрегирования серии значений в окончательный результат.

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

В этой главе также будет рассмотрено, как упаковать набор настраиваемых функций в расширение SQLite. Расширения - это стандартный способ упаковки настраиваемых функций, агрегатов, сопоставлений, виртуальных таблиц (см. главу 10) или любых других настраиваемых функций. Расширения - это удобный и стандартизированный способ объединения наборов связанных функций или настроек в библиотеки функций SQL.

Расширения могут быть статически связаны с приложением или могут быть встроены в загружаемые расширения. Загружаемые расширения действуют как «плагины» для библиотеки SQLite. Загружаемые расширения - это особенно полезный способ загрузки ваших пользовательских функций в sqlite3, предоставляющий возможность тестировать запросы или отлаживать проблемы в той же среде SQL, что и в вашем приложении.

Исходный код примеров, приведенных в этой главе, можно найти в книге для скачивания. Скачивание исходного кода значительно упростит создание примеров и их опробование. См. «Пример загрузки кода» для получения дополнительной информации о том, где найти исходный код (локальная ссылка: UsingSQLiteCode.tar.gz).

Скалярные функции

Структура и назначение скалярных функций SQL аналогичны функциям C или традиционным математическим функциям. Вызывающий предоставляет ряд параметров функции, а функция вычисляет и возвращает значение. Иногда эти функции являются чисто функциональными (в математическом смысле), поскольку они вычисляют результат, основываясь исключительно на параметрах без какого-либо внешнего влияния. В других случаях функции носят более процедурный характер и вызываются для вызова определенных побочных эффектов.

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

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

Однако скалярные функции по-прежнему можно использовать для обработки наборов данных. Рассмотрим этот оператор SQL:

SELECT format( name ) FROM employees;

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

Регистрация функций

Чтобы создать пользовательскую функцию SQL, вы должны привязать имя функции SQL к указателю функции C. Функция C действует как обратный вызов. Каждый раз, когда механизму SQL требуется вызвать названную функцию SQL, вызывается зарегистрированный указатель функции C. Это дает возможность оператору SQL вызывать написанную вами функцию C.

Эти функции позволяют создавать и связывать имя функции SQL с указателем функции C:

int sqlite3_create_function( sqlite3 *db, const char *func_name, int num_param, int text_rep, void *udp, func_ptr, step_func, final_func ) int sqlite3_create_function16( sqlite3 *db, const void *func_name, int num_param, int text_rep, void *udp, func_ptr, step_func, final_func )

Создает новую функцию SQL в соединении с базой данных. Первый параметр - это соединение с базой данных. Второй параметр - это имя функции в виде строки в кодировке UTF-8 или UTF-16. Третий параметр - это количество ожидаемых параметров функции SQL. Если это значение отрицательное, количество ожидаемых параметров является переменным или неопределенным. Четвертое - это ожидаемое представление текстовых значений, передаваемых в функцию, и может быть одним из SQLITE_UTF8, SQLITE_UTF16, SQLITE_UTF16BE, SQLITE_UTF16LE или SQLITE_ANY. За ним следует указатель пользовательских данных.

Последние три параметра - это указатели на функции. Позже мы рассмотрим конкретные прототипы этих указателей на функции. Для регистрации и создания скалярной функции используется только указатель первой функции. Два других указателя на функции используются для регистрации агрегатных функций и должны быть установлены в NULL при определении скалярной функции.

SQLite позволяет перегрузить имена функций SQL в зависимости от количества параметров и текстового представления. Это позволяет связать несколько функций C с одним и тем же именем функции SQL. Вы можете использовать эту возможность перегрузки для регистрации различных реализаций C одной и той же функции SQL. Это может быть полезно для эффективной обработки различных кодировок текста или для обеспечения различного поведения в зависимости от количества параметров.

Вам не нужно регистрировать несколько кодировок текста. Когда библиотеке SQLite необходимо выполнить вызов функции, она попытается найти зарегистрированную функцию с соответствующим текстовым представлением. Если она не может найти точное совпадение, она преобразует любые текстовые значения и вызовет одну из других доступных функций. Значение SQLITE_ANY указывает, что функция готова принимать текстовые значения в любой возможной кодировке.

Вы можете обновить или переопределить функцию, просто перерегистрировав ее с другим указателем на функцию. Чтобы удалить функцию, вызовите sqlite3_create_function_xxx() с тем же именем, количеством параметров и текстовым представлением, но передайте NULL для всех указателей функций. К сожалению, нет способа узнать, зарегистрировано ли имя функции или нет, кроме как самостоятельно отслеживать. Это означает, что нет никакого способа отличить действие создания от действия переопределения.

Допускается создание новой функции в любое время. Однако существуют ограничения на то, когда вы можете изменить или удалить функцию. Если в соединении с базой данных есть какие-либо подготовленные операторы, которые в настоящее время выполняются (sqlite3_step() был вызван хотя бы один раз, а sqlite3_reset() - нет), вы не можете переопределить или удалить пользовательскую функцию, вы можете только создать новую. Любая попытка переопределить или удалить функцию вернет SQLITE_BUSY.

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

Фактическая функция C, которую вам нужно написать, выглядит так:

void custom_scalar_function( sqlite3_context *ctx, int num_values, sqlite3_value **values )

Это прототип функции C, используемой для реализации специальной скалярной функции SQL. Первый параметр - это структура sqlite3_context, которую можно использовать для доступа к указателю пользовательских данных, а также для установки результата функции. Второй параметр - это количество значений параметра, присутствующих в третьем параметре. Третий параметр - это массив указателей sqlite3_value.

Второй и третий параметры (int num_values, sqlite3_value **values) работают вместе очень похоже на традиционные основные параметры C (int argc, char **argv).

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

Большинство пользовательских функций следуют довольно стандартному шаблону. Во-первых, вам нужно изучить параметры sqlite3_value, чтобы проверить их типы и извлечь их значения. Вы также можете извлечь указатель пользовательских данных, переданный в sqlite3_create_function_xxx(). Затем ваш код может выполнять любые необходимые вычисления или процедуры. Наконец, вы можете установить возвращаемое значение функции или вернуть состояние ошибки.

Извлечение параметров

Параметры функции SQL передаются в вашу функцию C в виде массива структур sqlite3_value. Каждая из этих структур содержит одно значение параметра.

Чтобы извлечь рабочие значения C из структур sqlite3_value, необходимо вызвать одну из функций sqlite3_value_xxx(). Эти функции очень похожи на функции sqlite3_column_xxx() по использованию и дизайну. Единственное существенное отличие состоит в том, что эти функции принимают один указатель sqlite3_value, а не подготовленный оператор и индекс столбца.

Как и их аналоги в столбцах, функции значений будут пытаться автоматически преобразовать значение в любой запрошенный тип данных. Процесс и правила преобразования такие же, как и в функциях sqlite3_column_xxx(). См. Таблицу 7-1 для получения более подробной информации.

const void* sqlite3_value_blob( sqlite3_value *value )
Извлекает и возвращает указатель на BLOB.
double sqlite3_value_double( sqlite3_value *value )
Извлекает и возвращает значение с плавающей запятой двойной точности.
int sqlite3_value_int( sqlite3_value *value )
Извлекает и возвращает 32-разрядное целое число со знаком. Возвращаемое значение будет обрезано (без предупреждения), если значение параметра содержит целочисленное значение, которое не может быть представлено только 32 битами.
sqlite3_int64 sqlite3_value_int64( sqlite3_value *value )
Извлекает и возвращает 64-битное целое число со знаком.
const unsigned char* sqlite3_value_text( sqlite3_value *value )
Извлекает и возвращает текстовое значение в кодировке UTF-8. Значение всегда будет обнулено. Обратите внимание, что возвращаемый указатель char не имеет знака и, вероятно, потребует приведения. Указатель также может иметь значение NULL, если требовалось преобразование типа.
const void* sqlite3_value_text16( sqlite3_value *value ) const void* sqlite3_value_text16be( sqlite3_value *value ) const void* sqlite3_value_text16le( sqlite3_value *value )
Извлекает и возвращает строку в кодировке UTF-16. Первая функция возвращает строку в собственном порядке байтов машины. Две другие функции будут возвращать строку, которая всегда закодирована с прямым или обратным порядком байтов. Значение всегда будет заканчиваться нулем. Указатель также может иметь значение NULL, если требовалось преобразование типа.

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

int sqlite3_value_type( sqlite3_value *value )
Возвращает собственный тип данных значения. Возвращаемое значение может быть одним из SQLITE_BLOB, SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT или SQLITE_NULL. Это значение может измениться или стать недействительным, если происходит преобразование типа.
int sqlite3_value_numeric_type( sqlite3_value *value )

Эта функция пытается преобразовать значение в числовой тип (SQLITE_FLOAT или SQLITE_INTEGER). Если преобразование может быть выполнено без потери данных, то преобразование выполняется и возвращается тип данных нового значения. Если преобразование не может быть выполнено, значение не будет преобразовано, и будет возвращен исходный тип данных значения. Это может быть любое значение, возвращаемое функцией sqlite3_value_type().

Основное различие между этой функцией и простым вызовом sqlite3_value_double() или sqlite3_value_int() заключается в том, что преобразование будет происходить только в том случае, если оно имеет смысл и не приведет к потере данных. Например, sqlite3_value_double() преобразует NULL в значение 0.0, а эта функция - нет. Точно так же sqlite3_value_int() преобразует первую часть строки '123xyz' в целое число 123, игнорируя завершающий 'xyz'. Однако эта функция не будет работать, потому что конечный 'xyz' не может быть понят в числовом контексте.

int sqlite3_value_bytes( sqlite3_value *value )
Возвращает количество байтов в BLOB или строке в кодировке UTF-8. При возврате размера текстового значения размер будет включать нулевой терминатор.
int sqlite3_value_bytes16( sqlite3_value *value )
Возвращает количество байтов в строке в кодировке UTF-16, включая нулевой признак конца.

Как и в случае с функциями sqlite3_column_xxx(), любые возвращаемые указатели могут стать недействительными, если другой вызов sqlite3_value_xxx() выполняется для той же структуры sqlite3_value. Точно так же преобразование данных может происходить для текстовых типов данных при вызове sqlite3_value_bytes() или sqlite3_value_bytes16(). В общем, вы должны следовать тем же правилам и практикам, что и при использовании функций sqlite3_column_xxx(). См. «Столбцы результатов» для получения более подробной информации.

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

void* sqlite3_user_data( sqlite3_context *ctx )
Извлекает указатель пользовательских данных, который был передан в sqlite3_create_function_xxx() при регистрации функции. Имейте в виду, что этот указатель используется для всех вызовов этой функции в этом соединении с базой данных.
sqlite3* sqlite3_context_db_handle( sqlite3_context *ctx )
Возвращает соединение с базой данных, которое использовалось для регистрации этой функции.

Соединение с базой данных, возвращаемое sqlite3_context_db_handle(), может использоваться функцией для выполнения запросов или иного взаимодействия с базой данных.

Возврат результатов и ошибок

После того, как функция извлекла и проверила свои параметры, она может приступить к работе. Когда результат вычислен, его нужно передать обратно в механизм SQLite. Это делается с помощью одной из функций sqlite3_result_xxx(). Эти функции устанавливают значение результата в структуре функции sqlite3_context.

Установка значения результата - единственный способ, которым ваша функция может сообщить механизму SQLite об успешном или неудачном вызове функции. Сама функция C имеет тип возвращаемого значения void, поэтому любой результат или ошибка должны быть переданы обратно через структуру контекста. Обычно одна из функций sqlite3_result_xxx() вызывается непосредственно перед вызовом return в вашей функции C, но допустимо устанавливать новый результат несколько раз на протяжении всей функции. Однако будет возвращен только последний результат.

Функции sqlite3_result_xxx() очень похожи на функции sqlite3_bind_xxx() по использованию и дизайну. Основное отличие состоит в том, что эти функции принимают структуру sqlite3_context, а не подготовленный оператор и индекс параметра. Функция может возвращать только один результат, поэтому любой вызов функции sqlite3_result_xxx() переопределит значение, установленное предыдущим вызовом.

void sqlite3_result_blob( sqlite3_context* ctx, const void *data, int data_len, mem_callback )
Кодирует буфер данных как результат BLOB.
void sqlite3_result_double( sqlite3_context *ctx, double data )
В результате кодирует 64-битное значение с плавающей запятой.
void sqlite3_result_int( sqlite3_context *ctx, int data )
В результате кодирует 32-битное целое число со знаком.
void sqlite3_result_int64( sqlite3_context *ctx, sqlite3_int64 data )
В результате кодирует 64-битное целое число со знаком.
void sqlite3_result_null( sqlite3_context *ctx )
В результате кодирует SQL NULL.
void sqlite3_result_text( sqlite3_context *ctx, const char *data, int data_len, mem_callback )
В результате кодирует строку в кодировке UTF-8.
void sqlite3_result_text16( sqlite3_context *ctx, const void *data, int data_len, mem_callback ) void sqlite3_result_text16be( sqlite3_context *ctx, const void *data, int data_len, mem_callback ) void sqlite3_result_text16le( sqlite3_context *ctx, const void *data, int data_len, mem_callback )
В результате кодирует строку в кодировке UTF-16. Первая функция используется для строки в собственном байтовом формате, а две последние функции используются для строк, которые явно закодированы как big-endian или little-endian, соответственно.
void sqlite3_result_zeroblob( sqlite3_context *ctx, int length )
В результате кодирует BLOB. BLOB будет содержать указанное количество байтов, и каждый байт будет установлен в ноль (0x00).
void sqlite3_result_value( sqlite3_context *ctx, sqlite3_value *result_value )

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

Эта функция принимает как защищенные, так и незащищенные объекты значений. Вы можете передать в эту функцию один из параметров sqlite3_value, если хотите вернуть один из входных параметров функции SQL. Вы также можете передать значение, полученное в результате вызова, в sqlite3_column_value().

Установка значения BLOB или текста требует того же типа управления памятью, что и эквивалентные функции sqlite3_bind_xxx(). Последний параметр этих функций - указатель обратного вызова, который должным образом освободит и выпустит указанный буфер данных. Вы можете передать ссылку на sqlite3_free() напрямую (при условии, что буферы данных были выделены с помощью sqlite3_malloc()), или вы можете написать свой собственный менеджер памяти (или оболочку). Вы также можете передать один из флагов SQLITE_TRANSIENT или SQLITE_STATIC. См. «Значения привязки» для подробностей о том, как можно использовать эти флаги.

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

void sqlite3_result_error( sqlite3_context *ctx, const char *msg, int msg_size ) void sqlite3_result_error16( sqlite3_context *ctx, const void *msg, int msg_size )
Устанавливает код ошибки на SQLITE_ERROR и устанавливает сообщение об ошибке в предоставленную строку в кодировке UTF-8 или UTF-16. Создается внутренняя копия строки, поэтому приложение может освободить или изменить строку, как только эта функция вернется. Последний параметр указывает размер сообщения в байтах. Если строка заканчивается нулем и последний параметр отрицательный, размер строки вычисляется автоматически.
void sqlite3_result_error_toobig( sqlite3_context *ctx )
Указывает, что функция не смогла обработать текст или значение BLOB из-за своего размера.
void sqlite3_result_error_nomem( sqlite3_context *ctx )
Указывает, что функция не может быть завершена из-за невозможности выделить необходимую память. Эта специализированная функция предназначена для работы без выделения дополнительной памяти. Если вы столкнулись с ошибкой выделения памяти, просто вызовите эту функцию, и ваша функция вернется.
void sqlite3_result_error_code( sqlite3_context *ctx, int code )
Устанавливает конкретный код ошибки SQLite. Не устанавливает и не изменяет сообщение об ошибке.

Можно вернуть как настраиваемое сообщение об ошибке, так и конкретный код ошибки. Сначала вызовите sqlite3_result_error() (или sqlite3_result_error16()), чтобы установить сообщение об ошибке. Это также установит код ошибки на SQLITE_ERROR. Если вам нужен другой код ошибки, вы можете вызвать sqlite3_result_error_code(), чтобы заменить общий код ошибки чем-то более конкретным, оставив сообщение об ошибке нетронутым. Просто имейте в виду, что sqlite3_result_error() всегда будет устанавливать код ошибки на SQLITE_ERROR, поэтому вы должны установить сообщение об ошибке, прежде чем устанавливать конкретный код ошибки.

Пример

Вот простой пример, который предоставляет функцию SQLite C API sqlite3_limit() среде SQL как функцию SQL sql_limit(). Эта функция используется для настройки различных ограничений, связанных с подключением к базе данных, таких как максимальное количество столбцов в наборе результатов или максимальный размер значения BLOB.

Вот краткое введение в C-функцию sqlite3_limit(), которую можно использовать для настройки мягких ограничений среды SQLite:

int sqlite3_limit( sqlite3 *db, int limit_type, int limit_value )
Для данного соединения с базой данных это устанавливает ограничение, на которое ссылается второй параметр, равным значению, указанному в третьем параметре. Возвращается старый лимит. Если новое значение отрицательное, предельное значение останется неизменным. Это можно использовать для проверки существующего лимита. Мягкий предел не может быть повышен выше жесткого предела, который устанавливается во время компиляции.

Для получения более подробной информации о sqlite3_limit() см. sqlite3_limit() в приложении G. Вам не нужно полностью понимать, как работает этот вызов API, чтобы понять эти примеры.

Хотя функция sqlite3_limit() является хорошим примером, возможно, это не та вещь, которую вы хотели бы использовать для языка SQL в реальном приложении. На практике раскрытие этого вызова C API на уровне SQL вызывает некоторые проблемы безопасности. Любой, кто может выполнять произвольные вызовы SQL, будет иметь возможность изменять мягкие ограничения SQLite. Это может быть использовано для некоторых типов атак типа «отказ в обслуживании» путем повышения или понижения пределов до крайних значений.

sql_set_limit

Чтобы вызвать функцию sqlite3_limit(), нам нужно определить параметры limit_type и value. Для этого потребуется функция SQL, которая принимает два параметра. Первым параметром будет тип ограничения, выраженный в виде текстовой константы. Второй параметр будет новым пределом. Функцию SQL можно вызвать следующим образом, чтобы установить новый предел глубины выражения:

SELECT sql_limit( 'EXPR_DEPTH', 400 );

Функция C, реализующая функцию SQL sql_limit(), состоит из четырех основных частей. Первая задача - проверить, является ли первый параметр функции SQL (переданный как values[0]) текстовым значением. Если это так, функция извлекает текст до указателя str:

static void sql_set_limit( sqlite3_context *ctx, int
                      num_values, sqlite3_value **values )
{
    sqlite3      *db = sqlite3_context_db_handle( ctx );
    const char   *str = NULL;
    int           limit = -1, val = -1, result = -1;

    /* verify the first param is a string and extract pointer */
    if ( sqlite3_value_type( values[0] ) == SQLITE_TEXT ) {
        str = (const char*) sqlite3_value_text( values[0] );
    } else {
    sqlite3_result_error( ctx, "sql_limit(): wrong parameter type", -1 );
    return;
}

Затем функция проверяет, что второй параметр SQL (values[1]) является целочисленным значением, и извлекает его в переменную val:

/* verify the second parameter is an integer and extract value */
if ( sqlite3_value_type( values[1] ) == SQLITE_INTEGER ) {
    val = sqlite3_value_int( values[1] );
} else {
    sqlite3_result_error( ctx, "sql_limit(): wrong parameter type", -1 );
    return;
}

Хотя наша функция SQL использует текстовое значение, чтобы указать, какой предел мы хотели бы изменить, для функции C sqlite3_limit() требуется предопределенное целочисленное значение. Нам нужно декодировать текстовое значение str в целочисленное предельное значение. Я покажу код для decode_limit_str() чуть позже:

/* translate string into integer limit */
limit = decode_limit_str( str );
if ( limit == -1 ) {
    sqlite3_result_error( ctx, "sql_limit(): unknown limit type", -1 );
    return;
}

После проверки двух параметров нашей функции SQL, извлечения их значений и перевода индикатора ограничения текста в правильное целочисленное значение мы, наконец, вызываем sqlite3_limit(). Результат устанавливается как значение результата функции SQL, и функция возвращает:

    /* call sqlite3_limit(), return result */
    result = sqlite3_limit( db, limit, val );
    sqlite3_result_int( ctx, result );
    return;
}

Функция decode_limit_str() очень проста и просто ищет предопределенный набор текстовых значений:

int decode_limit_str( const char *str )
{
    if ( str == NULL ) return -1;
    if ( !strcmp( str, "LENGTH"          ) ) return SQLITE_LIMIT_LENGTH;
    if ( !strcmp( str, "SQL_LENGTH"      ) ) return SQLITE_LIMIT_SQL_LENGTH;
    if ( !strcmp( str, "COLUMN"          ) ) return SQLITE_LIMIT_COLUMN;
    if ( !strcmp( str, "EXPR_DEPTH"      ) ) return SQLITE_LIMIT_EXPR_DEPTH;
    if ( !strcmp( str, "COMPOUND_SELECT" ) ) return SQLITE_LIMIT_COMPOUND_SELECT;
    if ( !strcmp( str, "VDBE_OP"         ) ) return SQLITE_LIMIT_VDBE_OP;
    if ( !strcmp( str, "FUNCTION_ARG"    ) ) return SQLITE_LIMIT_FUNCTION_ARG;
    if ( !strcmp( str, "ATTACHED"        ) ) return SQLITE_LIMIT_ATTACHED;
    if ( !strcmp( str, "LIKE_LENGTH"     ) ) return SQLITE_LIMIT_LIKE_PATTERN_LENGTH;
    if ( !strcmp( str, "VARIABLE_NUMBER" ) ) return SQLITE_LIMIT_VARIABLE_NUMBER;
    if ( !strcmp( str, "TRIGGER_DEPTH"   ) ) return SQLITE_LIMIT_TRIGGER_DEPTH;
    return -1;
}

Имея эти две функции, мы можем создать SQL-функцию sql_limit(), зарегистрировав указатель на C-функцию sql_set_limit().

sqlite3_create_function( db, "sql_limit", 2, SQLITE_UTF8,
                         NULL, sql_set_limit, NULL, NULL );

Параметры этой функции включают соединение с базой данных (db), имя функции SQL (sql_limit), необходимое количество параметров (2), ожидаемую кодировку текста (UTF-8), указатель данных пользователя (NULL) и, наконец, указатель функции C, реализующей эту функцию (sql_set_limit). Последние два параметра используются только при создании агрегатных функций и имеют значение NULL.

После создания функции SQL мы можем теперь управлять пределами нашей среды SQLite, вводя команды SQL. Вот несколько примеров того, как могла бы выглядеть SQL-функция sql_limit(), если бы мы интегрировали ее в инструмент sqlite3 (мы увидим, как это сделать с помощью загружаемого расширения, позже в этой главе).

Во-первых, мы можем найти текущий предел COLUMN, передав новое значение ограничения -1:

sqlite> SELECT sql_limit( 'COLUMN', -1 );
2000

Мы проверяем, что функция работает правильно, устанавливая максимальное ограничение столбца равным двум, а затем генерируя результат с тремя столбцами. Когда мы устанавливаем новое значение, возвращается предыдущее предельное значение:

sqlite> SELECT sql_limit( 'COLUMN', 2 );
2000
sqlite> SELECT 1, 2, 3;
Error: too many columns in result set

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

Одна вещь, которая может вас заинтересовать, - это количество значений параметра. Хотя функция sql_set_limit() тщательно проверяет типы параметров, на самом деле она не проверяет, что num_values равно двум. В этом случае это не обязательно, так как он был зарегистрирован с помощью sqlite3_create_function() с обязательным количеством параметров, равным двум. SQLite даже не будет вызывать нашу функцию sql_set_limit(), если у нас нет ровно двух параметров:

sqlite> SELECT sql_limit( 'COLUMN', 2000, 'extra' );
Error: wrong number of arguments to function sql_limit()

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

sql_get_limit

Хотя наличие фиксированного количества параметров упрощает код проверки, может быть полезно предоставить версию с одним параметром, которую можно использовать для поиска текущего значения. Это можно сделать несколькими способами. Во-первых, мы можем определить вторую функцию C, называемую sql_get_limit(). Эта функция будет такой же, как sql_set_limit(), но с удалением второго блока кода:

/* remove this block of code from a copy of */
/* sql_set_limit() to produce sql_get_limit() */
if ( sqlite3_value_type( values[1] ) == SQLITE_INTEGER ) {
    val = sqlite3_value_int( values[1] );
} else {
    sqlite3_result_error( ctx, "sql_limit(): wrong parameter type", -1 );
    return;
}

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

Регистрируем каждую из этих функций отдельно:

sqlite3_create_function( db, "sql_limit", 1,
        SQLITE_UTF8, NULL, sql_get_limit, NULL, NULL );
sqlite3_create_function( db, "sql_limit", 2,
        SQLITE_UTF8, NULL, sql_set_limit, NULL, NULL );

Эта двойная регистрация перегружает имя функции SQL sql_limit(). Перегрузка разрешена, потому что два вызова sqlite3_create_function() имеют разное количество требуемых параметров. Если функция SQL sql_limit() вызывается с одним параметром, то вызывается функция языка C sql_get_limit(). Если для функции SQL предоставлены два параметра, то вызывается функция C sql_set_limit().

sql_getset_limit

Хотя две функции C sql_get_limit() и sql_set_limit() обеспечивают правильную функциональность, большая часть их кода одинакова. Вместо того, чтобы иметь две функции, было бы проще объединить эти две функции в одну функцию, которая может работать с одним или двумя параметрами и способна как получать, так и устанавливать предельное значение.

Эту комбинированную функцию sql_getset_limit() можно создать, взяв исходную функцию sql_set_limit() и изменив второй раздел. Вместо того, чтобы устранять его, как мы это сделали при создании sql_get_limit(), мы просто заключим его в оператор if, поэтому второй раздел (который извлекает второй параметр функции SQL) запускается только в том случае, если у нас есть два параметра:

/* verify the second parameter is an integer and extract value */
if ( num_values == 2 ) {
    if ( sqlite3_value_type( values[1] ) == SQLITE_INTEGER ) {
        val = sqlite3_value_int( values[1] );
    } else {
        sqlite3_result_error( ctx, "sql_limit(): wrong parameter type", -1 );
        return;
    }
}

Мы регистрируем одну и ту же функцию sql_getset_limit() C для обоих счетчиков параметров:

sqlite3_create_function( db, "sql_limit", 1,
        SQLITE_UTF8, NULL, sql_getset_limit, NULL, NULL );
sqlite3_create_function( db, "sql_limit", 2,
        SQLITE_UTF8, NULL, sql_getset_limit, NULL, NULL );

Для этой конкретной задачи это, вероятно, лучший выбор. SQLite проверит, что функция SQL sql_limit() имеет ровно один или два параметра, прежде чем вызывать нашу функцию C, которая может легко справиться с любым из этих двух случаев.

sql_getset_var_limit

Если по какой-то причине вам не нравится идея регистрировать одну и ту же функцию дважды, мы также можем заставить SQLite игнорировать счетчик параметров и вызывать нашу функцию, несмотря ни на что. Это оставляет нам проверку правильности подсчета параметров. Для этого мы бы начали с функции sql_getset_limit() и изменили ее на sql_getset_var_limit(), добавив этот блок вверху функции:

if ( ( num_values < 1 )||( num_values > 2 ) ) {
    sqlite3_result_error( ctx, "sql_limit(): bad parameter count", -1 );
    return;
}

Регистрируем только одну версию. Передавая необходимое количество параметров, равное -1, мы сообщаем механизму SQLite, что готовы принять любое количество параметров:

sqlite3_create_function( db, "sql_limit", -1, SQLITE_UTF8,
        NULL, sql_getset_var_limit, NULL, NULL );

Хотя это работает, я по-прежнему предпочитаю версию sql_getset_limit(). Регистрация дает понять, какие версии функции считаются действительными, а код функции достаточно четкий и компактный.

Подсчет параметров полностью произвольной формы обычно используется такими элементами, как встроенная функция coalesce(). Функция coalesce() примет любое количество параметров (больше одного) и вернет первое значение в списке, отличное от NULL. Поскольку вы можете передать от двух до дюжины или более параметров, нецелесообразно регистрировать каждую возможную конфигурацию, и лучше просто позволить функции самостоятельно управлять параметрами.

С другой стороны, что-то вроде sql_getset_limit() действительно может принимать только две конфигурации: один или два параметра. В этом случае мне проще явно зарегистрировать оба счетчика параметров и позволить SQLite выполнить проверку моих параметров за меня.

Агрегатные функции

Агрегатные функции используются для сворачивания значений из группы строк в одно значение результата. Это можно сделать со всей таблицей, как это обычно бывает с агрегатной функцией count(*), или с группировкой строк из предложения GROUP BY, как это обычно бывает с чем-то вроде avg() или sum(). Агрегатные функции используются для суммирования или агрегирования всех значений отдельных строк в одно репрезентативное значение.

Определение агрегатов

Агрегатные функции SQL создаются с использованием той же функции sqlite3_create_function_xxx(), которая используется для создания скалярных функций (см. «Скалярные функции»). При определении скалярной функции вы передаете указатель функции C в шестом параметре и устанавливаете седьмой и восьмой параметр в NULL. При определении агрегатной функции шестой параметр устанавливается в NULL (указатель скалярной функции), а седьмой и восьмой параметры используются для передачи двух указателей функций C.

Первая функция C - это «пошаговая» функция. Она вызывается один раз для каждой строки в совокупной группе. Она действует аналогично скалярной функции, за исключением того, что не возвращает результата (однако может возвращать ошибку).

Вторая функция C - это функция «финализации». После того, как все строки SQL пройдены, вызывается функция finalize для вычисления и установки окончательного результата. Функция finalize не принимает никаких параметров SQL, но отвечает за установку значения результата.

Две функции C работают вместе, чтобы реализовать агрегатную функцию SQL. Рассмотрим встроенный агрегат avg(), который вычисляет среднее числовое значение всех строк в столбце. Каждый вызов функции step извлекает значение SQL для этой строки и обновляет как промежуточную сумму, так и количество строк. Функция finalize делит итог на количество строк и устанавливает значение результата агрегатной функции.

Функции C, используемые для реализации агрегата, определяются следующим образом:

void user_aggregate_step( sqlite3_context *ctx, int num_values, sqlite3_value **values )
Прототип определяемой пользователем агрегатной пошаговой функции. Эта функция вызывается один раз для каждой строки агрегированного вычисления. Прототип аналогичен скалярной функции, и все параметры имеют одинаковое значение. Функция step не должна устанавливать значение результата с помощью sqlite3_result_xxx(), но она может установить ошибку.
void user_aggregate_finalize( sqlite3_context *ctx )
Прототип определяемой пользователем функции завершения агрегата. Эта функция вызывается один раз в конце агрегации, чтобы произвести окончательный расчет и установить результат. Эта функция должна устанавливать значение результата или условие ошибки.

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

Также можно зарегистрировать как скалярную, так и агрегатную функции под одним и тем же именем, если количество параметров различается. Например, встроенные функции SQL min() и max() доступны как скалярные функции (с двумя параметрами), так и как агрегатные функции (с одним параметром).

Функции step и finalize можно смешивать и сопоставлять - они не всегда должны быть уникальными парами. Например, встроенные агрегаты sum() и avg() используют одну и ту же пошаговую функцию, поскольку оба агрегата должны вычислять промежуточную сумму. Единственное различие между этими агрегатами - это функция finalize. Функция finalize для sum() просто возвращает общий итог, а функция finalize для avg() сначала делит итог на количество строк.

Контекст агрегатов

Агрегатным функциям обычно требуется нести большое количество состояний. Например, встроенный агрегат avg() должен отслеживать промежуточную сумму, а также количество обработанных строк. Каждый вызов функции step, а также функции finalize требует доступа к некоторому общему блоку памяти, который содержит все значения состояния.

Хотя агрегатные функции могут вызывать sqlite3_user_data() или sqlite3_context_db_handle(), вы не можете использовать указатель пользовательских данных для хранения данных агрегированного состояния. Указатель пользовательских данных используется всеми экземплярами данной агрегатной функции. Если одновременно активны несколько экземпляров агрегатной функции (например, SQL-запрос, который усредняет более одного столбца), каждому экземпляру агрегата требуется частная копия данных агрегированного состояния или различные агрегированные вычисления будут перемешаны.

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

void* sqlite3_aggregate_context( sqlite3_context *ctx, int bytes )

Эта функция может быть вызвана внутри функции агрегированного шага или функции завершения. Первый параметр - это структура sqlite3_context, переданная в функцию step или finalize. Второй параметр представляет количество байтов.

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

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

Обычно одно из первых действий, выполняемых функцией step или finalize, - это вызов sqlite3_aggregate_context(). Например, рассмотрим эту упрощенную версию суммы:

void simple_sum_step( sqlite3_context *ctx, int num_values, sqlite3_value **values )
{
    double *total = (double*)sqlite3_aggregate_context( ctx, sizeof( double ) );
    *total += sqlite3_value_double( values[0] );
}

void simple_sum_final( sqlite3_context *ctx )
{
    double *total = (double*)sqlite3_aggregate_context( ctx, sizeof( double ) );
    sqlite3_result_double( ctx, *total );
}

/* ...inside an initialization function... */
    sqlite3_create_function( db, "simple_sum", 1, SQLITE_UTF8, NULL,
            NULL, simple_sum_step, simple_sum_final );

В этом случае мы выделяем достаточно памяти только для хранения значения с плавающей запятой двойной точности. Большинство агрегатных функций выделяют структуру C с любыми полями, необходимыми для вычисления агрегата, но все работает одинаково. При первом вызове simple_sum_step() вызов sqlite3_aggregate_context() выделит достаточно памяти для хранения значения double и обнулит его. Последующие вызовы simple_sum_step(), которые являются частью одного и того же вычисления агрегации (имеют тот же sqlite3_context), будут возвращать тот же блок памяти, что и simple_sum_final().

Поскольку sqlite3_aggregate_context() может потребоваться выделить память, также рекомендуется убедиться, что возвращаемое значение не равно NULL. Приведенный выше код в функциях step и finalize действительно должен выглядеть примерно так:

double *total = (double*)sqlite3_aggregate_context( ctx, sizeof( double ) );
if ( total == NULL ) {
    sqlite3_result_error_nomem( ctx );
    return;
}

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

Если состояние «все нули» по умолчанию для вновь выделенного контекста не подходит, и вам нужно каким-то образом инициализировать агрегированный контекст, вам нужно будет включить какой-либо тип флага инициализации. Например:

typedef struct agg_state_s {
    int    init_flag;
    /* other fields used by aggregate... */
} agg_state;

Агрегатные функции могут использовать этот флаг, чтобы определить, нужно ли инициализировать агрегированные данные контекста или нет:

agg_state *st = (agg_state*)sqlite3_aggregate_context( ctx, sizeof( agg_state ) );
/* ...return nonmem error if st == NULL... */
if ( st->init_flag == 0 ) {
    st->init_flag = 1;
    /* ...initialize the rest of agg_state... */
}

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

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

Пример

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

Говоря языком SQL, если наша функция wtavg() используется следующим образом:

SELECT wtavg( data, weight ) FROM ...

Результат должен быть примерно таким:

SELECT ( sum( data * weight ) / sum( weight ) ) FROM ...

Основное отличие состоит в том, что наша функция wtavg() должна быть немного более интеллектуальной в обработке недопустимых значений веса (например, NULL) и назначать им значение веса 1.0.

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

typedef struct wt_avg_state_s {
    double   total_data;  /* sum of (data * weight) values */
    double   total_wt;    /* sum of weight values */
} wt_avg_state;

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

В этом примере я сделал второй параметр агрегатной функции (значение веса) необязательным. Если указан только один параметр, предполагается, что все веса равны единице, что приводит к традиционному среднему значению. Однако она все равно будет отличаться от встроенной функции avg(). Встроенная функция SQLite avg() следует стандарту SQL в отношении ввода и обработки NULL, что может быть не тем, что вы сначала предполагали. (Подробнее см. avg() в приложении E). Наш wtavg() немного проще. Помимо того, что всегда возвращается значение double (даже если результат может быть выражен как целое число), он просто игнорирует любые значения, которые не могут быть легко преобразованы в число.

Во-первых, ступенчатая функция. Она обрабатывает каждую строку, складывая продукты с ценным весом, а также общее значение веса:

void wt_avg_step( sqlite3_context *ctx, int num_values, sqlite3_value **values )
{
    double        row_wt = 1.0;
    int           type;
    wt_avg_state  *st = (wt_avg_state*)sqlite3_aggregate_context( ctx,
                                         sizeof( wt_avg_state ) );
if ( st == NULL ) {
    sqlite3_result_error_nomem( ctx );
    return;
}

/* Extract weight, if we have a weight and it looks like a number */
if ( num_values == 2 ) {
    type = sqlite3_value_numeric_type( values[1] );
    if ( ( type == SQLITE_FLOAT )||( type == SQLITE_INTEGER ) ) {
        row_wt = sqlite3_value_double( values[1] );
    }
}

/* Extract data, if we were given something that looks like a number. */
type = sqlite3_value_numeric_type( values[0] );
if ( ( type == SQLITE_FLOAT )||( type == SQLITE_INTEGER ) ) {
    st->total_data += row_wt * sqlite3_value_double( values[0] );
    st->total_wt += row_wt;
}

Наша пошаговая функция использует sqlite3_value_numeric_type(), чтобы попытаться без потерь преобразовать значения параметров в числовой тип. Если преобразование возможно, мы всегда преобразуем значения в числа с плавающей запятой двойной точности, просто для простоты. Этот подход означает, что функция будет правильно работать с текстовыми представлениями чисел (такими как строка '153'), но будет игнорировать другие типы данных и другие строки.

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

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

void wt_avg_final( sqlite3_context *ctx )
{
    double        result = 0.0;
    wt_avg_state  *st = (wt_avg_state*)sqlite3_aggregate_context( ctx,
                                         sizeof( wt_avg_state ) );
    if ( st == NULL ) {
        sqlite3_result_error_nomem( ctx );
        return;
    }

    if ( st->total_wt != 0.0 ) {
        result = st->total_data / st->total_wt;
    }
    sqlite3_result_double( ctx, result );
}

Чтобы использовать наш агрегат, код нашего приложения должен зарегистрировать эти две функции с подключением к базе данных с помощью sqlite3_create_function(). Поскольку агрегат wtavg() предназначен для приема одного или двух параметров, мы зарегистрируем его дважды:

sqlite3_create_function( db, "wtavg", 1, SQLITE_UTF8, NULL,
        NULL, wt_avg_step, wt_avg_final );
sqlite3_create_function( db, "wtavg", 2, SQLITE_UTF8, NULL,
        NULL, wt_avg_step, wt_avg_final );

Вот несколько примеров запросов из командной оболочки sqlite3. Предполагается, что мы интегрировали наш настраиваемый агрегат в код sqlite3 (пример различных способов сделать это приведен ниже в этой главе):

sqlite> SELECT class, value, weight FROM t;

class       value       weight
----------  ----------  ----------
1           3.4         1.0
1           6.4         2.3
1           4.3         0.9
2           3.4         1.4
3           2.7         1.1
3           2.5         1.1

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

sqlite> SELECT class, wtavg( value ) AS wtavg, avg( value ) AS avg
   ...>   FROM t GROUP BY 1;

class       wtavg       avg
----------  ----------  ----------
1           4.7         4.7
2           3.4         3.4
3           2.6         2.6

И напоследок пример расчета полного средневзвешенного значения:

sqlite> SELECT class, wtavg( value, weight ) AS wtavg, avg( value ) AS avg
   ...>   FROM t GROUP BY 1;

class       wtavg             avg
----------  ----------------  ----------
1           5.23571428571428  4.7
2           3.4               3.4
3           2.6               2.6

В случае class = 1 мы видим явную разницу, где тяжелый вес 6.4 показывает среднее значение выше. Для class = 2 есть только одно значение, поэтому взвешенные и невзвешенные средние совпадают (само значение). В случае class = 3 веса одинаковы для всех значений, поэтому, опять же, среднее значение совпадает с невзвешенным средним.

Функции сопоставления

Сопоставления используются для сортировки текстовых значений. Их можно использовать с предложениями ORDER BY или GROUP BY или для определения индексов. Вы также можете назначить сопоставление столбцу таблицы, чтобы любая операция индексации или упорядочения, применяемая к этому столбцу, автоматически использовала определенное сопоставление. Помимо всего прочего, SQLite всегда будет сортировать по типу данных. Первыми всегда будут NULL, за которыми следует сочетание целочисленных и числовых значений с плавающей запятой в их естественном порядке сортировки. После чисел идут текстовые значения, за которыми следуют BLOB.

Большинство типов имеют четко определенный порядок сортировки. Типы NULL не имеют значений, поэтому их нельзя отсортировать. Числовые типы используют свой естественный числовой порядок, а BLOBы всегда сортируются с использованием двоичных сравнений. Что интересно, так это когда дело доходит до текстовых значений.

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

В дополнение к параметрам сортировки BINARY по умолчанию, SQLite включает встроенные параметры сортировки NOCASE и RTRIM, которые можно использовать с текстовыми значениями. Параметры сортировки NOCASE игнорируют регистр символов в целях сортировки 7-битного ASCII и будут рассматривать выражение 'A' == 'a' как истинное. Однако он не считает 'Ä' == 'ä' истинным и не считает 'Ä' == 'A' истинным, поскольку представления этих символов выходят за рамки стандарта ASCII. Параметры сортировки RTRIM (обрезка по правому краю) аналогичны параметрам сортировки BINARY по умолчанию, только игнорируют завершающие пробелы (то есть пробелы с правой стороны значения).

Хотя эти встроенные параметры сортировки предлагают некоторые основные параметры, бывают случаи, когда требуется сложный порядок сортировки. Это особенно верно, когда вы попадаете в представления Unicode для языков, которые не могут быть представлены с помощью простой 7-битной кодировки ASCII. Вам также может потребоваться специальная функция сортировки, которая сортирует по целым словам или группам символов, если вы храните что-то, кроме текста на естественном языке. Например, если вы храните последовательности генов в виде текстовых данных, вам может потребоваться пользовательская функция сортировки для этих данных.

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

Регистрация сопоставления

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

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

int sqlite3_create_collation( sqlite3 *db, const char *name, int text_rep, void *udp, comp_func ) int sqlite3_create_collation16( sqlite3 *db, const void *name, int text_rep, void *udp, comp_func )

Регистрирует функцию сравнения параметров сортировки с подключением к базе данных. Первый параметр - это соединение с базой данных. Второй параметр - это имя настраиваемого сопоставления, закодированного как строка UTF-8 или UTF-16. Третий параметр - это строковая кодировка, которую ожидает функция сравнения, и может быть одним из SQLITE_UTF8, SQLITE_UTF16, SQLITE_UTF16BE, SQLITE_UTF16LE или SQLITE_UTF16_ALIGNED (собственный UTF-16, выровненный по 16-битной памяти). Четвертый параметр - это общий указатель пользовательских данных, который передается вашей функции сравнения. Последний параметр - это указатель функции на вашу функцию сравнения (прототип этой функции приведен ниже).

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

int sqlite3_create_collation_v2( sqlite3 *db, const char *name, int text_rep, void *udp, comp_func, dest_func )

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

Сопоставление уничтожается при закрытии соединения с базой данных, при регистрации сопоставления замены или когда имя сопоставления очищается путем привязки указателя функции сравнения NULL.

В имени сортировки регистр не учитывается. SQLite позволяет регистрировать несколько функций сортировки C под одним именем, если они принимают разные текстовые представления. Если под одним именем доступно более одной функции сравнения, SQLite выберет ту, которая требует наименьшего количества преобразований. Если вы регистрируете более одной функции под одним и тем же именем, их логическое поведение сортировки должно быть одинаковым.

Формат указателей пользовательских функций приведен ниже.

int user_defined_collation_compare( void* udp, int lenA, const void *strA, int lenB, const void *strB )

Это тип функции определяемой пользователем функции сравнения параметров сортировки. Первый параметр - это указатель пользовательских данных, переданный в sqlite3_create_collation_xxx() в качестве четвертого параметра. Следующие параметры передают указатели длины и буфера для двух строк. Строки будут в любой кодировке, определенной функцией регистрации. Вы не можете предполагать, что строки заканчиваются нулем.

Возвращаемое значение должно быть отрицательным, если строка A меньше строки B (то есть, A сортирует до B), 0, если строки считаются равными, и положительным, если строка A больше, чем B (A сортирует после B). По сути, возвращаемое значение - это порядок A минус B.

void user_defined_collation_destroy( void *udp )
Это тип функции определяемой пользователем функции уничтожения сопоставления. Единственный параметр - это указатель пользовательских данных, переданный в качестве четвертого параметра sqlite3_cre ate_collation_v2().

Хотя функции сопоставления имеют доступ к указателю пользовательских данных, у них нет указателя sqlite3_context. Это означает, что нет никакого способа сообщить об ошибке движку SQLite. Таким образом, если у вас есть сложная функция сопоставления, вы должны попытаться устранить как можно больше источников ошибок. В частности, это означает, что рекомендуется заранее выделить любые рабочие буферы, которые могут вам понадобиться, поскольку нет возможности прервать сравнение, если распределение памяти не выполнено. Поскольку функция сопоставления на самом деле представляет собой простое сравнение, ожидается, что она будет работать и каждый раз давать ответ.

Сопоставления также могут быть динамически зарегистрированы по запросу. См. sqlite3_collation_needed() в приложении G для получения более подробной информации.

Пример сопоставления

Вот простой пример определяемой пользователем сортировки. В этом примере мы определяем параметры сортировки STRINGNUM, которые можно использовать для сортировки строковых представлений числовых значений.

Если они не одинаковой длины, строковые представления чисел часто сортируются странным образом. Например, используя стандартные правила сортировки текста, строка '485' будет отсортирована перед строкой '73', потому что символ '4' сортируется перед символом '7', так же, как символ 'D' сортируется перед символом 'G'. Чтобы было ясно, это текстовые строки, состоящие из символов, которые представляют собой цифры, а не фактические числа.

При сопоставлении предпринимается попытка преобразовать эти строки в числовое представление, а затем использовать это числовое значение для сортировки. Используя это сопоставление, строка '485' будет отсортирована после '73'. Для простоты мы будем иметь дело только с целочисленными значениями:

int col_str_num( void *udp,
    int lenA, const void *strA,
    int lenB, const void *strB )
{
    int valA = col_str_num_atoi_n( (const char*)strA, lenA );
    int valB = col_str_num_atoi_n( (const char*)strB, lenB );

    return valA - valB;
}

static int col_str_num_atoi_n( const char *str, int len )
{
    int total = 0, i;
    for ( i = 0; i < len; i++ ) {
        if ( ! isdigit( str[i] ) ) {
            break;
        }
        total *= 10;
        total += digittoint( str[i] );
    }
    return total;
}

Сопоставление пытается преобразовать каждую строку в целочисленное значение с помощью нашей пользовательской функции col_str_num_atoi_n(), а затем сравнивает числовые результаты. Функция col_str_num_atoi_n() очень похожа на стандартную функцию C atoi() с той разницей, что она принимает параметр максимальной длины. Это необходимо в данном случае, поскольку строки, переданные в наши сопоставления, могут не иметь завершающего нуля.

Мы бы зарегистрировали это сопоставление с помощью SQLite следующим образом:

sqlite3_create_collation( db, "STRINGNUM", SQLITE_UTF8, NULL, col_str_num );

Поскольку стандартная функция C isdigit() не поддерживает Unicode, наша функция сортировки с сопоставлением будет работать только со строками, ограниченными 7-битным ASCII.

Тогда у нас может быть SQL, который выглядит так:

sqlite> CREATE TABLE t ( s TEXT );
sqlite> INSERT INTO t VALUES ( '485' );
sqlite> INSERT INTO t VALUES ( '73' );
sqlite> SELECT s FROM t ORDER BY s;
485
73
sqlite> SELECT s FROM t ORDER BY s COLLATE STRINGNUM;
73
485

Также можно было бы навсегда связать нашу сортировку с определенным столбцом таблицы, включив сортировку в определение таблицы. См. CREATE TABLE в приложении C для более подробной информации.

Расширения SQLite

Хотя пользовательские функции являются очень мощной функцией, они также могут создавать нежелательные зависимости между файлами базы данных и пользовательскими средами SQLite. Если база данных использует настраиваемую сортировку в определении таблицы или настраиваемую функцию в определении представления, то эта база данных не может быть открыта никаким приложением (включая sqlite3), в котором не определены все надлежащие настраиваемые функции.

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

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

Решение состоит в том, чтобы создать свой собственный контент как расширение. Расширения бывают двух видов: статические и загружаемые (динамические). Разница в том, как расширение построено и связано с вашим основным приложением. Один и тот же источник можно использовать для создания как статического, так и загружаемого расширения.

Статические расширения можно создавать и связывать непосредственно с приложением, в отличие от статической библиотеки C. Загружаемые расширения действуют как внешние библиотеки или «плагины» для механизма SQLite. Если вы создаете свое расширение как внешнее загружаемое расширение, вы можете загрузить расширение (почти) в любую среду SQLite, сделав свои пользовательские функции и среду SQL доступными для sqlite3 или любого другого менеджера баз данных.

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

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

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

Архитектура расширений

Расширения - это не что иное, как стиль упаковки вашего кода. Вызовы API SQLite, используемые для регистрации и создания пользовательских обработчиков функций, агрегатных обработчиков, сопоставлений или других настраиваемых функций, полностью не изменяются в расширении. Единственное различие заключается в процессе инициализации, который создает и связывает ваши пользовательские функции C с подключением к базе данных. Процесс сборки также немного отличается, в зависимости от того, хотите ли вы создать статически связанное расширение или динамически загружаемое расширение, но оба типа расширений могут быть построены из одного и того же исходного кода.

Архитектура расширений направлена на обеспечение корректной работы динамически загружаемых расширений на нескольких платформах. Самая большая проблема для архитектуры динамических расширений - убедиться, что загружаемое расширение имеет доступ к SQLite API. Если не вдаваться в подробности того, как компоновщик среды выполнения работает в разных операционных системах, основная проблема заключается в том, что код, скомпилированный в расширение и загруженный во время выполнения, может быть не в состоянии разрешить зависимости ссылок из загружаемого расширения обратно в приложение, в котором находится библиотека SQLite.

Чтобы избежать этой проблемы, при инициализации расширения ему передается большая структура данных, которая содержит указатель функции C на каждую функцию в SQLite API. Вместо прямого вызова функций SQLite расширение будет разыменовывать требуемый указатель функции и использовать его. Это дает возможность разрешать любые вызовы библиотеки SQLite вне зависимости от компоновщика. Хотя это не полностью требуется для статического расширения, механизм одинаково хорошо работает как со статическими, так и с динамическими расширениями.

К счастью, детали того, как работает эта большая структура данных, хорошо скрыты от разработчика с помощью альтернативного файла заголовка и нескольких макросов препроцессора. Эти макросы полностью скрывают всю проблему компоновщика и указателя на функцию, но с одним ограничением: весь код расширения, который выполняет вызовы в API SQLite, должен находиться в одном файле вместе с функцией инициализации расширения. Этот код может обращаться к другим файлам и другим библиотекам, пока этот «другой код» не выполняет никаких прямых вызовов какой-либо функции sqlite3_xxx().

Чтобы расширение SQLite работало правильно, каждая функция, которая взаимодействует с библиотекой SQLite, должна находиться в том же исходном файле C, что и функция инициализации.

На практике это редко бывает значительным ограничением. Хранение ваших пользовательских расширений SQLite в их собственных файлах, вне кода вашего приложения, - это естественный способ организовать ваш код. Большинство расширений SQLite состоят из нескольких сотен строк или меньше, особенно если они просто действуют как связующий слой между SQLite и какой-либо другой библиотекой. Это может сделать их большими, но обычно не такими большими, что они становятся неуправляемыми как единый файл.

Дизайн расширения

Чтобы написать расширение, нам нужно использовать файл заголовка расширения. Вместо более распространенного файла sqlite.h расширение использует файл sqlite3ext.h:

#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1;  /* required by SQLite extension header */

Заголовок расширения SQLite определяет два макроса. Первым из них является SQLITE_EXTENSION_INIT1, и на него следует ссылаться в верхней части файла C, содержащего источник расширения. Этот макрос определяет переменную в файловой области, которая содержит указатель на большую структуру API.

Каждое расширение должно определять точку входа. Это действует как функция инициализации расширения. Функция точки входа выглядит так:

int ext_entry_point( sqlite3 *db, char **error, const sqlite3_api_routines *api )

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

Эта функция вызывается механизмом SQLite при загрузке статического или динамического расширения. Обычно эта функция создает и регистрирует любые настраиваемые функции или другие настраиваемые расширения с подключением к базе данных.

Точка входа выполняет две основные задачи. Первое задание - завершить процесс инициализации, вызвав второй макрос расширения. Это должно быть сделано в качестве первого бита кода в точке входа (макрос разворачивается в строку кода, поэтому, если вы работаете на чистом C, вам нужно будет поместить любые переменные области действия перед макросом инициализации). Это необходимо сделать до того, как будут выполнены какие-либо вызовы sqlite3_xxx(), иначе приложение выйдет из строя:

int ext_init( sqlite3 *db, char **error, const sqlite3_api_routines *api )
{
    /* local variable definitions */
    SQLITE_EXTENSION_INIT2(api);
    /* ... */
}

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

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

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

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

В результате принято называть точку входа чем-то уникальным для расширения, но довольно легко документируемым и запоминающимся. Точка входа часто имеет то же имя, что и само расширение, возможно, с суффиксом _init. Пример расширения, который мы рассмотрим, называется sql_trig, поэтому хорошим выбором для точки входа будет sql_trig() или sql_trig_init().

Пример расширения: sql_trig

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

Первая половина нашего исходного файла sql_trig.c содержит две функции, которые мы будем определять в нашем примере расширения. Сами функции довольно просты: они извлекают одно число с плавающей запятой двойной точности, конвертируют градусы в радианы, а затем возвращают результат из математической библиотеки. Я также показал верхнюю часть файла с необходимыми операторами #include и макросами инициализации:

/* sql_trig.c */

#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1;

#include <stdlib.h>

/* this bit is required to get M_PI out of MS headers */
#if defined( _WIN32 )
#define _USE_MATH_DEFINES
#endif /* _WIN32 */

#include <math.h>

static void sql_trig_sin( sqlite3_context *ctx, int num_values, sqlite3_value **values )
{
    double a = sqlite3_value_double( values[0] );
    a = ( a / 180.0 ) * M_PI;   /* convert from degrees to radians */
    sqlite3_result_double( ctx, sin( a ) );
}

static void sql_trig_cos( sqlite3_context *ctx, int num_values, sqlite3_value **values )
{
    double a = sqlite3_value_double( values[0] );
    a = ( a / 180.0 ) * M_PI;   /* convert from degrees to radians */
    sqlite3_result_double( ctx, cos( a ) );
}

Вы заметите, что они объявлены как static функции. Если сделать их static, они будут скрыты от компоновщика, что устранит любые возможные конфликты имен между этим расширением и другими расширениями. Пока точка входа расширения находится в том же файле (который, как мы уже обсуждали, требуется по другим причинам), точка входа по-прежнему сможет правильно зарегистрировать эти функции. Объявление этих функций статическими не является строго обязательным, но это хорошая практика и может устранить конфликты связывания.

Затем нам нужно определить нашу точку входа. Вот вторая часть файла sql_trig.c:

int sql_trig_init( sqlite3 *db, char **error, const sqlite3_api_routines *api )
{
    SQLITE_EXTENSION_INIT2(api);

    sqlite3_create_function( db, "sin", 1,
            SQLITE_UTF8, NULL, sql_sin, NULL, NULL );
    sqlite3_create_function( db, "cos", 1,
            SQLITE_UTF8, NULL, sql_cos, NULL, NULL );

    return SQLITE_OK;
}

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

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

Сборка и интеграция статических расширений

Чтобы статически связать расширение с приложением, вы можете просто встроить исходный файл расширения в приложение, как и любой другой файл .c. Если код вашего приложения содержался в файле application.c, вы могли бы создать и связать наш пример расширения sql_trig с помощью команд, показанных здесь.

В случае большинства систем Linux, Unix и Mac OS X наш пример триггера требует, чтобы мы явно связали математическую библиотеку (libm). В некоторых случаях также требуется стандартная библиотека C (libc). Windows включает математические функции в стандартные библиотеки времени выполнения, поэтому связывание в математической библиотеке не требуется.

Системы Unix и Mac OS X (с математической библиотекой):

$ gcc -o application application.c sqlite3.c sql_trig.c -lm

Системы Windows, использующие компилятор Visual Studio:

> cl /Feapplication application.c sqlite3.c sql_trig.c

Эти команды должны создать исполняемый файл с именем application (или application.exe в Windows).

Простое объединение кода вместе не позволяет волшебным образом интегрировать его в SQLite. Вам по-прежнему необходимо, чтобы SQLite знал о расширении, чтобы библиотека SQLite могла правильно инициализировать расширение:

int sqlite3_auto_extension( entry_point_function );

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

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

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

Единственная странность в sqlite3_auto_extension() - это объявление функции точки входа. Вызов API автоматического расширения объявляет, что указатель функции имеет тип void entry_point (void). Это определяет функцию, которая не принимает параметров и не возвращает значения. Как мы уже видели, фактическая точка входа в расширение имеет немного более сложный прототип.

Код, который фактически вызывает расширение, сначала приводит предоставленный указатель функции к правильному типу, поэтому тот факт, что типы не совпадают, является проблемой только для установки указателя. Расширения обычно не имеют файлов заголовков, поскольку функция точки входа обычно является единственным элементом заголовка. Чтобы все заработало, вы можете либо предоставить правильный прототип для точки входа, а затем привести обратно к тому, что ожидает API, либо вы можете просто неправильно объявить прототип функции и позволить компоновщику согласовать вещи. Чистый C не проверяет типы параметров функции при связывании, поэтому это будет работать, даже если это не самый элегантный подход.

Вот как может выглядеть правильный прототип с приведением в коде нашего приложения:

/* declare the (correct) function prototype manually */
int sql_trig_init( sqlite3 *db, char **error, const sqlite3_api_routines *api );

/* ... */
    sqlite3_auto_extension( (void(*)(void))sql_trig_init ); /* needs cast */
/* ... */

Или, если вы работаете на чистом C, вы можете просто объявить другой прототип:

/* declare the (wrong) function prototype manually */
void sql_trig_init(void);
/* ... */
    sqlite3_auto_extension( sql_trig_init );
/* ... */

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

Если вам нужен быстрый практический пример того, как добавить статическое расширение к существующему приложению, мы можем добавить наше расширение sql_trig в оболочку sqlite3 с минимальным количеством изменений. Нам понадобится наш файл sql_trig.c, который содержит две триггерные функции SQL, а также функцию ввода sql_trig_init(). Нам также понадобится исходный код shell.c для приложения командной строки sqlite3.

Во-первых, нам нужно добавить несколько хуков инициализации в исходный код sqlite3. Сделайте копию файла shell.c как shell_trig.c. Откройте новую копию и найдите фразу «int main (», чтобы быстро найти начальную точку приложения. Прямо перед основной функцией в области глобального файла добавьте прототип для нашей точки входа sql_trig_init():

/* ... */
void sql_trig_init(void);  /* insert this line */

int main(int argc, char **argv){
/* ... */

Затем внутри существующей функции main() найдите вызов «open_db (», чтобы найти подходящее место для вставки нашего кода. Прямо перед небольшим блоком кода (и комментариями), содержащим первый вызов open_db(), добавьте эту строку:

sqlite3_auto_extension( sql_trig_init );

С этими двумя изменениями вы можете сохранить и закрыть файл shell_trig.c. Затем мы можем перекомпилировать наш модифицированный исходный код shell_trig.c в специальную утилиту sqlite3trig, в которую встроено наше расширение.

Unix/Linux и Mac OS X:

$ gcc -o sqlite3trig sqlite3.c shell_trig.c sql_trig.c -lm

Windows:

> cl /Fesqlite3trig sqlite3.c shell_trig.c sql_trig.c

В наше новое приложение sqlite3trig теперь встроено наше расширение, и наши функции доступны из любой базы данных, которая открывается с помощью нашей модифицированной утилиты sqlite3trig:

$ ./sqlite3trig
SQLite version 3.X.XX
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> SELECT sin( 30 );
0.5
sqlite> SELECT cos( 30 );
0.866025403784439

Хотя нам пришлось изменить исходный код, изменения были довольно небольшими. Хотя нам нужно было изменить и перекомпилировать основное приложение (в данном случае sqlite3trig) для интеграции расширения, вы можете видеть, насколько легко было бы добавить дополнительные расширения.

Использование загружаемых расширений

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

Загружаемые файлы расширений в основном представляют собой общие библиотеки в любом формате, подходящем для платформы. Загружаемые расширения упаковывают скомпилированный код в формат, который операционная система может загрузить и связать с вашим приложением во время выполнения. Таблица 9-1 содержит сводку соответствующих форматов файлов на разных платформах.

Таблица 9-1. Сводка загружаемого формата файла расширения

Платформа Тип файла Расширение файла по умолчанию
Linux и большинство Unix Общие объектные файлы .so
Mac OS X Динамическая библиотека .dylib
Windows Динамически подключаемая библиотека .DLL

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

Хотя форматы файлов и расширения зависят от платформы, нередко можно выбрать собственное расширение файла, которое используется на всех поддерживаемых вами платформах. Использование общего расширения файла не требуется, но оно может упростить кроссплатформенный код C или SQL, который отвечает за загрузку расширений. Как и файлы базы данных, для загружаемого расширения SQLite нет официального расширения, но иногда используется .sqlite3ext. Это то, что я буду использовать в наших примерах.

Создание загружаемых расширений

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

Mac OS X и Unix / Linux:

$ gcc -c sql_trig.c

Windows:

> cl /c sql_trig.c

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

Во-первых, команда Unix и Linux, которая создает общий объектный файл и связывает его со стандартной математической библиотекой:

$ ld -shared -o sql_trig.sqlite3ext sql_trig.o -lm

Mac OS X, в которой используются динамические библиотеки, а не файлы общих объектов:

$ ld -dylib -o sql_trig.sqlite3ext sql_trig.o -lm

И, наконец, Windows, где нам нужно создать файл DLL. В этом случае нам нужно указать компоновщику, какие символы мы хотим экспортировать. Для расширения нужно экспортировать только точку входа, поэтому мы просто включаем ее в командную строку:

> link /dll /out:sql_trig.sqlite3ext /export:sql_trig_init sql_trig.obj

Вы можете протестировать свое динамическое расширение в sqlite3 с помощью команды .load. Команда принимает два параметра. Первый - это имя файла вашего загружаемого расширения, а второй - имя функции точки входа:

$ sqlite3
SQLite version 3.X.XX
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> SELECT sin( 60 );
Error: no such function: sin
sqlite> .load sql_trig.sqlite3ext sql_trig_init
sqlite> SELECT sin( 60 );
0.866025403784439

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

Безопасность загружаемых расширений

Есть некоторые незначительные проблемы безопасности, связанные с загружаемыми расширениями. Поскольку расширение может содержать практически любой код, загружаемое расширение может использоваться для переопределения значений приложения для среды SQLite. В частности, если приложение использует функцию авторизации для защиты от определенных типов запросов или модификаций, загружаемое расширение может очистить функцию обратного вызова авторизации, исключив любой шаг авторизации (подробнее см. sqlite3_set_authorizer() в приложении G).

Чтобы предотвратить эту и другие возможные проблемы, приложение должно явно разрешить возможность загрузки внешних расширений. Это нужно делать каждый раз, когда устанавливается соединение с базой данных.

int sqlite3_enable_load_extension( sqlite3 *db, int onoff )

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

Первый параметр - это устанавливаемое соединение с базой данных. Второй параметр должен иметь значение true (ненулевое значение), чтобы включить расширения, или false (ноль), чтобы отключить их. Эта функция всегда возвращает SQLITE_OK.

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

Загрузка загружаемых расширений

Есть два способа загрузить расширение. Один - через вызов C API, а другой - через функцию SQL, которая вызывает тот же код, что и функция C API. В обоих случаях вы указываете имя файла и, необязательно, имя функции точки входа.

int sqlite3_load_extension( sqlite3 *db, const char *ext_name, const char *entry_point, char **error )

Пытается загрузить загружаемое расширение и связать его с данным подключением к базе данных. Первый параметр - это соединение с базой данных, которое нужно связать с этим расширением. Второй параметр - это имя файла расширения. Третий параметр - это имя функции точки входа. Если имя точки входа NULL, используется точка входа sqlite3_extension_init. Четвертый параметр используется для передачи сообщения об ошибке, если что-то пойдет не так. Этот строковый буфер должен быть освобожден с помощью sqlite3_free(). Этот последний параметр является необязательным и может иметь значение NULL.

Это вернет либо SQLITE_OK, чтобы указать, что расширение было загружено и функция инициализации была успешно вызвана, либо он может вернуть SQLITE_ERROR, чтобы указать, что что-то пошло не так. Если возвращается условие ошибки, может быть или не быть допустимой строки ошибки.

Эта функция обычно вызывается, как только открывается соединение с базой данных, до подготовки каких-либо операторов. Хотя вызов sqlite3_load_extension() в любое время является законным, любые вызовы API, сделанные точкой входа расширения и функцией инициализации, подчиняются стандартным ограничениям. В частности, это означает, что любые вызовы sqlite3_create_function(), сделанные функцией точки входа расширения, не смогут переопределить или удалить функцию, если есть какие-либо выполняющиеся операторы SQL.

Другой способ загрузить загружаемое расширение - использовать встроенную функцию SQL load_extension().

load_extension( 'ext_name' ) load_extension( 'ext_name', 'entry_point' )
Эта функция SQL загружает расширение с заданным именем файла. Если указано имя точки входа, оно используется как функция инициализации. В противном случае будет использовано имя sqlite3_extension_init.

Эта функция аналогична вызову sqlite3_load_extension() в языке C, но с одним существенным ограничением. Поскольку это функция SQL, при ее вызове по определению будет выполняться инструкция SQL при загрузке расширения. Это означает, что любое расширение, загруженное функцией SQL load_extension(), не сможет переопределить или удалить пользовательскую функцию, включая специализированный набор функций like().

Чтобы избежать этой проблемы при тестировании загружаемых расширений в оболочке sqlite3, используйте команду .load. Это обеспечивает прямой доступ к вызову C API, позволяя обойти ограничения функции SQL. См. .load в приложении B для более подробной информации.

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

Единственный способ полностью выгрузить загружаемое расширение - закрыть соединение с базой данных.

Несколько точек входа

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

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

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

Даже если ваше расширение невелико и на самом деле не оправдывает множественные точки входа, второе может быть удобно. Некоторые расширения определяют «чистую» точку входа, например, sql_trig_clear(). Обычно это очень похоже на функцию точки входа _init(), но вместо того, чтобы связывать все указатели функций в соединение с базой данных, она будет связывать все указатели NULL. Это эффективно «выгружает» расширение из среды SQL или, по крайней мере, удаляет все созданные им функции. Файл расширения может все еще находиться в памяти, но функции SQL больше не доступны для этого соединения с базой данных. Единственное, что следует помнить о точке входа _clear(), - это то, что ее нельзя вызвать во время выполнения оператора SQL из-за правил переопределения/удаления для таких функций, как sqlite3_create_function().

Краткое содержание главы

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

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

Глава 10
Виртуальные таблицы и модули

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

Разработка реализации виртуальной таблицы, известной как модуль SQLite, - довольно продвинутая функция. Эта глава должна дать вам хорошее представление о том, на что способны виртуальные таблицы, и основы написания собственного модуля. Мы рассмотрим код двух разных модулей. Первый довольно простой, он предоставляет некоторые внутренние данные SQLite в виде таблицы. Во втором примере будет разрешен доступ только для чтения к стандартным журналам сервера Apache httpd.

Эта глава должна стать хорошей отправной точкой. Однако, если вы обнаружите, что вам нужно написать более надежный модуль, вам, возможно, придется немного углубиться в документацию по разработке, которую можно найти по адресу http://www.sqlite.org/vtab.html. Я также предлагаю взглянуть на исходный код некоторых других модулей (включая те, которые поставляются с SQLite), чтобы лучше понять, как работают расширенные функции. Модули достаточно сложные, поэтому иногда проще изменить существующий модуль, чем реализовывать все с нуля.

Как и в предыдущей главе, этот тип продвинутого развития лучше всего изучать вручную. Чтобы помочь в этом, полный исходный код обоих примеров доступен в книге для загрузки. См. «Пример загрузки кода» для получения более подробной информации.

Введение в модули

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

Внутренние модули

Внутренние модули автономны в базе данных. То есть виртуальная таблица действует как причудливый интерфейс для более традиционных таблиц базы данных, которые создаются и обслуживаются модулем виртуальной таблицы. Эти внутренние таблицы иногда называют теневыми таблицами (shadow tables). Что наиболее важно, все данные, используемые модулем, по-прежнему хранятся в файле базы данных. Эти типы модулей обычно предоставляют специализированный тип индексирования или функции поиска, который плохо подходит для собственных индексов базы данных. Для эффективной работы внутренних виртуальных таблиц может потребоваться несколько теневых таблиц.

Два самых больших модуля, включенных в дистрибутив SQLite (FTS3 и R*Trees), являются модулями внутреннего стиля. Оба этих модуля создают и настраивают несколько стандартных таблиц для хранения и индексирования данных, которые им было предложено поддерживать.

Обычно внутренние модули используются для улучшения или расширения возможностей обработки данных в базе данных. В большинстве случаев внутренняя виртуальная таблица не делает того, что разработчик SQL не мог бы сделать самостоятельно, модуль просто упрощает или ускоряет работу (или и то, и другое). Внутренние модули часто играют роль абстрактного «интеллектуального представления», которое предлагает оптимизированные шаблоны доступа к определенным типам данных или определенным структурам данных. И модуль полнотекстового поиска, и модуль R*Tree являются яркими примерами модулей, обеспечивающих узкоспециализированный поиск по определенным типам и структурам данных.

Внешние модули

Другая основная категория модулей - внешние модули. Это модули, которые взаимодействуют с некоторыми типами внешних источников данных. Этот источник данных может быть таким же простым, как внешний файл. Например, модуль может предоставить файл CSV или Excel в виде таблицы SQL в базе данных. Таким образом можно открыть практически любой структурированный файл. Внешний модуль также может использоваться для представления других источников данных механизму базы данных SQLite. Фактически вы могли бы написать модуль SQLite, который предоставлял бы таблицы из базы данных MySQL механизму базы данных SQLite. Или, для чего-то более необычного, запросите SELECT ip FROM dns WHERE hostname = 'www.oreilly.com' и обработайте DNS-запрос. Внешние модули могут получиться довольно экзотическими.

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

Конечно, вы можете использовать внешний модуль в качестве импортера, скопировав данные из виртуальной таблицы в стандартную таблицу с помощью оператора INSERT...SELECT. Если модуль имеет полную поддержку чтения/записи, вы даже можете использовать его в качестве экспортера, копируя данные из базы данных в виртуальную таблицу. Используя эту технику, я видел случаи, когда SQLite использовался как «универсальный переводчик» для нескольких различных форматов внешних данных. Написав модуль виртуальной таблицы, который может взаимодействовать с каждым форматом файла, вы можете легко и быстро перемещать данные между поддерживаемыми форматами.

Примеры модулей

Чтобы объяснить, как работают модули, мы рассмотрим два примера. Первый пример - это очень простой внутренний модуль, который представляет вывод команды PRAGMA database_list в виде полноценной таблицы. Это позволяет выполнять запросы SELECT (включая ограничения WHERE) для текущего списка базы данных. Хотя этот модуль предназначен только для чтения и чрезвычайно прост, он должен послужить хорошим первым введением в модульную систему.

Второй пример более подробный. Мы рассмотрим создание внешнего модуля, который будет предоставлять журналы httpd-сервера Apache механизму базы данных. Это позволяет веб-мастеру запускать SQL-запросы непосредственно к файлу журнала (включая активный файл журнала) без необходимости сначала импортировать данные в традиционную таблицу базы данных.

SQL для чего угодно

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

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

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

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

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

Модуль API

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

Однако эта улица идет в два направления. В то время как ядро SQLite ожидает от вас выполнения своей работы, оно всегда выполняет свою работу предсказуемым и задокументированным образом. Большая часть этого кода работает довольно глубоко в ядре SQLite, и SQLite выполняет серьезную работу по защите вашего кода от странного поведения пользователя. Например, ни один из примеров кода не проверяет значения параметра NULL, так как вы можете быть уверены, что SQLite никогда не позволит передать указатель базы данных NULL (или какой-либо не менее важный параметр) в вашу функцию.

Реализация модуля виртуальной таблицы немного похожа на разработку агрегатной функции, только намного сложнее. Вы должны написать серию функций, которые вместе определяют поведение модуля. Затем этот блок функций регистрируется под именем модуля.

int sqlite3_create_module( sqlite3 *db, const char *name, const sqlite3_module *module, void *udp )
Создает и регистрирует модуль виртуальной таблицы с подключением к базе данных. Второй параметр - это имя модуля. Третий параметр - это блок указателей на функции, реализующий виртуальную таблицу. Этот указатель должен оставаться действительным, пока библиотека SQLite не будет закрыта. Последний параметр - это общий указатель пользовательских данных, который передается некоторым функциям модуля.
int sqlite3_create_module_v2( sqlite3 *db, const char *name, const sqlite3_module *p, void *udp, destroy_callback )
Версия этой функции v2 идентична исходной функции, за исключением дополнительного пятого параметра. Эта версия добавляет уничтожение обратного вызова в форме void callback(void *udp). Эта функция может использоваться для освобождения или иной очистки указателя пользовательских данных и вызывается, когда весь модуль выгружается. Это делается при выключении базы данных или при регистрации нового модуля с тем же именем вместо этого. Указатель на функцию уничтожения является необязательным и может иметь значение NULL.

Указатели функций передаются через структуру sqlite3_module. Основная причина этого в том, что существует около 20 функций, которые определяют виртуальную таблицу. Все эти функции, кроме некоторых, являются обязательными.

Модуль определяет конкретный тип виртуальной таблицы. После успешной регистрации модуля необходимо создать фактический экземпляр таблицы этого типа с помощью команды SQL CREATE VIRTUAL TABLE. В одной базе данных может быть несколько экземпляров виртуальных таблиц одного типа. Одна база данных может также иметь разные типы виртуальных таблиц, если все модули правильно зарегистрированы.

Синтаксис команды CREATE VIRTUAL TABLE выглядит примерно так:

CREATE VIRTUAL TABLE table_name USING module_name( arg1, arg2, ... )

Виртуальная таблица имеет имя, как и любая другая таблица. Чтобы определить таблицу, вы должны указать имя модуля и любые аргументы, которые требуются модулю. Блок аргументов является необязательным, и точное значение аргументов зависит от реализации отдельных модулей. Модуль отвечает за определение фактической структуры (имен и типов столбцов) таблицы. Аргументы не имеют предопределенной структуры и не обязательно должны быть действительными выражениями SQL или определениями столбцов. Каждый аргумент передается модулю как буквальное текстовое значение, с обрезкой только начального и конечного пробелов. Все остальное, включая пробелы в аргументе, передается как одно текстовое значение.

Вот краткий обзор различных функций, которые определяются структурой sqlite3_module. Когда мы рассмотрим примеры модулей, мы вернемся к ним по очереди более подробно. Функции модуля разделены на три грубые группы. Первый набор функций работает с экземплярами таблиц. Второй набор включает функции, которые просматривают таблицу и возвращают значения данных. Последняя группа функций связана с реализацией управления транзакциями. Чтобы реализовать модуль виртуальной таблицы, вам нужно будет написать функцию C, которая выполняет каждую из этих задач.

Функции, которые работают с отдельными экземплярами таблиц, включают:

xCreate()
Обязательная. Вызывается при первом создании экземпляра виртуальной таблицы с помощью команды CREATE VIRTUAL TABLE.
xConnect()
Обязательная, но часто то же самое, что и xCreate(). Очень похожа на xCreate(), она вызывается, когда загружается база данных с существующим экземпляром виртуальной таблицы. Вызывается один раз для каждого экземпляра таблицы.
xDisconnect()
Обязательная. Вызывается, когда база данных, содержащая экземпляр виртуальной таблицы, отключается или закрывается. Вызывается один раз для каждого экземпляра таблицы.
xDestroy()
Обязательная, но часто то же самое, что и xDisconnect(). Очень похожа на xDisconnect(), она вызывается, когда экземпляр виртуальной таблицы уничтожается с помощью команды DROP TABLE.
xBestIndex()
Обязательная. Вызывается, иногда несколько раз, когда ядро базы данных готовит инструкцию SQL, которая включает виртуальную таблицу. Эта функция используется, чтобы определить, как лучше всего оптимизировать поиск и запросы, выполняемые по таблице. Эта информация помогает оптимизатору понять, как добиться максимальной производительности из таблицы.
xUpdate()
По желанию. Вызывается для изменения (INSERT, UPDATE или DELETE) строки таблицы. Если эта функция не определена, виртуальная таблица будет доступна только для чтения.
xFindFunction()
По желанию. Вызывается при подготовке оператора SQL, который использует значения виртуальной таблицы в качестве параметров функции SQL. Эта функция позволяет модулю отменять реализацию по умолчанию любой функции SQL. Обычно она используется вместе с функциями SQL like() или match(), для определения специфичных для модуля версий этих функций (и, следовательно, специфичных для модуля версий выражений SQL LIKE и MATCH).
xRename()
Обязательная. Вызывается при переименовании виртуальной таблицы с помощью команды ALTER TABLE...RENAME.

Вторая группа функций связана с обработкой сканирования таблиц. Эти функции работают с табличным курсором (table cursor), который содержит всю информацию о состоянии, необходимую для выполнения сканирования таблицы. Когда ядро базы данных сканирует таблицу и проходит через каждую отдельную строку, курсор отвечает за отслеживание того, какая строка обрабатывается.

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

Функции курсора включают:

xOpen()
Обязательная. Вызывается для создания и инициализации табличного курсора.
xClose()
Обязательная. Вызывается для выключения и освобождения курсора таблицы.
xFilter()
Обязательная. Вызывается для запуска сканирования таблицы и предоставления информации о любых конкретных условиях, наложенных на это конкретное сканирование таблицы. Условия обычно исходят из ограничений WHERE запроса. Функция xFilter() разработана для работы вместе с xBestIndex(), чтобы позволить виртуальной таблице предварительно фильтровать как можно больше строк. После подготовки модуля к сканированию таблицы xFilter() также должен найти первую строку. Это может быть вызвано более одного раза между xOpen() и xClose().
xNext()
Обязательная. Вызывается для перемещения курсора таблицы к следующей строке.
xEof()
Обязательная. Вызывается, чтобы узнать, достиг ли курсор таблицы конца таблицы. EOF - это традиционное сокращение для конца файла (end-of-file). Эта функция всегда вызывается сразу после вызова xFilter() или xNext().
xRowid()
Обязательная. Вызывается для извлечения виртуального ROWID текущей строки.
xColumn()
Обязательная. Вызывается для извлечения значения столбца для текущей строки. Обычно вызывается несколько раз для каждой строки.

Наконец, у нас есть функции управления транзакциями. Они позволяют внешним источникам данных участвовать в процессе управления транзакциями и включают:

xBegin()
По желанию. Вызывается при запуске транзакции.
xSync()
По желанию. Вызывается для начала совершения транзакции.
xCommit()
По желанию. Вызывается для завершения транзакции базы данных.
xRollback()
По желанию. Вызывается для отката транзакции базы данных.

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

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

Простой пример: модуль dblist

В первом примере вывод команды PRAGMA database_list представлен в виде таблицы. Поскольку выходные данные команды PRAGMA уже находятся в той же структуре, что и таблица, это преобразование довольно просто. Основная причина для этого - использовать полный синтаксис SELECT, включая условия WHERE, для виртуальной таблицы. Это невозможно с командой PRAGMA.

Команда PRAGMA database_list обычно возвращает три столбца: seq, name и file. Столбец seq - это значение последовательности, указывающее, к какому «слоту» присоединена база данных. Столбец name - это логическое имя базы данных, такое как main или temp, или любое другое имя, присвоенное команде ATTACH DATABASE. (См. ATTACH DATABASE в приложении C). В столбце file отображается полный путь к файлу базы данных, если такой файл существует. Например, базы данных в памяти не имеют связанных имен файлов.

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

Создать и подключиться

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

int xCreate( sqlite3 *db, void *udp, int argc, char **argv, sqlite3_vtab **vtab, char **errMsg )

Обязательная. Эта функция вызывается SQLite в ответ на команду CREATE VIRTUAL TABLE. Эта функция создает новый экземпляр виртуальной таблицы и инициализирует все необходимые структуры данных и объекты базы данных.

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

Если все работает как запланировано, эта функция возвращает SQLITE_OK. Если код возврата отличается от SQLITE_OK, структура vtab не должна выделяться.

Каждому модулю передается не менее трех аргументов. Переменная argv[0] всегда будет содержать имя модуля, используемого для создания виртуальной таблицы. Это позволяет использовать одну и ту же функцию xCreate() с аналогичными модулями. Логическое имя базы данных (main, temp и т. д.) передается как argc[1], а argv[2] содержит имя таблицы, предоставленное пользователем. Любые дополнительные аргументы, переданные оператору CREATE VIRTUAL TABLE, будут передаваться, начиная с argv[3], как текстовые значения.

int xConnect( sqlite3 *db, void *udp, int argc, char **argv, sqlite3_vtab **vtab, char **errMsg )

Обязательная. Формат и параметры этой функции идентичны xCreate(). Основное отличие состоит в том, что xCreate() вызывается только при первом создании экземпляра виртуальной таблицы. xConnect(), с другой стороны, вызывается каждый раз при открытии базы данных. Функция по-прежнему должна выделять и возвращать структуру vtab, но ей не нужно инициализировать какие-либо объекты базы данных.

Если этап создания объекта не требуется, многие модули используют одну и ту же функцию как для xCreate(), так и для xConnect().

Эти функции создают экземпляр виртуальной таблицы. Для каждой виртуальной таблицы только одна из этих функций будет вызываться за время существования соединения с базой данных.

Функции создания и подключения выполняют две основные задачи. Во-первых, они должны выделить структуру sqlite3_vtab и передать ее обратно механизму SQLite. Во-вторых, они должны определить структуру таблицы с помощью вызова sqlite3_declare_vtab(). Вызов xCreate() также должен создавать и инициализировать любое хранилище, будь то теневые таблицы, внешние файлы или что-то еще, что требуется по проекту модуля. Порядок этих задач не важен, если все задачи выполняются до возврата из функции xCreate() или xConnect().

Выделение структуры vtab

Функции xCreate() и xConnect() отвечают за выделение и передачу структуры sqlite3_vtab. Эта структура выглядит так:

struct sqlite3_vtab {
    const sqlite3_module   *pModule;  /* module used by table */
    int                    nRef;      /* SQLite internal use only */
    char                   *zErrMsg;  /* Return error message */
};

Модуль также (в конечном итоге) отвечает за освобождение этой структуры, поэтому вы можете технически использовать любые процедуры управления памятью, которые захотите. Однако для максимальной совместимости настоятельно рекомендуется, чтобы модули использовали sqlite3_malloc(). Это позволит модулю работать в любой среде SQLite.

Единственное интересное поле в структуре sqlite3_vtab - это поле zErrMsg. Это поле позволяет клиенту передать настраиваемое сообщение об ошибке обратно в ядро SQLite, если какая-либо из функций (кроме xCreate() или xConnect()) возвращает код ошибки. Функции xCreate() и xConnect() возвращают любое потенциальное сообщение об ошибке через свой шестой параметр, поскольку они не могут выделить и передать обратно структуру vtab (включая указатель zErrMsg), если вызов xCreate() или xConnect() не был успешным. Функции xCreate() и xConnect() инициализируют указатель сообщения об ошибке vtab значением NULL после выделения структуры vtab.

Обычно виртуальная таблица должна содержать много состояний. Во многих системах это делается с помощью какого-либо указателя на данные пользователя или другого универсального указателя. Хотя функция sqlite3_create_module() позволяет передавать указатель пользовательских данных, этот указатель доступен только для функций xCreate() и xConnect(). Кроме того, один и тот же указатель пользовательских данных предоставляется каждому экземпляру таблицы, управляемой данным модулем, поэтому это не лучшее место для хранения данных, относящихся к конкретному экземпляру.

Стандартный способ предоставления данных о состоянии конкретного экземпляра - обернуть и расширить структуру sqlite3_vtab. Например, наш модуль dblist определит настраиваемую структуру данных vtab, которая выглядит следующим образом:

typedef struct dblist_vtab_s {
    sqlite3_vtab    vtab;  /* this must go first */
    sqlite3         *db;   /* module-specific fields then follow */
} dblist_vtab;

Определяя настраиваемую структуру данных, модуль может эффективно расширять стандартную структуру sqlite3_vtab своими собственными данными. Однако это будет работать, только если структура sqlite3_vtab является первым полем. Это также должна быть обычная структура C, а не класс C++ или какой-либо другой управляемый объект. Также обратите внимание, что поле vtab является полным экземпляром структуры sqlite3_vtab, а не указателем. То есть настраиваемая структура vtab «содержит» структуру sqlite3_vtab, а не «ссылается на» структуру sqlite3_vtab.

Добавление данных экземпляра модуля в структуру sqlite3_vtab таким способом может показаться немного уродливым, но именно так устроен интерфейс виртуальной таблицы. Фактически, это вся причина, по которой функции xCreate() и xConnect() отвечают за выделение памяти, необходимой для структуры sqlite3_vtab. Благодаря тому, что модуль выделяет память, он может намеренно увеличить доступность структуры для своих собственных средств.

В случае модуля dblist единственным дополнительным параметром, который требуется модулю, является соединение с базой данных. Для большинства модулей требуется гораздо больше информации. В частности, функции xCreate() и xConnect() - единственный раз, когда код модуля будет иметь доступ к указателю пользовательских данных, указателю соединения с базой данных (указатель sqlite3), имени базы данных или имени виртуальной таблицы. Если модулю потребуется доступ к ним позже, ему необходимо спрятать копии этих данных в структуре vtab. Самый простой способ сделать копии этих строк - с помощью sqlite3_mprintf(). (См. sqlite3_mprintf() в приложении G.)

Большинству внутренних модулей потребуется сделать копию соединения с базой данных, имени базы данных, содержащей виртуальный модуль, и имени виртуальной таблицы. Эта информация требуется не только для создания любых теневых таблиц (что происходит в xCreate()), но эта информация также требуется для подготовки любых внутренних операторов SQL (обычно в xOpen()), а также для работы с вызовами xRename(). Одна из наиболее распространенных ошибок в конструкции модуля - это предположение, что к соединению с базой данных подключена только одна база данных, а виртуальная таблица находится в основной базе данных. Обязательно протестируйте свой модуль, когда виртуальные таблицы создаются и управляются в других базах данных, которые были открыты с помощью ATTACH DATABASE.

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

Определение структуры таблицы

Другой важной задачей функций xCreate() и xConnect() является определение структуры виртуальной таблицы.

int sqlite3_declare_vtab( sqlite3 *db, const char *sql )
Эта функция используется для объявления формата виртуальной таблицы. Эта функция может быть вызвана только из пользовательской функции xCreate() или xConnect(). Первый параметр - это соединение с базой данных, переданное в xCreate() или xConnect(). Второй параметр - это строка, которая должна содержать единственный правильно сформированный оператор CREATE TABLE.

Хотя модуль должен предоставить имя таблицы в операторе CREATE TABLE, имя таблицы (и имя базы данных, если оно указано) игнорируется. Данное имя не обязательно должно быть именем экземпляра виртуальной таблицы. Кроме того, любые ограничения, значения по умолчанию или определения ключей в определении таблицы также игнорируются - это включает любое определение INTEGER PRIMARY KEY как псевдонима ROWID. Единственные части оператора CREATE TABLE, которые действительно имеют значение, - это имена столбцов и типы столбцов. Все остальное зависит от модуля виртуальной таблицы.

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

Определение виртуальной таблицы dblist довольно простое и отражает ту же структуру, что и вывод PRAGMA database_list. Поскольку структура таблицы также полностью статична, код может просто определить инструкцию SQL как статическую строку:

static const char *dblist_sql =
"CREATE TABLE dblist ( seq INTEGER, name TEXT, file TEXT );";

/* ... */
    sqlite3_declare_vtab( db, dblist_sql );

В зависимости от требований проекта модулю может потребоваться динамическое построение определения таблицы на основе предоставленных пользователем аргументов CREATE VIRTUAL TABLE.

Уже было решено, что пример dblist будет просто использовать значения seq, возвращаемые командой PRAGMA, в качестве источника как выходного столбца seq, так и значений ROWID. Виртуальные таблицы не имеют неявного способа наложения псевдонима стандартного столбца на столбец ROWID, но модуль может сделать это явно в коде.

Для виртуальной таблицы имеет смысл определять свою собственную структуру, а не определять ее напрямую с помощью оператора CREATE VIRTUAL TABLE. Это позволяет приложению адаптироваться к его собственным потребностям и имеет тенденцию значительно упрощать операторы CREATE VIRTUAL TABLE. Однако есть один недостаток: если вы хотите найти структуру виртуальной таблицы, вы не можете просто просмотреть системную таблицу sqlite_master. Каждый экземпляр виртуальной таблицы будет иметь запись в этой таблице, но единственное, что вы найдете там, - это исходный оператор CREATE VIRTUAL TABLE. Если вы хотите найти имена столбцов и типы экземпляра виртуальной таблицы, вам нужно будет использовать команду PRAGMA table_info( table_name ). Это предоставит полный список всех имен и типов столбцов в таблице, даже для виртуальной таблицы.

Инициализация хранилища

Если модуль виртуальной таблицы управляет своим собственным хранилищем, функция xCreate() должна выделить и инициализировать требуемую структуру хранилища. В случае внутреннего модуля, который использует теневые таблицы, модуль должен будет создать соответствующие таблицы. Это необходимо только функции xCreate(). При следующем открытии базы данных будет вызвана функция xConnect(), а не xCreate(). Функция xConnect() может захотеть проверить, существуют ли правильные теневые таблицы в правильной базе данных, но она не должна их создавать.

Если вы пишете внутренний модуль, использующий теневые таблицы, обычно имена теневых таблиц соответствуют виртуальной таблице. В большинстве случаев вы также захотите создать теневые таблицы в той же базе данных, что и виртуальная таблица. Например, если вашему модулю требуются три теневые таблицы для каждого экземпляра виртуальной таблицы, такие как Data, IndexA и IndexB, типичный способ создания таблиц в вашей функции xCreate() будет примерно таким (см. sqlite3_mprintf() в приложении G для получения подробной информации о формате %w):

sql_cmd1 = sqlite3_mprintf(
         "CREATE TABLE \"%w\".\"%w_Data\" (...)", argv[1], argv[2],... );
sql_cmd2 = sqlite3_mprintf(
         "CREATE TABLE \"%w\".\"%w_IndexA\" (...)", argv[1], argv[2],... );
sql_cmd3 = sqlite3_mprintf(
         "CREATE TABLE \"%w\".\"%w_IndexB\" (...)", argv[1], argv[2],... );

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

Если вы пишете внешний модуль, который управляет собственными файлами или что-то подобное, вы должны попытаться следовать некоторому аналогичному соглашению. Просто помните, что не следует использовать имя базы данных (argv[1]) в вашем соглашении об именах, так как оно может измениться в зависимости от того, как база данных была открыта или присоединена к текущему соединению с базой данных.

Пример создания/подключения dblist

Хотя модуль dblist можно рассматривать как внутренний модуль, он не управляет хранилищем каких-либо данных, которые он использует. Это означает, что создавать теневые таблицы не требуется. Это позволяет модулю использовать одну и ту же функцию для указателей функций xCreate() и xConnect().

Вот полная функция создания и подключения dblist:

static int dblist_connect( sqlite3 *db, void *udp, int argc,
        const char *const *argv, sqlite3_vtab **vtab, char **errmsg )
{
    dblist_vtab   *v = NULL;

    *vtab = NULL;
    *errmsg = NULL;
    if ( argc != 3 ) return SQLITE_ERROR;
    if ( sqlite3_declare_vtab( db, dblist_sql ) != SQLITE_OK ) {
        return SQLITE_ERROR;
    }

    v = sqlite3_malloc( sizeof( dblist_vtab ) ); /* alloc our custom vtab */
    *vtab = (sqlite3_vtab*)v;
    if ( v == NULL ) return SQLITE_NOMEM;

    v->db = db;                                  /* stash this for later */
    (*vtab)->zErrMsg = NULL;                     /* initalize this */
    return SQLITE_OK;
}

Функция создания/подключения последовательно выполняет необходимые шаги. Мы проверяем количество аргументов (в данном случае разрешая только три стандартных аргумента), определяем структуру таблицы и, наконец, выделяем и инициализируем нашу настраиваемую структуру vtab. Помните, что вы не должны возвращать выделенную структуру vtab, если вы не возвращаете статус SQLITE_OK.

Отключение и уничтожение

Неудивительно, что каждая из функций xCreate() и xConnect() имеет свои собственные аналоги:

int xDisconnect( sqlite3_vtab *vtab )
Обязательная. Это аналог xConnect(). Она вызывается каждый раз, когда база данных, содержащая виртуальную таблицу, отключается или закрывается. Эта функция должна очистить все ресурсы процесса, используемые реализацией виртуальной таблицы, и освободить структуру данных vtab.
int xDestroy( sqlite3_vtab *vtab )
Обязательная. Это аналог xCreate(), она вызывается в ответ на команду DROP TABLE. Если внутренний модуль создал какие-либо теневые таблицы для хранения данных модуля, эта функция должна вызвать DROP TABLE для этих таблиц. Как и в случае с xDisconnect(), эта функция должна также освободить все ресурсы процесса и структуру виртуальной таблицы.

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

Как и в случае с функциями xCreate() и xConnect(), только одна из этих функций будет вызываться в контексте данного соединения с базой данных. Обе функции должны освободить память, выделенную для указателя vtab. Функция xDestroy() также должна удалять, отбрасывать или освобождать любое хранилище, используемое виртуальной таблицей. Убедитесь, что вы используете полные имена баз данных и таблиц в кавычках.

Версия этой функции для dblist, которая охватывает как xDisconnect(), так и xDestroy(), очень проста:

static int dblist_disconnect( sqlite3_vtab *vtab )
{
    sqlite3_free( vtab );
    return SQLITE_OK;
}

Код освобождает память vtab, вот и все.

Оптимизация запросов

Виртуальные таблицы представляют собой проблему для оптимизатора запросов. Чтобы оптимизировать операторы SELECT и выбрать наиболее эффективный план запроса, оптимизатор должен взвесить ряд факторов. Помимо понимания ограничений запроса (таких как условия WHERE), оптимизация также требует некоторого понимания того, насколько велика таблица, какие столбцы индексируются и как таблица может быть отсортирована.

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

Решение состоит в том, чтобы позволить оптимизатору запросов задавать модулю виртуальной таблицы вопросы о стоимости и производительности различных видов поиска. Это делается с помощью функции xBestIndex():

int xBestIndex( sqlite3_vtab *vtab, sqlite3_index_info *idxinfo )
Обязательная. Когда подготовлен оператор SQL, который ссылается на виртуальную таблицу, оптимизатор запросов вызывает эту функцию для сбора информации о структуре и возможностях виртуальной таблицы. Оптимизатор в основном задает виртуальной таблице ряд вопросов о наиболее эффективных шаблонах доступа, возможностях индексирования и естественном порядке, обеспечиваемом модулем. Эта функция может быть вызвана несколько раз при подготовке оператора.

Связь между оптимизатором запросов и виртуальной таблицей осуществляется через структуру sqlite3_index_info. Эта структура данных содержит раздел ввода и вывода. Библиотека SQLite заполняет раздел ввода (ввод для вашей функции), по сути задавая серию вопросов. Вы можете заполнить выходной раздел структуры, предоставив оптимизатору ответы и расчеты по расходам.

Если xBestIndex() звучит сложно, это потому, что это так. Хорошая новость заключается в том, что если вы проигнорируете оптимизатор, он вернется к полному сканированию таблицы для всех запросов и самостоятельно выполнит проверку ограничений. В случае с модулем dblist мы выберем простой выход и более или менее проигнорируем оптимизатор:

static int dblist_bestindex( sqlite3_vtab *vtab, sqlite3_index_info *info )
{
    return SQLITE_OK;
}

Учитывая, насколько прост модуль и тот факт, что он никогда не вернет более 30 строк (существует внутреннее ограничение на количество подключенных баз данных), это справедливый компромисс между производительностью и сохранением простоты кода. Даже с довольно большими наборами данных SQLite довольно хорошо справляется с обработкой полного сканирования таблицы с удивительной скоростью.

Конечно, не все модули, особенно внутренние, могут с этим справиться. Большинство больших модулей должны пытаться дать разумный ответ на вопросы оптимизатора. Чтобы увидеть, как это делается, мы более подробно рассмотрим эту функцию позже в этой главе. См. «Лучший индекс и фильтр».

Пользовательские функции

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

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

xFindFunction()позволяет модулю переопределить существующую функцию и предоставить свою собственную реализацию. В большинстве случаев в этом нет необходимости (или даже не рекомендуется). Основными исключениями являются функции SQL like() и match(), которые используются для реализации выражений SQL LIKE и MATCH (более подробную информацию см. в приложении D).

Система текстового поиска, вероятно, переопределит функции like() и match(), чтобы предоставить реализацию, которая может напрямую обращаться к строке поиска и основывать оптимизацию индекса на предоставленных аргументах. Без возможности переопределения этих функций было бы очень сложно оптимизировать текстовый поиск, поскольку стандартный алгоритм потребовал бы полного стабильного сканирования, извлечения каждого значения строки и выполнения внешнего сравнения.

int xFindFunction( sqlite3_vtab *vtab, int arg, const char *func_name, custom_function_ref, void **udp_ref)

По желанию. Эта функция позволяет модулю переопределить существующую функцию. Он вызывается при подготовке оператора SQL, который использует столбец виртуальной таблицы в качестве первого параметра в функции SQL (или второго, в случае like(), glob(), match() или regexp()). Первый параметр - это структура vtab для этого экземпляра таблицы. Второй параметр указывает, сколько параметров передается в функцию SQL, а третий параметр содержит имя функции. Четвертый параметр - это ссылка на указатель скалярной функции (см. «Скалярные функции»), а пятый параметр - это ссылка на указатель пользовательских данных.

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

Большинству модулей не нужно реализовывать эту функцию, а тем, которые это делают, нужно только переопределить несколько ключевых функций. Модуль dblist не предоставляет реализации.

Переименование таблицы

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

int xRename( sqlite3_vtab *vtab, const char *new_name )

Обязательная. Эта функция вызывается в ответ на команду SQL ALTER TABLE...RENAME. Первый параметр - это переименовываемый экземпляр таблицы, а второй параметр - это новое имя таблицы.

В случае внутреннего модуля наиболее вероятным способом действий является переименование любых теневых таблиц в соответствии с новым именем. Чтобы сделать это правильно, потребуется знать имя исходной таблицы, а также базу данных (main , temp и т. д.), которая была передана в xCreate() или xConnect().

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

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

В модуле dblist есть очень короткая функция xRename():

static int dblist_rename( sqlite3_vtab *vtab, const char *newname )
{
    return SQLITE_OK;
}

Модуль dblist ни для чего не использует имя таблицы, поэтому ему ничего не надо делать для сохранения безопасности.

Открытие и закрытие табличных курсоров

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

Курсор создается с помощью функции xOpen() и освобождается с помощью функции xClose(). Как и в случае со структурой vtab, модуль отвечает за выделение структуры sqlite3_vtab_cursor и возврат ее обратно в механизм SQLite.

int xOpen( sqlite3_vtab *vtab, sqlite3_vtab_cursor **cursor )
Обязательная. Эта функция должна выделять, инициализировать и возвращать курсор.
int xClose( sqlite3_vtab_cursor *cursor )
Обязательная. Эта функция должна очистить и освободить структуру курсора. По сути, она должна отменить все, что делает xOpen().

Собственная структура sqlite3_vtab_cursor довольно минимальна и выглядит так:

struct sqlite3_vtab_cursor {
    sqlite3_vtab   *pVtab; /* pointer to table instance */
};

Как и в случае со структурой sqlite3_vtab, ожидается, что модуль расширит эту структуру любыми данными, которые требуются модулю. Пользовательский курсор dblist выглядит так:

typedef struct dblist_cursor_s {
    sqlite3_vtab_cursor   cur;    /* this must go first */
    sqlite3_stmt          *stmt;  /* PRAGMA database_list statement */
    int                   eof;    /* EOF flag */
} dblist_cursor;

Для модуля dblist единственные данные, зависящие от курсора, - это указатель оператора SQLite и флаг EOF. Флаг используется, чтобы указать, когда модуль достиг конца вывода PRAGMA database_list.

Помимо выделения dblist_cursor, единственной другой задачей, которую должна выполнить функция dblist xOpen(), является подготовка оператора SQL PRAGMA:

static int dblist_open( sqlite3_vtab *vtab, sqlite3_vtab_cursor **cur )
{
    dblist_vtab    *v = (dblist_vtab*)vtab;
    dblist_cursor  *c = NULL;
    int            rc = 0;
    
    c = sqlite3_malloc( sizeof( dblist_cursor ) );
    *cur = (sqlite3_vtab_cursor*)c;
    if ( c == NULL ) return SQLITE_NOMEM;
    
    rc = sqlite3_prepare_v2( v->db, "PRAGMA database_list", -1, &c->stmt, NULL );
    if ( rc != SQLITE_OK ) {
        *cur = NULL;
        sqlite3_free( c );
        return rc;
    }
    return SQLITE_OK;
}

Как и в случае с xCreate() и xConnect(), sqlite3_vtab_cursor не должен выделяться или передаваться обратно, если не возвращается SQLITE_OK. Нет необходимости инициализировать поле pVtab курсора - SQLite позаботится об этом за нас.

Версия xClose() для dblist очень проста. Перед освобождением структуры курсора модуль должен убедиться, что подготовленный оператор завершен:

static int dblist_close( sqlite3_vtab_cursor *cur )
{
    sqlite3_finalize( ((dblist_cursor*)cur)->stmt );
    sqlite3_free( cur );
    return SQLITE_OK;
}

Вам может быть интересно, почему модуль помещает указатель оператора в курсор. Это требует, чтобы модуль представил оператор PRAGMA для каждого курсора. Разве не имеет смысла поместить указатель оператора в структуру vtab? Таким образом, его можно было подготовить только один раз, а затем повторно использовать для каждого курсора.

Поначалу это выглядит привлекательным вариантом. Это было бы более эффективно и, в большинстве случаев, работало бы нормально - вплоть до того момента, когда SQLite нужно было создать более одного курсора для одного и того же экземпляра таблицы одновременно. Поскольку модуль зависит от структуры оператора, чтобы отслеживать позицию в данных виртуальной таблицы (то есть, вывод PRAGMA database_list), конструкция модуля требует, чтобы каждый курсор имел свой собственный оператор. Самый простой способ сделать это - просто подготовить и сохранить оператор с курсором, привязав время жизни оператора к времени жизни курсора.

Фильтрация строк

Функция xFilter() работает вместе с функцией xBestIndex(), обеспечивая механизм запросов SQLite средством сообщения любых конкретных ограничений или условий, накладываемых на запрос. Функция xBestIndex() используется оптимизатором запросов, чтобы задать модулю вопросы о различных шаблонах поиска или ограничениях. Как только SQLite решает, что делать, функция xFilter() используется, чтобы сообщить модулю, какой план действий выполняется для этого конкретного сканирования.

int xFilter( sqlite3_vtab_cursor *cursor, int idx_num, const char *idx_str, int argc, sqlite3_value **argv )
Обязательная. Эта функция используется для сброса курсора и запуска нового сканирования таблицы. SQLite сообщит о любых ограничениях, наложенных на текущий курсор. Модуль может пропустить любые строки, которые не соответствуют этим ограничениям. Все параметры определяются действиями, выполняемыми функцией xBestIndex(). Также необходимо получить первую строку данных.

Идея состоит в том, чтобы позволить модулю «предварительно фильтровать» как можно больше строк. Каждый раз, когда библиотека SQLite просит модуль переместить курсор таблицы к следующей строке, модуль может использовать информацию, предоставленную функции xFilter(), чтобы пропустить любые строки, которые не соответствуют указанным критериям для этого сканирования таблицы.

Функции xBestIndex() и xFilter() также могут работать вместе, чтобы указать конкретный порядок строк. Обычно SQLite не делает никаких предположений о порядке строк, возвращаемых виртуальной таблицей, но можно использовать xBestIndex(), чтобы указать на возможность поддержки одного или нескольких конкретных порядков. Если один из этих порядков передается в xFilter(), таблица должна возвращать строки в указанном порядке.

Чтобы использовать функцию xFilter(), модуль также должен иметь полностью реализованную функцию xBestIndex(). Функция xBestIndex() устанавливает данные, которые передаются в функцию xFilter(). Большая часть данных, передаваемых в xFilter(), не имеет особого значения для SQLite, они просто основаны на кодовых соглашениях между xBestIndex() и xFilter().

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

Полное сканирование таблицы может быть приемлемым для многих типов внешних модулей, но если вы разрабатываете индивидуальную систему индексации, у вас мало выбора, кроме как заняться написанием надежных функций xBestIndex() и xFilter(). Чтобы лучше понять, как это сделать, см. «Лучший индекс и фильтр».

Даже если фактический процесс фильтрации игнорируется, функция xFilter() по-прежнему требуется для выполнения двух важных задач. Во-первых, она должна сбросить курсор и подготовить его к новому сканированию таблицы. Во-вторых, xFilter() отвечает за выборку первой строки выходных данных. Поскольку модуль dblist не использует систему фильтрации, это практически единственное, что выполняет функция xFilter():

static int dblist_filter( sqlite3_vtab_cursor *cur,
            int idxnum, const char *idxstr,
            int argc, sqlite3_value **value )
{
    dblist_cursor  *c = (dblist_cursor*)cur;
    int            rc = 0;
    
    rc = sqlite3_reset( c->stmt );    /* start a new scan */
    if ( rc != SQLITE_OK ) return rc;
    c->eof = 0;                       /* fetch first row */
    
    dblist_get_row( c );              /* clear EOF flag */
    return SQLITE_OK; 
}

Хотя модуль dblist не использует данные xBestIndex(), есть еще важные дела. Функция xFilter() сначала должна сбросить инструкцию. Это «перематывает» инструкцию прагмы, помещая курсор в начало таблицы. Бывают ситуации, когда sqlite3_reset() может быть вызван для только что подготовленного (или только что сброшенного) оператора, но это не проблема. Существуют и другие вызывающие последовательности, которым может потребоваться xFilter() для сброса оператора.

Поскольку и xFilter(), и функция xNext() (которую мы рассмотрим далее) должны получать данные строки, мы переместили это в отдельную функцию:

static int dblist_get_row( dblist_cursor *c )
{
    int rc;
    
    if ( c->eof ) return SQLITE_OK;
    rc = sqlite3_step( c->stmt );
    if ( rc == SQLITE_ROW ) return SQLITE_OK;  /* we have a valid row */
    
    sqlite3_reset( c->stmt );
    c->eof = 1;
    return ( rc == SQLITE_DONE ? SQLITE_OK : rc ); /* DONE -> OK */
}

Основное, что делает эта функция, - это вызов sqlite3_step() для SQL-оператора курсора. Если модуль получает допустимую строку данных (SQLITE_ROW), все в порядке. Если модуль получает что-либо еще (включая SQLITE_DONE), он считает сканирование выполненным. В этом случае модуль сбрасывает инструкцию перед возвратом SQLITE_OK (если он дошел до конца таблицы) или ошибки. Хотя модуль может дождаться, пока xFilter() сбросит инструкцию или xClose() завершит ее, лучше всего сбросить инструкцию, как только мы узнаем, что достигли конца доступных данных.

Извлечение и возврат данных

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

int xNext( sqlite3_vtab_cursor *cursor )

Обязательная. Эта функция используется для перемещения курсора к следующей строке. Когда механизму SQLite больше не нужны данные из текущей строки, она вызывается для перехода при сканировании виртуальной таблицы к следующей строке. Если виртуальная таблица уже находится в последней строке таблицы при вызове xNext(), она не должна возвращать ошибку.

Обратите внимание, что xNext() действительно является «следующей» функцией, а не функцией «получить строку». Она не вызывается для выборки первой строки данных. Первая строка данных должна быть получена и сделана доступной функцией xFilter().

Если модуль фильтрует строки с помощью xBestIndex() и xFilter(), для xNext() законно пропускать любые строки в виртуальной таблице, которые не соответствуют условиям, выдвинутым для xFilter(). Кроме того, если xBestIndex() указала возможность возвращать данные в определенном порядке, xNext() обязана сделать это. В противном случае xNext() может возвращать строки в любом порядке, если они все возвращены.

int xEof( sqlite3_vtab_cursor *cursor )

Обязательная. Эта функция используется, чтобы определить, достигла ли виртуальная таблица конца таблицы. Каждый вызов xFilter() и xNext() немедленно сопровождается вызовом xEof(). Если предыдущий вызов xNext() продвинул курсор за конец таблицы, xEof() должен вернуть истинное (ненулевое) значение, указывающее, что конец таблицы достигнут. Если курсор все еще указывает на допустимую строку таблицы, xEof() должен вернуть false (ноль).

xEof() также вызывается сразу после xFilter(). Если таблица пуста или не будет возвращать строки в условиях, определенных xFilter(), тогда xEof() должен вернуть true в это время.

Нет никакой гарантии, что xNext() будет продолжать вызываться до тех пор, пока xEof() не вернет true. Запрос может принять решение о прекращении сканирования таблицы в любое время.

int xRowid( sqlite3_vtab_cursor *cursor, sqlite_int64 *rowid )
Обязательная. Эта функция используется для получения значения ROWID текущей строки. Значение ROWID следует передать обратно через ссылку rowid, указанную в качестве второго параметра.
int xColumn( sqlite3_vtab_cursor *cursor, sqlite3_context *ctx, int cidx )
Обязательная. Эта функция используется для извлечения значений столбцов из текущей строки курсора. Параметры включают курсор виртуальной таблицы, структуру sqlite3_context и индекс столбца. Значения следует возвращать с помощью функций sqlite3_context и sqlite3_result_xxx(). Индекс столбца отсчитывается от нуля, поэтому первый столбец, определенный в определении виртуальной таблицы, будет иметь нулевой индекс столбца. Эта функция обычно вызывается несколько раз между вызовами xNext().

Первые две функции, xNext() и xEof(), используются для перемещения курсора по данным виртуальной таблицы. Курсор можно перемещать только по данным, его нельзя попросить выполнить резервное копирование, за исключением полного возврата в начало таблицы. Если xBestIndex() и xFilter() не согласовали конкретную фильтрацию или упорядочение, xNext() не обязан представлять данные в определенном порядке. Единственное требование - непрерывные вызовы xNext() в конечном итоге обращаются к каждой строке ровно один раз.

В каждой строке можно использовать xRowid() и xColumn() для извлечения значений из текущей строки. xRowid() используется для извлечения значения виртуального ROWID, а xColumn() используется для извлечения значений из всех других столбцов. Пока курсор находится в определенной строке, функции xRowid() и xColumn() могут вызываться любое количество раз в любом порядке.

Поскольку модуль dblist зависит от выполнения оператора PRAGMA для возврата данных, большинство этих функций чрезвычайно просты. Например, функция dblist xNext() вызывает функцию dblist_get_row(), которая, в свою очередь, вызывает sqlite3_step() для оператора курсора:

static int dblist_next( sqlite3_vtab_cursor *cur )
{
    return dblist_get_row( (dblist_cursor*)cur );
}

Функция dblist xEof() возвращает флаг EOF курсора. Этот флаг устанавливается dblist_get_row(), когда модуль достигает конца данных PRAGMA database_list. Флаг просто возвращается:

static int dblist_eof( sqlite3_vtab_cursor *cur )
{
    return ((dblist_cursor*)cur)->eof;
}

Функции извлечения данных для модуля dblist также чрезвычайно просты. Модуль dblist использует столбец seq из вывода PRAGMA database_list в качестве своего виртуального ROWID. Это означает, что он может возвращать значение столбца seq как наш ROWID. Как это часто бывает, столбец seq является первым столбцом, поэтому он имеет нулевой индекс:

static int dblist_rowid( sqlite3_vtab_cursor *cur, sqlite3_int64 *rowid )
{
    *rowid = sqlite3_column_int64( ((dblist_cursor*)cur)->stmt, 0 );
    return SQLITE_OK;
}

Функция xColumn() почти такая же простая. Поскольку между выходными столбцами оператора PRAGMA и столбцами виртуальной таблицы dblist существует взаимно однозначное соответствие, модуль может извлекать значения непосредственно из выходных данных PRAGMA и передавать их обратно как значения столбцов для нашей виртуальной таблицы:

static int dblist_column( sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int cidx )
{
    dblist_cursor   *c = (dblist_cursor*)cur;
    sqlite3_result_value( ctx, sqlite3_column_value( c->stmt, cidx ) );
    return SQLITE_OK;
}

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

Чтобы лучше понять, как может выглядеть более типичный модуль, взгляните на реализацию этих функций в другом примере модуля («Расширенный пример: модуль веб-журнала»).

Модификации виртуальной таблицы

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

int xUpdate( sqlite3_vtab *vtab, int argc, sqlite3_value **argv, sqlite_int64 *rowid )
По желанию. Этот вызов используется для поддержки всех модификаций виртуальных таблиц. Он будет вызываться в ответ на любую команду INSERT, UPDATE или DELETE. Первый параметр - это экземпляр таблицы. Второй и третий параметры передаются в серии значений базы данных. Четвертый параметр является ссылкой на значение ROWID и используется для передачи вновь определенного ROWID при вставке новой строки.

Параметр argv будет иметь допустимую структуру sqlite3_value для каждого аргумента, хотя некоторые из этих значений могут иметь тип SQLITE_NULL. Строки всегда вставляются или обновляются целыми наборами. Даже если команда SQL UPDATE обновляет только один столбец строки, команде xUpdate() всегда будет предоставлено значение для каждого столбца в строке.

Если указан только один аргумент, это запрос DELETE. Единственным аргументом (argv[0]) будет SQLITE_INTEGER, содержащий ROWID строки, которую необходимо удалить.

Во всех остальных случаях будет предоставлено ровно n + 2 аргумента, где n - количество столбцов, включая HIDDEN (см. «Создание и подключение») в определении таблицы. Первый аргумент (argv[0]) используется для ссылки на существующие значения ROWID, а второй (argv[1]) используется для ссылки на новые значения ROWID. За этими двумя аргументами будет следовать значение для каждого столбца в строке, начиная с argv[2]. По сути, аргументы от argv[1] до argv[n+1] представляют весь набор значений строк, начиная с подразумеваемого столбца ROWID, за которым следуют все объявленные столбцы.

Если argv[0] имеет тип SQLITE_NULL, это запрос INSERT. Если оператор INSERT предоставил явное значение ROWID, это значение будет в argv[1] как SQLITE_INTEGER. Перед использованием модуль должен убедиться, что ROWID является подходящим и уникальным. Если не было задано явное значение ROWID, argv[1] будет иметь тип SQLITE_NULL. В этом случае модуль должен присвоить неиспользуемое значение ROWID и передать его обратно через указатель ссылки rowid в параметрах xUpdate().

Если argv[0] имеет тип SQLITE_INTEGER, это UPDATE. В этом случае и argv[0], и argv[1] будут типами SQLITE_INTEGER со значениями ROWID. Существующая строка, указанная в argv[0], должна быть обновлена значениями, указанными в argv[1] по argv[n+1]. В большинстве случаев argv[0] будет таким же, как argv[1], что означает отсутствие изменений в значении ROWID. Однако, если оператор UPDATE включает явное обновление столбца ROWID, может случиться так, что первые два аргумента не совпадают. В этом случае строка, обозначенная argv[0], должна иметь значение ROWID, измененное на argv[1]. В любом случае все остальные столбцы должны быть обновлены дополнительными аргументами.

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

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

Функция xUpdate() не является обязательной. Если реализация не предусмотрена, все экземпляры виртуальных таблиц, предоставленные этим модулем, будут доступны только для чтения. Так обстоит дело с нашим модулем dblist, поэтому для xUpdate() нет реализации.

Последовательность курсора

Большинство функций курсора имеют очень специфические задачи. Некоторые из них, например xEof(), обычно очень маленькие, а другие, например xNext(), могут быть довольно сложными. Независимо от размера или сложности, все они должны работать вместе, чтобы выполнять надлежащие задачи и поддерживать состояние структуры sqlite3_vtab_cursor, включая любые расширения, которые могут потребоваться вашему модулю.

Чтобы поддерживать правильное состояние, важно понимать, какие функции могут быть вызваны и когда. На рис. 10-1 представлена карта последовательности того, когда могут быть вызваны функции курсора. Ваш модуль должен быть подготовлен к тому, чтобы должным образом справиться с любым из этих переходов.

Рисунок 10-1. Продолжительность жизни курсора виртуальной таблицы. Здесь показаны возможные последовательности вызовов для функций курсора модуля виртуальной таблицы.

Некоторые последовательности вызовов могут застать людей врасплох, например, если xClose() вызывается до того, как xEof() вернет true. Это может произойти, если в запросе SQL есть предложение LIMIT. Кроме того, xRowid() может вызываться несколько раз между вызовами xNext(). Точно так же xColumn() может вызываться несколько раз с одним и тем же индексом столбца между вызовами xNext(). Также возможно, что ни xRowid(), ни xColumn() (ни оба) не могут быть вызваны вообще между вызовами xNext().

Помимо функций курсора, в этой последовательности также могут вызываться некоторые функции уровня таблицы. В частности, xUpdate() может быть вызван в любое время, возможно, изменив строку, которую курсор обрабатывает в данный момент. Обычно это происходит, когда оператор обновления открывает курсор, находит строку, которую он хочет изменить, а затем вызывает xUpdate() вне контекста курсора.

Может быть сложно протестировать ваш модуль и убедиться, что все работает правильно. Единственный совет, который я могу предложить, - протестировать свой модуль с известным и относительно небольшим набором данных, пропустив его через как можно больше типов запросов. Попробуйте включить различные варианты GROUP BY, ORDER BY и любое количество операций соединения (включая самостоятельные соединения). Когда вы впервые начинаете писать модуль, может также помочь размещение простых printf() или других операторов отладки в верхней части каждой функции. Это поможет понять шаблоны вызовов.

Контроль транзакций

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

int xBegin( sqlite3_vtab *vtab )
По желанию. Указывает начало транзакции с виртуальной таблицей. Любой код возврата, отличный от SQLITE_OK, приведет к сбою транзакции.
int xSync( sqlite3_vtab *vtab )
По желанию. Указывает начало фиксации транзакции, в которой задействована виртуальная таблица. Любой код возврата, отличный от SQLITE_OK, приведет к автоматическому откату всей транзакции.
int xCommit( sqlite3_vtab *vtab )
По желанию. Указывает на завершение фиксации транзакции, в которой задействована виртуальная таблица. Код возврата игнорируется - если xSync() завершился успешно, эта функция должна завершиться успешно.
int xRollback( sqlite3_vtab *vtab )
По желанию. Указывает, что транзакция с виртуальной таблицей откатывается. Модуль должен вернуться в то состояние, в котором он находился до вызова xBegin(). Код возврата игнорируется.

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

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

Если вам действительно нужно поддерживать свои собственные транзакции, важно помнить о потоке программы. xBegin() всегда будет первой вызываемой функцией*. Обычно будут вызовы xUpdate(), за которыми следует двухэтапная последовательность вызовов xSync() и xCommit() для закрытия и фиксации транзакции. После вызова xBegin() также можно получить вызов xRollback() для отката транзакции. Функция xRollback() также может быть вызвана после xSync() (но перед xCommit()), если шаг синхронизации завершился неудачно.

Полные транзакции не вкладываются, а виртуальные таблицы не поддерживают точки сохранения. После того, как был сделан вызов xBegin(), вы не получите другого, пока не будет вызван xCommit() или xRollback().

В соответствии со свойствами ACID транзакции, любые изменения, сделанные между вызовами xBegin() и xSync(), должны быть видны только этому экземпляру виртуальной таблицы в этом соединении с базой данных (то есть этой структуре vtab). Это можно сделать, отложив любые записи или модификации во внешние источники данных до тех пор, пока не будет вызвана функция xSync(), или каким-то образом заблокировав источник данных, чтобы гарантировать, что другие экземпляры модуля (или другие приложения) не могут изменять или получать доступ к данным. Если данные записываются в xSync(), источник данных все равно необходимо заблокировать до тех пор, пока не будет выполнен вызов xCommit() или xRollback(). Если xSync() возвращает SQLITE_OK, предполагается, что любой вызов xCommit() будет успешным, поэтому вы хотите попытаться внести свои изменения в xSync() и проверить и выпустить их в xCommit().

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

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

* Теоретически. В настоящее время вызовы выполняются непосредственно в xSync() и xCommit() после вызова xCreate(). Неясно, считается ли это ошибкой или нет, поэтому это поведение может измениться в будущих версиях SQLite.

Регистрация модуля

Теперь, когда мы рассмотрели все функции, необходимые для определения модуля, нам нужно их зарегистрировать. Как мы уже видели, это делается с помощью функции sqlite3_create_module(). Чтобы зарегистрировать модуль, нам нужно заполнить структуру sqlite3_module и передать ее функции создания.

Вы могли заметить, что все функции нашего модуля были отмечены как статические. Это связано с тем, что модуль был написан как расширение (см. Раздел «Расширения SQLite»). Структурируя код таким образом, мы можем легко встроить наш модуль виртуальной таблицы в приложение или создать динамическое расширение.

Вот функция инициализации нашего расширения:

static sqlite3_module dblist_mod = {
    1,                  /* iVersion        */
    dblist_connect,     /* xCreate()       */
    dblist_connect,     /* xConnect()      */
    dblist_bestindex,   /* xBestIndex()    */
    dblist_disconnect,  /* xDisconnect()   */
    dblist_disconnect,  /* xDestroy()      */
    dblist_open,        /* xOpen()         */
    dblist_close,       /* xClose()        */
    dblist_filter,      /* xFilter()       */
    dblist_next,        /* xNext()         */
    dblist_eof,         /* xEof()          */
    dblist_column,      /* xColumn()       */
    dblist_rowid,       /* xRowid()        */
    NULL,               /* xUpdate()       */
    NULL,               /* xBegin()        */
    NULL,               /* xSync()         */
    NULL,               /* xCommit()       */
    NULL,               /* xRollback()     */
    NULL,               /* xFindFunction() */
    dblist_rename       /* xRename()       */
};

int dblist_init( sqlite3 *db, char **error, const sqlite3_api_routines *api )
{
    int   rc;
    SQLITE_EXTENSION_INIT2(api);

    /* register module */
    rc = sqlite3_create_module( db, "dblist", &dblist_mod, NULL );
    if ( rc != SQLITE_OK ) {
        return rc;
    }

    /* automatically create an instance of the virtual table */
    rc = sqlite3_exec( db,
        "CREATE VIRTUAL TABLE temp.sql_database_list USING dblist",
        NULL, NULL, NULL );
    return rc;
}

Самая важная вещь, на которую следует обратить внимание, - это то, что структуре sqlite3_module присвоено статическое распределение. Библиотека SQLite не создает копию этой структуры при регистрации модуля, поэтому структура sqlite3_module должна оставаться действительной в течение всего времени соединения с базой данных. В этом случае мы используем глобальный уровень файла, который статически инициализируется со всеми правильными значениями.

Функция точки входа расширения немного уникальна в том смысле, что она не только определяет модуль, но и создает экземпляр виртуальной таблицы dblist. Обычно функция инициализации расширения не будет (и не должна) делать что-то подобное, но в данном случае это имеет смысл. Как и любая другая таблица, виртуальные таблицы обычно привязаны к определенной базе данных. Но список активных баз данных, который мы получаем из команды PRAGMA database_list, является функцией текущего состояния соединения с базой данных (и всех подключенных баз данных) и на самом деле не является специфическим для отдельной базы данных. Если бы вы создали таблицу dblist в нескольких базах данных, которые все были подключены к одному и тому же соединению с базой данных, все они вернули бы одни и те же данные. Настоящим источником данных является соединение с базой данных (а не с конкретной базой данных).

Итак, в несколько уникальном случае модуля dblist нам нужен только один экземпляр виртуальной таблицы для каждого соединения с базой данных. В идеале он всегда был бы там, независимо от того, какие базы данных подключены. Также было бы лучше, если бы таблица не была «оставлена» в файле базы данных после того, как эта база данных была закрыта или отсоединена. Это не только привяжет файл базы данных к нашему модулю, но и не нужно, поскольку экземпляр таблицы dblist не имеет никакого состояния, кроме соединения с базой данных.

Чтобы удовлетворить все эти потребности, модуль продолжает работу и просто создает единственный экземпляр таблицы во временной базе данных. Каждое соединение с базой данных имеет временную базу данных, и она всегда называется temp. Это упрощает поиск экземпляра таблицы. Создание ее в базе данных temp также позволяет исключить экземпляр таблицы из любых «реальных» файлов базы данных и связывает время жизни таблицы со временем жизни соединения с базой данных. В целом, он идеально, хотя и несколько необычно, подходит для этого конкретного модуля.

Конечным результатом является то, что если вы загрузите расширение dblist, оно не только зарегистрирует модуль dblist, но и создаст экземпляр виртуальной таблицы dblist в temp.sql_database_list. Системные таблицы в SQLite имеют префикс sqlite_, но эти имена зарезервированы, и расширение не может создать таблицу с этим префиксом. Однако имя sql_database_list передает идею.

Пример использования

Что мы получим после всей этой работы? Во-первых, давайте посмотрим, что делает PRAGMA database_list сам по себе. Вот пример вывода:

sqlite> PRAGMA database_list;
seq        name       file
---------- ---------- -----------------------------
0          main       /Users/jak/sqlite/db1.sqlite3
1          temp
2          memory
3          two        /Users/jak/sqlite/db2.sqlite3

В этом случае я запустил утилиту sqlite3 с файлом db1.sqlite3, создал пустую временную таблицу (чтобы появилась временная база данных temp), подключил базу данных в памяти как memory и, наконец, присоединил второй файл базы данных как two.

Теперь загрузим наше расширение модуля и посмотрим, что у нас получится:

sqlite> .load dblist.sqlite3ext dblist_init
sqlite> SELECT * FROM sqlite3_database_list;
seq        name       file
---------- ---------- -----------------------------
0          main       /Users/jak/sqlite/db1.sqlite3
1          temp
2          memory
3          two        /Users/jak/sqlite/db2.sqlite3

И получаем... то же самое! На самом деле, это хорошо - в этом весь смысл. Ключевым моментом является то, что, в отличие от команды PRAGMA, мы можем сделать это:

sqlite> SELECT * FROM sqlite3_database_list WHERE file == '';
seq        name       file
---------- ---------- -----------------------------
1          temp
2          memory

Это показывает нам все базы данных, у которых нет ассоциированного имени файла. Вы заметите, что PRAGMA database_list (и, следовательно, модуль dblist) возвращает пустую строку, а не NULL, для базы данных, не имеющей связанного файла базы данных.

Возможно, наиболее полезно то, что мы также можем делать такие запросы, чтобы выяснить, каково логическое имя базы данных для конкретного файла базы данных:

sqlite> SELECT name FROM sqlite3_database_list
   ...>   WHERE file LIKE '%db2.sqlite3';
name
----------
two

Я бы первым признал, что это не совсем новаторская работа. Анализ прямого вывода PRAGMA database_list не представляет большого труда ни для программы, ни для человека. Основной смысл этого примера состоял не в том, чтобы показать всю мощь виртуальных таблиц, а в том, чтобы дать нам задачу для работы, с которой мы могли бы сосредоточиться на функциях и интерфейсе, требуемых системой виртуальных таблиц, вместо того, чтобы сосредоточиться на сложном коде, необходимом для его реализации.

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

Расширенный пример: модуль веб-журнала

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

Чтобы предоставить более сложный и реалистичный пример, мы рассмотрим второй пример модуля. Этот модуль известен как weblog и предназначен для анализа журналов сервера Apache httpd и представления их механизму базы данных в виде виртуальной таблицы. Он проанализирует дефолтные лог-файлы Apache формата combine и common или любой другой файл журнала, который соответствует этому формату. Файлы журнала Apache кроссплатформенны и достаточно распространены. Многие люди имеют доступ к файлам журналов с приличным количеством интересных данных, что позволяет сделать этот пример более практическим.

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

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

Виртуальная таблица веб-журнала может быть создана с помощью такой команды SQL:

CREATE VIRTUAL TABLE current USING weblog( /var/log/httpd/access.log );

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

CREATE VIRTUAL TABLE log USING weblog( /var/log/httpd/access log file.txt );

Первый пример создаст текущий экземпляр таблицы и привяжет его к данным, найденным в файле /var/log/httpd/access.log. Во втором примере таблица SQL log будет привязана к файлу access log file.txt в том же каталоге.

Вкратце, формат журнала Apache common содержит семь полей. Первое поле - это IP-адрес клиента. В редких случаях это может быть имя хоста, но большинство серверов настроено просто на запись IP-адреса в десятичном формате с точками. Второе поле - это устаревшее поле идентификатора. Большинство веб-серверов не поддерживают это и записывают только одно тире. В третьем поле записывается имя пользователя, если оно указано. Если не указано, то это поле также записывается как одиночное тире. Четвертое поле - это отметка времени, заключенная в квадратные скобки ([ ]). Пятое - первая строка HTTP-запроса, заключенная в двойные кавычки. Оно содержит операцию HTTP (например, GET или POST), а также URL-адрес. В шестом столбце находится код результата HTTP (например, 200 для ОК, 404 для отсутствующего ресурса), с количеством байтов полезной нагрузки, возвращаемым в седьмом поле.

Формат файла combine добавляет еще два поля. Восьмое поле - это заголовок реферера, который содержит URL. Девятое поле - это заголовок пользовательского агента, также заключенный в двойные кавычки.

Счетчик Поле лог-файла Значение
1 Client Address IP или имя хоста HTTP-клиента
2 Ident Устаревшее поле, не используется
3 Username Имя пользователя, предоставленное клиентом
4 Timestamp Время транзакции
5 HTTP Request HTTP-операция и URL
6 Result Code Статус результата HTTP-запроса
7 Bytes Байты полезной нагрузки
8 Referrer URL-адрес страницы реферера
9 User Agent Идентификатор клиентского программного обеспечения

Модуль weblog предназначен для чтения файлов формата combine. Однако, если дан файл журнала common, в котором отсутствуют последние два поля, эти дополнительные поля будут просто NULL.

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

Например, IP-адрес будет возвращен в одном столбце в виде текстового значения, содержащего традиционную точечную нотацию. Другой столбец предоставит необработанное целочисленное представление. Текстовый столбец легче понять людям, но целочисленный столбец позволяет ускорить поиск, особенно по диапазонам. Базовые данные одинаковы: два столбца просто возвращают данные в разных форматах. Точно так же столбец отметки времени может возвращать строковое значение из файла журнала или может возвращать отдельные целочисленные значения для года, месяца, дня и т. д.

Если бы это было полностью поддерживаемое расширение SQLite, оно, вероятно, включало бы больше, чем просто модуль веб-журнала. В идеале он также должен включать ряд служебных функций, таких как функция, преобразующая текстовые значения, содержащие IP-адреса с десятичными точками, в целочисленные значения и обратно. (Опять же, если бы это был полностью поддерживаемый модуль, он бы включал приличные сообщения об ошибках и другие доработки, которых не хватает в этом примере. Я стараюсь, чтобы количество строк было как можно меньше.) Некоторые из этих функций уменьшили бы потребность в дополнительных столбцах, поскольку вы можете просто преобразовать данные с помощью SQL, но бывают случаи, когда наличие дополнительных столбцов чрезвычайно полезно.

Создание и подключение

Поскольку модуль weblog является внешним модулем, нет никаких данных для инициализации. Это означает, что, как и dblist, мы можем использовать одну и ту же функцию как для xCreate(), так и для xConnect().

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

typedef struct weblog_vtab_s {
    sqlite3_vtab   vtab;
    char           *filename;
} weblog_vtab;

Функция создания/подключения блога немного длиннее, чем версия dblist, но все же довольно проста в использовании. Во-первых, она проверяет, что у нас есть ровно четыре аргумента. Помните, что первые три аргумента - это всегда имя модуля, имя базы данных и имя таблицы. Четвертый аргумент - это первый предоставленный пользователем аргумент, которым в данном случае является имя файла журнала. Функция пытается открыть этот файл для доступа только для чтения, просто чтобы убедиться, что файл существует и может быть открыт для чтения. Этот тест не является надежным, но это хорошая проверка. Затем модуль выделяет структуру vtab, сохраняет копию имени файла и объявляет определение таблицы:

static int weblog_connect( sqlite3 *db, void *udp, int argc,
        const char *const *argv, sqlite3_vtab **vtab, char **errmsg )
{
    weblog_vtab  *v = NULL;
    const char   *filename = argv[3];
    FILE         *ftest;

    if ( argc != 4 ) return SQLITE_ERROR;

    *vtab = NULL;
    *errmsg = NULL;

    /* test to see if filename is valid */
    ftest = fopen( filename, "r" );
    if ( ftest == NULL ) return SQLITE_ERROR;
    fclose( ftest );

    /* allocate structure and set data */
    v = sqlite3_malloc( sizeof( weblog_vtab ) );
    if ( v == NULL ) return SQLITE_NOMEM;
    ((sqlite3_vtab*)v)->zErrMsg = NULL; /* need to init this */

    v->filename = sqlite3_mprintf( "%s", filename );
    if ( v->filename == NULL ) {
        sqlite3_free( v );
        return SQLITE_NOMEM;
    }
    v->db = db;

    sqlite3_declare_vtab( db, weblog_sql );
    *vtab = (sqlite3_vtab*)v;
    return SQLITE_OK;
}

Определение таблицы содержит всего 20 столбцов. Первые 9 отображаются непосредственно в поля в файле журнала, в то время как дополнительные 11 столбцов предоставляют разные представления одних и тех же данных. Последний столбец представляет собой всю строку файла журнала без изменений:

const static char *weblog_sql =
"    CREATE TABLE weblog (          "
"        ip_str       TEXT,         " /*  0 */
"        login        TEXT HIDDEN,  " /*  1 */
"        user         TEXT,         " /*  2 */
"        time_str     TEXT,         " /*  3 */
"        req          TEXT,         " /*  4 */
"        result       INTEGER,      " /*  5 */
"        bytes        INTEGER,      " /*  6 */
"        ref          TEXT,         " /*  7 */
"        agent        TEXT,         " /*  8 */
#define TABLE_COLS_SCAN              9
"        ip_int       INTEGER,      " /*  9 */
"        time_day     INTEGER,      " /* 10 */
"        time_mon_s   TEXT,         " /* 11 */
"        time_mon     INTEGER,      " /* 12 */
"        time_year    INTEGER,      " /* 13 */
"        time_hour    INTEGER,      " /* 14 */
"        time_min     INTEGER,      " /* 15 */
"        time_sec     INTEGER,      " /* 16 */
"        req_op       TEXT,         " /* 17 */
"        req_url      TEXT,         " /* 18 */
"        line         TEXT HIDDEN   " /* 19 */
"     );                            ";
#define TABLE_COLS                   20

Возможно, вы заметили, что в некоторых столбцах есть ключевое слово HIDDEN. Это ключевое слово допустимо только для определений виртуальных таблиц. Любой столбец, помеченный как HIDDEN, не будет возвращен запросами в стиле SELECT * FROM... Вы можете явно запросить столбец, но по умолчанию он не возвращается. Это очень похоже на поведение столбца ROWID в стандартных таблицах. В нашем случае мы пометили столбцы login и line как HIDDEN. Столбец login почти никогда не содержит действительных данных, в то время как столбец line является избыточным (и большим). Столбцы есть, если они вам нужны, но в большинстве случаев люди не заинтересованы в их просмотре. Чтобы общий вывод был более чистым, я решил скрыть их.

Отключение и уничтожение

Как и в случае с xConnect() и xCreate(), функции веб-журнала xDisconnect() и xDestroy() используют одну и ту же реализацию:

static int weblog_disconnect( sqlite3_vtab *vtab )
{
    sqlite3_free( ((weblog_vtab*)vtab)->filename );
    sqlite3_free( vtab );
    return SQLITE_OK;
}

Освободите память, используемую для имени файла, освободите память, используемую структурой vtab, и вернитесь. Просто и легко.

Другие табличные функции

Последний набор функций на уровне таблицы включает xBestIndex(), xFindFunction(), xRename() и xUpdate(), а также четыре транзакционные функции: xBegin(), xSync(), xCommit() и xRollback(). Функция xFindFunction() является необязательной, и модуль weblog не может ее использовать, поэтому реализации этой функции нет. Поскольку это модуль только для чтения, то же самое верно и для xUpdate(). Точно так же транзакционные функции также являются необязательными и не требуются для модулей только для чтения. Для функций уровня таблицы остается только xRename() и xBestIndex().

Функция xRename() обязательна, но поскольку модуль не использует имя экземпляра виртуальной таблицы, это, по сути, не работает:

static int weblog_rename( sqlite3_vtab *vtab, const char *newname )
{
    return SQLITE_OK;
}

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

Последняя функция, xBestIndex(), обязательна, но на самом деле она не возвращает никаких полезных данных:

static int weblog_bestindex( sqlite3_vtab *vtab, sqlite3_index_info *info )
{
    return SQLITE_OK;
}

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

Открытие и закрытие

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

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

#define LINESIZE 4096

#define LINESIZE 4096
typedef struct weblog_cursor_s {
    sqlite3_vtab_cursor   cur;               /* this must be first */

    FILE           *fptr;                    /* used to scan file */
    sqlite_int64   row;                      /* current row count (ROWID) */
    int            eof;                      /* EOF flag */

    /* per-line info */
    char           line[LINESIZE];           /* line buffer */
    int            line_len;                 /* length of data in buffer */
    int            line_ptrs_valid;          /* flag for scan data */
    char           *(line_ptrs[TABLE_COLS]); /* array of pointers */
    int            line_size[TABLE_COLS];    /* length of data for each pointer */
} weblog_cursor;

Второй блок имеет дело с данными, которые нам нужны для сканирования файла журнала. Модуль weblog использует стандартные функции библиотеки C f (такие как fopen()) для открытия и сканирования файла журнала. Каждому курсору weblog нужен уникальный указатель FILE, так же как каждому курсору dblist требуется уникальная структура оператора. Модуль использует структуру FILE для отслеживания своего местоположения в файле, поэтому каждому курсору нужна собственная уникальная структура FILE. Курсору необходимо отслеживать количество строк, прочитанных из файла, поскольку это значение используется как ROWID. Наконец, курсору нужен флаг EOF, чтобы указать, когда он достиг конца файла.

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

Третий раздел структуры weblog_cursor содержит все, что нужно знать курсору о текущей строке. Курсор имеет буфер для хранения текста и длины текущей строки. Есть также ряд указателей и счетчиков длины, которые используются для сканирования строки. Поскольку сканирование строки довольно дорогое и должно выполняться сразу, модуль откладывает сканирование строки до тех пор, пока не убедится, что данные необходимы. После сканирования модуль будет хранить данные сканирования до тех пор, пока не прочитает новую строку. Чтобы отслеживать, когда строка была просканирована, курсор содержит флаг «valid».

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

Вы могли подумать, что строчный буфер размером 4 КБ кажется немного большим, но часто этого недостаточно. Сценарии CGI, использующие обширные строки запроса, могут генерировать очень длинные строки файла журнала. Другая проблема заключается в том, что многие URL-адреса рефереров, особенно из поисковых систем, могут быть очень большими. Хотя большинство строк содержат всего около сотни символов, лучше всего, если модуль сможет попытаться справиться и с более длинными. Даже с буфером 4 КБ вам необходимо правильно справиться с потенциальным переполнением буфера.

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

static int weblog_open( sqlite3_vtab *vtab, sqlite3_vtab_cursor **cur )
{
    weblog_vtab     *v = (weblog_vtab*)vtab;
    weblog_cursor   *c;
    FILE            *fptr;

    *cur = NULL;

    fptr = fopen( v->filename, "r" );
    if ( fptr == NULL ) return SQLITE_ERROR;

    c = sqlite3_malloc( sizeof( weblog_cursor ) );
    if ( c == NULL ) {
        fclose( fptr );
        return SQLITE_NOMEM;
    }

    c->fptr = fptr;
    *cur = (sqlite3_vtab_cursor*)c;
    return SQLITE_OK;
}

Функция open не нуждается в инициализации строковых данных, так как все они будут сброшены, когда мы прочитаем первую строку из файла данных.

Функция xClose() относительно проста:

static int weblog_close( sqlite3_vtab_cursor *cur )
{
    if ( ((weblog_cursor*)cur)->fptr != NULL ) {
        fclose( ((weblog_cursor*)cur)->fptr );
    }
    sqlite3_free( cur );
    return SQLITE_OK;
}

Закройте файл, освободите память.

Фильтр

Поскольку модуль веб-журнала предпочитает игнорировать функцию xBestIndex(), он также в значительной степени игнорирует xFilter(). На всякий случай файл сбрасывается в начало, и модуль считывает первую строку данных:

static int weblog_filter( sqlite3_vtab_cursor *cur,
        int idxnum, const char *idxstr,
        int argc, sqlite3_value **value )
{
    weblog_cursor   *c = (weblog_cursor*)cur;

    fseek( c->fptr, 0, SEEK_SET );
    c->row = 0;
    c->eof = 0;
    return weblog_get_line( (weblog_cursor*)cur );
}

Функция weblog_get_line() считывает одну строку из файла журнала и копирует ее в наш строковый буфер. Она также проверяет наличие полной строки. Если она не получила полную строку, функция продолжает чтение (но отбрасывает ввод), чтобы убедиться, что расположение файла осталось в начале следующей допустимой строки. Мы можем уменьшить частоту этого, увеличив буфер строки, но независимо от того, насколько большим мы делаем буфер, всегда полезно убедиться, что вся строка потребляется, даже если отбрасывается хвост:

static int weblog_get_line( weblog_cursor *c )
{
    char   *cptr;
    int    rc = SQLITE_OK;

    c->row++;                 /* advance row (line) counter */
    c->line_ptrs_valid = 0;   /* reset scan flag */
    cptr = fgets( c->line, LINESIZE, c->fptr );
    if ( cptr == NULL ) { /* found the end of the file/error */
        if ( feof( c->fptr ) ) {
            c->eof = 1;
        } else {
            rc = -1;
        }
        return rc;
    }
    /* find end of buffer and make sure it is the end a line... */
    cptr = c->line + strlen( c->line ) - 1;       /* find end of string */
    if ( ( *cptr != '\n' )&&( *cptr != '\r' ) ) { /* overflow? */
        char   buf[1024], *bufptr;
        /* ... if so, keep reading */
        while ( 1 ) {
            bufptr = fgets( buf, sizeof( buf ), c->fptr );
            if ( bufptr == NULL ) { /* found the end of the file/error */
                if ( feof( c->fptr ) ) {
                    c->eof = 1;
                } else {
                    rc = -1;
                }
                break;
            }
            bufptr = &buf[ strlen( buf ) - 1 ];
            if ( ( *bufptr == '\n' )||( *bufptr == '\r' ) ) {
                break;              /* found the end of this line */
            }
        }
    }

    while ( ( *cptr == '\n' )||( *cptr == '\r' ) ) {
        *cptr-- = '\0';    /* trim new line characters off end of line */
    }
    c->line_len = ( cptr - c->line ) + 1;
    return rc;
}

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

Строки и столбцы

У нас осталось всего несколько функций. В частности, модулю необходимо определить только две функции обработки строк, xNext() и xEof(). Нам также нужны две функции для столбцов: xRowid() и xColumn().

Три из этих четырех функций довольно просты. Функция xNext() может вызывать weblog_get_line(), как и функция xFilter(). Функции xEof() и xRowid() возвращают или передают значения, которые уже были вычислены в другом месте:

static int weblog_next( sqlite3_vtab_cursor *cur )
{
    return weblog_get_line( (weblog_cursor*)cur );
}

static int weblog_eof( sqlite3_vtab_cursor *cur )
{
    return ((weblog_cursor*)cur)->eof;
}

static int weblog_rowid( sqlite3_vtab_cursor *cur, sqlite3_int64 *rowid )
{
    *rowid = ((weblog_cursor*)cur)->row;
    return SQLITE_OK;
}

Интересной функцией является функция xColumn(). Если вы помните, в дополнение к строковому буферу структура weblog_cursor также имела массив символьных указателей и значений длины. Каждый из этих указателей и длин соответствует значению столбца в определенном формате таблицы. Прежде чем модуль сможет извлечь эти значения, ему необходимо просканировать строку ввода и пометить все столбцы, установив значения указателя и длины.

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

Функция, которая устанавливает все эти указатели и вычисления длины, - это weblog_scanline(). Мы проработаем этот раздел за разделом. Вверху, конечно же, находятся определения переменных. Указатели start и end будут использоваться для сканирования строчного буфера, в то время как значение next отслеживает завершающий символ для текущего поля:

static int weblog_scanline( weblog_cursor *c )
{
    char  *start = c->line, *end = NULL, next = ' ';
    int   i;

    /* clear pointers */
    for ( i = 0; i < TABLE_COLS; i++ ) {
        c->line_ptrs[i] = NULL;
        c->line_size[i] = -1;
    }

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

Затем функция сканирования перебирает собственные поля данных в строке. Она сканирует до девяти полей из строкового буфера. Эти поля соответствуют всем основным полям в файле журнала формата combine. Если файл журнала является файлом формата common (всего с семью полями) или если строковый буфер был обрезан, сканируется меньшее количество полей. Любые поля, которые не просканированы должным образом, в конечном итоге вернут SQL значения NULL:

/* process actual fields */
for ( i = 0; i < TABLE_COLS_SCAN; i++ ) {
    next = ' ';
    while ( *start == ' ' ) start++;     /* trim whitespace */
    if (*start == '\0' ) break;          /* found the end */
    if (*start == '"' ) {
        next = '"'; /* if we started with a quote, end with one */
        start++;
    }
    else if (*start == '[' ) {
        next = ']'; /* if we started with a bracket, end with one */
        start++;
    }
    end = strchr( start, next );   /* find end of this field */
    if ( end == NULL ) {           /* found the end of the line */
        int     len = strlen ( start );
        end = start + len;         /* end now points to '\0' */
    }
    c->line_ptrs[i] = start;       /* record start */
    c->line_size[i] = end - start; /* record length */
    while ( ( *end != ' ' )&&( *end != '\0' ) ) end++; /* find end */
    start = end;
}

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

Когда этот цикл завершается, код пытается установить указатели первых девяти столбцов. Они составляют собственные поля файла журнала. Следующим шагом является установка указателей и длин для дополнительных 11 столбцов, которые представляют подполя и альтернативные представления. Первое дополнительное значение - это IP-адрес, возвращенный как целое число. Эта функция не выполняет преобразование данных, поэтому можно сделать прямую копию указателя и длины из первого столбца:

/* process special fields */
/* ip_int - just copy */
c->line_ptrs[9] = c->line_ptrs[0];
c->line_size[9] = c->line_size[0];

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

/* assumes: "DD/MMM/YYYY:HH:MM:SS zone" */
/*     idx:  012345678901234567890...   */
if (( c->line_ptrs[3] != NULL )&&( c->line_size[3] >= 20 )) {
    start = c->line_ptrs[3];
    c->line_ptrs[10] = &start[0];    c->line_size[10] = 2;
    c->line_ptrs[11] = &start[3];    c->line_size[11] = 3;
    c->line_ptrs[12] = &start[3];    c->line_size[12] = 3;
    c->line_ptrs[13] = &start[7];    c->line_size[13] = 4;
    c->line_ptrs[14] = &start[12];   c->line_size[14] = 2;
    c->line_ptrs[15] = &start[15];   c->line_size[15] = 2;
    c->line_ptrs[16] = &start[18];   c->line_size[16] = 2;
}

Следующим шагом после полей даты является извлечение операции HTTP и URL-адреса. Они извлекаются как первые два подполя поля журнала HTTP-запросов. Код играет в некоторые игры, чтобы убедиться, что он случайно не передаст указатель NULL в strchr(), но в противном случае он просто находит первые два пробела и считает их окончанием двух полей, которые он пытается извлечь:

/* req_op, req_url */
start = c->line_ptrs[4];
end = ( start == NULL ? NULL : strchr( start, ' ' ) );
if ( end != NULL ) {
    c->line_ptrs[17] = start;
    c->line_size[17] = end - start;
    start = end + 1;
}
end = ( start == NULL ? NULL : strchr( start, ' ' ) );
if ( end != NULL ) {
    c->line_ptrs[18] = start;
    c->line_size[18] = end - start;
}

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

    /* line */
    c->line_ptrs[19] = c->line;
    c->line_size[19] = c->line_len;
    
    c->line_ptrs_valid = 1;
    return SQLITE_OK;
}

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

Первое, что делает код xColumn(), - это проверяет, была ли строка уже просканирована. Если нет, код вызывает weblog_scanline() для установки всех указателей полей:

static int weblog_column( sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int cidx )
{
    weblog_cursor    *c = (weblog_cursor*)cur;

    if ( c->line_ptrs_valid == 0 ) {
        weblog_scanline( c );         /* scan line, if required */
}
    if ( c->line_size[cidx] < 0 ) {   /* field not scanned and set */
    sqlite3_result_null( ctx );
    return SQLITE_OK;
}

Затем, если запрошенный столбец не имеет допустимого набора значений, модуль возвращает SQL NULL для столбца.

Затем код обрабатывает столбцы с конкретными потребностями преобразования. Любой столбец, который требует специальной обработки или преобразования, будет перехвачен этим оператором switch. Первый специализированный столбец - это целочисленная версия IP-адреса. Этот блок кода преобразует каждый октет IP-адреса в целое число. Единственная проблема заключается в том, что все целочисленные значения в SQLite подписаны, поэтому код должен быть осторожен при преобразовании значения в 64-битное целое число. Для максимальной совместимости избегается использование операций сдвига:

switch( cidx ) {
case 9: { /* convert IP address string to signed 64 bit integer */
    int            i;
    sqlite_int64   v = 0;
    char           *start = c->line_ptrs[cidx], *end, *oct[4];

    for ( i = 0; i < 4; i++ ) {
        oct[i] = start;
        end = ( start == NULL ? NULL : strchr( start, '.' ) );
        if ( end != NULL ) {
            start = end + 1;
        }
    }
    v += ( oct[3] == NULL ? 0 : atoi( oct[3] ) ); v *= 256;
    v += ( oct[2] == NULL ? 0 : atoi( oct[2] ) ); v *= 256;
    v += ( oct[1] == NULL ? 0 : atoi( oct[1] ) ); v *= 256;
    v += ( oct[0] == NULL ? 0 : atoi( oct[0] ) );
    sqlite3_result_int64( ctx, v );
    return SQLITE_OK;
}

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

case 12: {
    int m = 0;
         if ( strncmp( c->line_ptrs[cidx], "Jan", 3 ) == 0 ) m =  1;
    else if ( strncmp( c->line_ptrs[cidx], "Feb", 3 ) == 0 ) m =  2;
    else if ( strncmp( c->line_ptrs[cidx], "Mar", 3 ) == 0 ) m =  3;
    else if ( strncmp( c->line_ptrs[cidx], "Apr", 3 ) == 0 ) m =  4;
    else if ( strncmp( c->line_ptrs[cidx], "May", 3 ) == 0 ) m =  5;
    else if ( strncmp( c->line_ptrs[cidx], "Jun", 3 ) == 0 ) m =  6;
    else if ( strncmp( c->line_ptrs[cidx], "Jul", 3 ) == 0 ) m =  7;
    else if ( strncmp( c->line_ptrs[cidx], "Aug", 3 ) == 0 ) m =  8;
    else if ( strncmp( c->line_ptrs[cidx], "Sep", 3 ) == 0 ) m =  9;
    else if ( strncmp( c->line_ptrs[cidx], "Oct", 3 ) == 0 ) m = 10;
    else if ( strncmp( c->line_ptrs[cidx], "Nov", 3 ) == 0 ) m = 11;
    else if ( strncmp( c->line_ptrs[cidx], "Dec", 3 ) == 0 ) m = 12;
    else break;    /* give up, return text */
    sqlite3_result_int( ctx, m );
    return SQLITE_OK;
}

Есть ряд дополнительных столбцов (включая некоторые из «собственных»), которые возвращаются как целые числа. Ни один из этих столбцов не требует специальной обработки, кроме преобразования строки в целое число. Для этого преобразования используется стандартная функция atoi(). Хотя указатели строк не оканчиваются нулем, функция atoi() автоматически вернется, как только встретит нечисловой символ. Поскольку все эти поля ограничены пробелами или другими символами, это работает именно так, как мы хотим:

    case  5: /* result code */
    case  6: /* bytes transfered */
    case 10: /* day-of-month */
    case 13: /* year */
    case 14: /* hour */
    case 15: /* minute */
    case 16: /* second */
        sqlite3_result_int( ctx, atoi( c->line_ptrs[cidx] ) );
        return SQLITE_OK;
    default:
        break;
    }

    sqlite3_result_text( ctx, c->line_ptrs[cidx],
                              c->line_size[cidx], SQLITE_STATIC );
    return SQLITE_OK;
}

Наконец, любое поле, не требующее специальной обработки, возвращается как текстовое значение. Хотя буфер строки будет перезаписан при чтении следующей строки, указатель данных, переданный в sqlite3_result_text(), должен оставаться действительным только до следующего вызова xNext(). Это позволяет модулю использовать флаг SQLITE_STATIC.

Таким образом, мы определили все необходимые функции для нашего модуля weblog.

Регистрирация модуля

Теперь, когда мы увидели, как реализованы все функции модуля, последнее, что нужно сделать, это зарегистрировать модуль weblog как часть функции инициализации расширения:

static sqlite3_module weblog_mod = {
    1,                  /* iVersion        */
    weblog_connect,     /* xCreate()       */
    weblog_connect,     /* xConnect()      */
    weblog_bestindex,   /* xBestIndex()    */
    weblog_disconnect,  /* xDisconnect()   */
    weblog_disconnect,  /* xDestroy()      */
    weblog_open,        /* xOpen()         */
    weblog_close,       /* xClose()        */
    weblog_filter,      /* xFilter()       */
    weblog_next,        /* xNext()         */
    weblog_eof,         /* xEof()          */
    weblog_column,      /* xColumn()       */
    weblog_rowid,       /* xRowid()        */
    NULL,               /* xUpdate()       */
    NULL,               /* xBegin()        */
    NULL,               /* xSync()         */
    NULL,               /* xCommit()       */
    NULL,               /* xRollback()     */
    NULL,               /* xFindFunction() */
    weblog_rename       /* xRename()       */
}; 

int weblog_init( sqlite3 *db, char **error, const sqlite3_api_routines *api )
{
    SQLITE_EXTENSION_INIT2(api);
    return sqlite3_create_module( db, "weblog", &weblog_mod, NULL );
}

Поскольку не предпринимается попыток создать экземпляр таблицы weblog, эта функция инициализации немного проще, чем в предыдущем примере dblist.

Пример использования

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

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

Чтобы продемонстрировать, как работает этот модуль, администраторы сервера http://oreilly.com/ были достаточно любезны и предоставили мне некоторые из своих файлов журналов. Файл oreilly.com_access.log представляет собой combine файл журнала Apache со 100 000 строками данных. После компиляции и сборки в загружаемый модуль мы можем импортировать модуль веб-журнала и создать виртуальную таблицу, привязанную к этому файлу, используя следующие команды:

sqlite> .load weblog.sqlite3ext weblog_init
sqlite> CREATE VIRTUAL TABLE log USING weblog( oreilly.com_access.log );

Затем мы отправляем запросы, чтобы посмотреть на различные аспекты файла. Например, если мы хотим узнать, какой URL-адрес является наиболее распространенным, мы запускаем такой запрос:

sqlite> SELECT count(*) AS Count, req_url AS URL FROM log
   ...>   GROUP BY 2 ORDER BY 1 DESC LIMIT 8;

Count  URL
-----  ----------------------------------------
2490   /images/oreilly/button_cart.gif
2480   /images/oreilly/button_acct.gif
2442   /styles/all.css
2348   /images/oreilly/888-line.gif
2233   /styles/chrome.css
2206   /favicon.ico
1975   /styles/home2.css
1941   /images/oreilly/satisfaction-icons.gif

Довольно часто можно увидеть favicon.ico очень близко к верху, вместе с любыми CSS и файлами изображений для всего сайта. В случае небольших сайтов с гораздо меньшим объемом трафика наиболее часто запрашиваемым URL-адресом является /robots.txt, который используется поисковыми системами.

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

sqlite> SELECT sum(bytes) AS Bytes, count(*) AS Count, req_url AS URL
   ...>   FROM log WHERE result = 200 GROUP BY 3 ORDER BY 1 DESC LIMIT 8;

Bytes     Count  URL
--------  -----  ---------------------------------------------
46502163  1137   /images/oreilly/mac_os_x_snow_leopard-148.jpg
40780252  695    /
37171328  2384   /styles/all.css
35403200  2180   /styles/chrome.css
31728906  781    /catalog/assets/pwr/engine/js/full.js
31180460  494    /catalog/9780596510046/index.html
21573756  88     /windows/archive/PearPC.html
21560154  3      /catalog/dphotohdbk/chapter/ch03.pdf

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

И последний пример. Это показывает, с каких IP-адресов загружается наибольшее количество уникальных элементов. Поскольку это данные в реальном времени, я изменил IP-адреса:

sqlite> SELECT count(*) AS Uniq, sum(sub_count) AS Ttl,
   ...>   sum(sub_bytes) AS TtlBytes, sub_ip AS IP
   ...>   FROM (SELECT count(*) AS sub_count, sum(bytes) AS sub_bytes,
   ...>   ip_str AS sub_ip FROM log GROUP BY 3, req_url)
   ...>   GROUP BY 4 ORDER BY 1 DESC LIMIT 8;

Uniq  Ttl   TtlBytes    IP
----  ----  ----------  ------------------
1295  1295  31790418    10.5.69.83
282   334   13571771    10.170.13.97
234   302   4234382     10.155.7.28
213   215   3089112     10.155.7.77
163   176   2550477     10.155.7.29
159   161   4279779     10.195.137.175
153   154   2292407     10.23.146.198
135   171   2272949     10.155.7.71

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

Есть бесчисленное множество других запросов, которые мы могли бы выполнить. Для тех, кто когда-либо импортировал данные журнала в базу данных SQL и экспериментировал с ними, ничто из этого не особенно вдохновляет. Но задумайтесь на мгновение: время запроса для первых двух из этих примеров составляет чуть меньше пяти секунд на настольной системе эконом-класса, возраст которой составляет несколько лет. Третий запрос был немного ближе к восьми секундам.

Пять секунд на сканирование таблицы из 100000 строк могут быть не очень быстрыми темпами, но помните, что эти пять секунд - это общий итог для всего, включая «импорт» данных. Использование модуля виртуальной таблицы позволяет нам перейти от необработанного файла журнала со 100 000 строками к ответу на запрос всего за это время - без промежуточного хранения данных, без преобразования формата, без импорта данных. Это важно, поскольку импорт требует большого количества операций ввода-вывода и может быть медленным процессом. Например, импорт того же файла в стандартную таблицу SQLite более традиционными способами занимает почти минуту, и это даже не включает никаких запросов!

Теперь представьте, что мы задействуем всю эту функциональность с менее чем 400 строками кода C. Доступ к исходным данным вместо их импорта в стандартные таблицы позволяет значительно ускорить процесс сквозного анализа данных и позволяет запрашивать данные в том виде, в котором они записаны веб-сервером, в режиме реального времени. В качестве дополнительного бонуса виртуальную таблицу также можно использовать в качестве импортера с помощью команд SQL CREATE TABLE... AS или INSERT... SELECT.

Если вы столкнулись с задачей написать сценарий для анализа, поиска или обобщения некоторого структурированного источника данных, вы можете вместо этого подумать о написании модуля SQLite. Базовый модуль только для чтения - это довольно незначительный проект, и как только вы его внедрите, в ваше распоряжение будет вся мощь движка базы данных SQLite (плюс добавленный импортер данных!). Это позволяет легко писать, тестировать и настраивать любые запросы, которые вам нужны, всего в нескольких строках SQL.

Лучшие Index и Filter

Давайте подробнее рассмотрим функции xBestIndex() и xFilter(). Оба наших примера модулей были довольно простыми и не использовали их, но правильное использование этих функций критически важно для внутренних модулей, реализующих некоторые типы высокопроизводительной системы индексирования.

Цель и необходимость

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

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

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

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

Назначение функций xBestIndex() и xFilter() - восполнить этот пробел. Когда инструкция SQL подготовлена, оптимизатор запросов может вызывать xBestIndex() несколько раз, представляя несколько различных возможностей запроса. Это позволяет модулю сформулировать свой собственный план запроса и передать приблизительную метрику стоимости оптимизатору запросов. Оптимизатор запросов будет использовать эту информацию для выбора конкретного плана запроса.

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

xBestIndex()

Если вы помните, функция xBestIndex() - это функция уровня таблицы, которая выглядит следующим образом:

int xBestIndex( sqlite3_vtab *vtab, sqlite3_index_info *idxinfo );

Весь ключ к этой функции - это структура sqlite3_index_info. Эта структура разделена на две части. Первый раздел предоставляет ряд входных данных для вашей функции, позволяя SQLite предлагать план запроса модулю. Раздел ввода следует рассматривать как доступный только для чтения.

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

Раздел ввода состоит из двух значений размера и двух массивов. Целое число nConstraint указывает, сколько элементов находится в массиве aConstraint[]. Точно так же целое число nOrderBy[] указывает, сколько элементов находится в массиве aOrderBy[]:

struct sqlite3_index_info {
    /**** Inputs ****/
    int      nConstraint;              /* Number of entries in aConstraint */
    struct sqlite3_index_constraint {
        int             iColumn;       /* Column on lefthand side of constraint */
        unsigned char   op;            /* Constraint operator */
        unsigned char   usable;        /* True if this constraint is usable */
        int             iTermOffset;   /* Used internal - xBestIndex should ignore */
    } *aConstraint;                    /* Table of WHERE clause constraints */

    int      nOrderBy;                 /* Number of terms in the ORDER BY clause */
    struct sqlite3_index_orderby {
        int             iColumn;       /* Column number */
        unsigned char   desc;          /* True for DESC. False for ASC. */
    } *aOrderBy;                       /* The ORDER BY clause */

Массив aConstraint[] сообщает серию простых ограничений, которые запрос может наложить на виртуальную таблицу. Каждый элемент массива определяет одно ограничение запроса, передавая значения для индекса столбца (aConstraint[i].iColumn) и оператора ограничения (aConstraint [i].op). Индекс столбца относится к столбцам виртуальной таблицы, где ноль означает первый столбец. Индекс -1 указывает, что ограничение применяется к виртуальному столбцу ROWID.

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

SQLITE_INDEX_CONSTRAINT_EQ      /* COL = Expression */
SQLITE_INDEX_CONSTRAINT_GT      /* COL > Expression */
SQLITE_INDEX_CONSTRAINT_LE      /* COL <= Expression */
SQLITE_INDEX_CONSTRAINT_LT      /* COL < Expression */
SQLITE_INDEX_CONSTRAINT_GE      /* COL >= Expression */
SQLITE_INDEX_CONSTRAINT_MATCH   /* COL MATCH Expression */

Например, если один из элементов aConstraint имел значения:

aConstraint[i].iColumn = -1;
aConstraint[i].op      = SQLITE_INDEX_CONSTRAINT_LE;

Это примерно соответствует предложению WHERE:

...WHERE ROWID <= ?

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

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

Второй массив входной секции, aOrderBy[], передает набор запрошенных сортировок ORDER BY (он также может быть сгенерирован столбцами в предложении GROUP BY). Каждый элемент упорядочивания определяется индексом столбца и направлением (по возрастанию или по убыванию). Индексы столбцов работают так же, с определенными столбцами, начинающимися с 0 и -1, относящимися к ROWID. Элементы упорядочения следует рассматривать как серию аргументов ORDER BY, при этом весь набор данных сортируется по первому порядку, затем подмножества равных значений сортируются по второму порядку, и так далее.

Раздел вывода содержит данные, которые возвращаются оптимизатору SQLite. Он состоит из массива ограничений и набора значений. Массив aConstraintUsage[] всегда будет того же размера, что и массив aConstraint[] (то есть всегда будет иметь элементы ограничения nCon). SQLite всегда обнуляет память, используемую секцией вывода. Вот почему можно безопасно игнорировать структуру в упрощенных реализациях xBestIndex() - структура в основном предустановлена на ответ «этот модуль не может ничего оптимизировать». В этом случае для каждого запроса виртуальной таблицы потребуется полное сканирование таблицы:

    /**** Outputs ****/
    struct sqlite3_index_constraint_usage {
        int             argvIndex; /* If >0,constraint is part of argv to xFilter */
        unsigned char   omit;      /* Do not code a test for this constraint */
    } *aConstraintUsage;
    
    int      idxNum;               /* Number used to identify the index */
    char     *idxStr;              /* Application-defined string */
    int      needToFreeIdxStr;     /* Free idxStr using sqlite3_free() if true */
    int      orderByConsumed;      /* True if output is already ordered */
    double   estimatedCost;        /* Estimated cost of using this index */
};

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

Каждый элемент массива aConstraintUsage[] соответствует одному и тому же упорядоченному элементу массива aConstraint[]. Для каждого ограничения, описанного в элементе aConstraint[], соответствующий элемент aConstraintUsage[] используется для описания того, как модуль хочет применить ограничение.

Значение argvIndex используется для указания SQLite, что вы хотите, чтобы значение выражения этого ограничения (то есть значение в правой части ограничения) было передано в функцию xFilter() в качестве одного из параметров argv. Значения argvIndex используются для определения последовательности каждого значения выражения. Любому элементу aConstraintUsage[] может быть присвоено любое значение индекса, при условии, что набор назначенных значений индекса начинается с единицы и не имеет пробелов. Никакие элементы aConstraintUsage[] не должны иметь одно и то же ненулевое значение argvIndex. Если возвращается нулевое значение argvIndex по умолчанию, значение выражения не становится доступным для функции xFilter(). То, как именно это используется, станет более понятным, если мы более внимательно посмотрим на xFilter().

Поле omit элемента aConstraintUsage[] используется для указания библиотеке SQLite, что модуль виртуальной таблицы возьмет на себя ответственность за соблюдение этого ограничения и что SQLite может пропустить процесс проверки для этого ограничения. По умолчанию SQLite проверяет ограничения каждой строки, возвращаемой виртуальной таблицей (например, каждая строка xNext() останавливается). Установка поля omit заставит SQLite пропустить процесс проверки для этого ограничения.

За массивом ограничений следует ряд полей. Первые три поля используются для связи с функцией xFilter(). Поля idxNum и idxStr могут использоваться модулем по своему усмотрению. Механизм SQLite не использует эти поля, кроме как передать их обратно в xFilter(). Третье поле, needToFreeIdxStr, является флагом, который указывает библиотеке SQLite, что память, на которую указывает idxStr, была динамически выделена sqlite3_malloc(), и библиотека SQLite должна освободить эту память с помощью sqlite3_free(), если библиотека решит, что она больше не требуется.

Этот флаг нужен для предотвращения утечек памяти. Помните, что xBestIndex() может вызываться несколько раз как часть процесса подготовки для оператора SQL. Модуль обычно возвращает уникальное значение idxStr для каждого предлагаемого плана запроса. Однако только одно из этих значений idxStr будет передано в xFilter(), а остальные должны быть отброшены. Это означает, что любая строка (или другой блок памяти), которую вы предоставляете idxStr, должна быть либо статической памятью, либо память должна быть выделена с помощью sqlite3_malloc() и должен быть установлен флаг needToFreeIdxStr. Это позволяет библиотеке SQLite правильно очищать любые неиспользуемые выделения idxStr.

Поле orderByConsumed используется, чтобы указать, что модуль может возвращать предварительно отсортированные данные в порядке, определенном массивом aOrderBy. Это флаг "все или ничего". Если заданы три элемента aOrderBy, но модуль может сортировать вывод только по первому столбцу, он должен возвращать ложное значение.

Наконец, поле EstimatedCost используется для передачи значения стоимости обратно в библиотеку SQLite. Если это внешний модуль, это число должно приблизительно соответствовать общему количеству обращений к диску, необходимых для возврата всех строк, соответствующих указанным ограничениям. Если это внутренний модуль, это может быть приблизительное количество вызовов sqlite3_step() и sqlite3_column_xxx(). В ситуациях, когда требуется полное сканирование таблицы, он может оценить количество строк в виртуальной таблице. Точное измерение не имеет особого смысла, кроме относительных значений между различными вызовами xBestIndex().

xFilter()

Функция xFilter() позволяет библиотеке SQLite уведомлять модуль в контексте конкретного курсора таблицы о том, какие именно ограничения и порядок следует применить к следующему сканированию таблицы. Напомним, что прототип xFilter() выглядит так:

int xFilter( sqlite3_vtab_cursor *cursor,
        int idxNum, const char *idxStr,
        int argc, sqlite3_value **argv )

Первый аргумент - это курсор таблицы, для которого требуются эти ограничения. Значения idxNum и idxStr - это те же значения, которые были переданы модулем в предыдущем вызове xBestIndex(). Они означают все, что хочет модуль, до тех пор, пока код в xBestIndex() и xFilter() согласен с тем, что они собой представляют и что представляют собой значения.

Наконец, последние два аргумента являются производными от значений aConstraintUsage[].argvIndex, переданных модулем обратно. Параметр argv представляет собой массив структур sqlite3_value, а параметр argc указывает, сколько элементов находится в массиве argv.

Возвращаясь к нашему предыдущему примеру, рассмотрим структуру sqlite3_index_info с элементом aConstraint[i], где iColumn=-1 и op=SQLITE_INDEX_CONSTRAINT_LE (что указывает на ограничение ROWID>=?). Если функция модуля xBestIndex() устанавливает aConstraintUsage[i].argIndex значение 1, значение argv[0], переданное в xFilter(), будет иметь значение, найденное в правой части выражения.

Обратите внимание, что индексы аргументов между xBestIndex() и xFilter() отличаются на единицу. Поскольку sqlite3_index_info считает значение aConstraintUsage[].argvIndex равным 0, чтобы указать недопустимый индекс, значения argvIndex начинаются с 1. Однако фактические индексы argv будут на единицу меньше, поскольку они начинаются с 0.

Используя значения idxNum, idxStr и argv, xFilter() отвечает за настройку этого табличного курсора для обеспечения правильных ограничений и порядка, которые были обещаны соответствующим блоком sqlite3_index_info.

Типичное использование

Дизайн функций xBestIndex() и xFilter() сильно ориентирован на оптимизацию модулей внутреннего стиля. Это модули, которые будут использовать один или несколько операторов SQL, которые работают с набором внутренних таблиц для создания данных виртуальной таблицы. Это похоже на то, как работает модуль dblist, но обычно включает более сложные команды SQL.

Модуль может делать со значениями idxNum и idxStr все, что угодно, но большинство внутренних модулей используют их для передачи заранее созданных командных строк SQL. Каждый раз, когда вызывается xBestIndex(), модуль пытается выяснить, как он будет обслуживать ограничения запроса и ограничения порядка, путем добавления условий, ограничений и параметров ORDER BY во внутренние операторы SQL, используемые для генерации данных виртуальной таблицы. Функция xBestIndex() отмечает ограничения, которые она может использовать, и строит требуемые строки команд SQL с параметрами оператора. Эти команды SQL передаются обратно со значением idxStr. IdxNum может использоваться для передачи длины строки или какого-либо другого значения индекса, битовых флагов или чего угодно, что хочет модуль. Значения argvIndex элементов aConstraintUsage устанавливаются на соответствующие значения индекса параметра инструкции.

Когда вызывается xFilter(), значение idxStr будет иметь соответствующие строки команд SQL для этой конфигурации запроса. Затем можно подготовить командные строки SQL, а выражения ограничений передаются через массив argv и могут быть привязаны к любым параметрам оператора. Функция xFilter() начинает проходить подготовленные операторы, генерируя первую строку. Как и внутренний модуль dblist, последующие вызовы xNext() продолжают проходить через все внутренние операторы, возвращая дополнительные строки.

Пока xBestIndex() может получить разумный набор командных строк SQL, способных выражать требуемый внутренний запрос (или запросы), все это достаточно просто. При необходимости в xFilter() можно передать несколько командных строк SQL, определив их одну за другой в большой строке и используя хвостовой параметр sqlite3_prepare_xxx() для подготовки нескольких операторов, один за другим.

При работе с внешними модулями могут возникнуть трудности. Очень часто внешние модули не могут определять сложные условия запроса или порядок сортировки с помощью простой строки. Хотя указатель idxStr может использоваться для передачи некоторого типа структуры данных, может быть сложно закодировать всю информацию об ограничениях. Это одна из причин, по которой многие модули, и особенно внешние модули, отказываются от использования xBestIndex() и xFilter() и просто зависят от полного сканирования таблицы для всех операций. Полное сканирование таблицы может быть медленнее, но оно все равно работает.

Это может показаться плохим, но помните, что даже в стандартной таблице со стандартным индексом вы обычно не начинаете видеть действительно хорошую отдачу от использования индекса, если только ограничение и соответствующий индекс не могут исключить 80% или более строк. Тратить много времени на создание обработчика ограничений, который отфильтровывает только небольшой процент строк, обычно является неудачным предложением. Хотя в этом может заключаться вся суть внутренних модулей, основная цель большинства внешних модулей - просто обеспечить возможность подключения к данным. Если вы работаете над внешним модулем, сначала заставьте работать базовое преобразование данных, а затем подумайте о возможной реализации более эффективных шаблонов поиска.

Подведение итогов

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

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

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

Приложение A
Параметры сборки SQLite

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

Все эти директивы являются стандартными флагами C #define. Вы можете часто определять их в среде сборки вашей IDE как переменные среды (если вы компилируете из командной строки) или передавая один или несколько флагов вашему компилятору.

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

Директивы Shell

Существует только одна директива компилятора, относящаяся к исходному файлу shell.c и инструменту командной строки sqlite3. Эту директиву нужно определять только при сборке исходного кода shell.c. Фактическое ядро SQLite не распознает эту директиву.

См. также SQLITE_ENABLE_IOTRACE далее в этом приложении, которая включает команду .iotrace в sqlite3, но должна быть определена при компиляции как shell.c, так и sqlite3.c.

ENABLE_READLINE
Включить библиотеку GNU Readline
Стандартное использование
ENABLE_READLINE=1
По умолчанию
не определено (не используется)
Описание
Значение 1 позволяет использовать библиотеку GNU Readline. Когда это включено, библиотека Readline должна быть подключена как часть процесса сборки.

Значения по умолчанию

Следующие директивы компилятора используются для изменения значения по умолчанию для различных параметров базы данных. Эти директивы изменяют значения запуска по умолчанию. Большинство этих параметров можно изменить во время выполнения.

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

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

SQLITE_DEFAULT_AUTOVACUUM
Режим автоматического вакуумирования по умолчанию
Стандартное использование
SQLITE_DEFAULT_AUTOVACUUM=<0|1|2>
По умолчанию
0 (автоматический вакуум отключен)
Описание

Устанавливает режим автоматического вакуумирования по умолчанию для новых баз данных. Значение по умолчанию 0 полностью отключает функцию автоматического вакуумирования.

Значение Режим Смысл
0 Нет Автоматический вакуум отключен
1 Полный Автоматический вакуум включен, работает
2 Инкрементальный Автоматический вакуум включен, отложен
См. также
auto_vacuum [PRAGMA, Ap F]
SQLITE_DEFAULT_CACHE_SIZE
Максимальный размер кэша по умолчанию
Стандартное использование
SQLITE_DEFAULT_CACHE_SIZE=number-of-pages
По умолчанию
2000 страниц
Описание
Устанавливает максимальный размер кеша по умолчанию в страницах для новой базы данных.
См. также
default_cache_size [PRAGMA, Ap F], cache_size [PRAGMA, Ap F]
SQLITE_DEFAULT_FILE_FORMAT
Формат файла по умолчанию
Стандартное использование
SQLITE_DEFAULT_FILE_FORMAT=<1|4>
По умолчанию
1 (исходный/устаревший формат)
Описание

Устанавливает формат файла по умолчанию. SQLite 3.3.0 представил новый формат файла (4), который понимает убывающие индексы и использует более эффективное кодирование для логических значений (целые значения 0 и 1). Все версии SQLite после 3.3.0 могут читать и записывать файлы обоих форматов. Версии SQLite до 3.3.0 могут понимать только исходный (1) формат.

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

См. также
legacy_file_format [PRAGMA, Ap F]
SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT
Ограничение размера по умолчанию для постоянных журналов
Стандартное использование
SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT=bytes
По умолчанию
Не определено (неограниченно)
Описание
Устанавливает ограничение размера по умолчанию для файлов постоянного журнала. Когда эта директива не определена, ограничений нет. Этот предел может быть установлен во время выполнения.
См. также
journal_size_limit [PRAGMA, Ap F]
SQLITE_DEFAULT_MEMSTATUS
Конфигурация состояния памяти
Стандартное использование
SQLITE_DEFAULT_MEMSTATUS=<0|1>
По умолчанию
1 (включено и доступно)
Описание
Устанавливает конфигурацию по умолчанию (включена или выключена) системы состояния памяти. Состояние памяти должно быть доступно для функций API sqlite3_memory_used(), sqlite3_memory_highwater(), sqlite3_soft_heap_limit() или sqlite3_status() для предоставления достоверных данных. Систему состояния памяти можно включить или отключить во время выполнения с помощью sqlite3_config().
См. также
sqlite3_config() [C API, Ap G]
SQLITE_DEFAULT_PAGE_SIZE
Размер страницы базы данных по умолчанию
Стандартное использование
SQLITE_DEFAULT_PAGE_SIZE=bytes
По умолчанию
1024 байта
Описание
Устанавливает размер страницы базы данных по умолчанию в байтах. Значения должны быть степенью двойки от 512 до SQLITE_MAX_PAGE_SIZE, который по умолчанию равен 32 КБ (и является максимальным поддерживаемым значением). Возможные значения: 512, 1024, 2048, 4096, 8192, 16384 или 32768.
См. также
SQLITE_MAX_PAGE_SIZE, page_size [PRAGMA, Ap F]
SQLITE_DEFAULT_TEMP_CACHE_SIZE
Размер временного кэша
Стандартное использование
SQLITE_DEFAULT_TEMP_CACHE_SIZE=number-of-pages
По умолчанию
500 страниц
Описание
Устанавливает максимальный размер любого временного кэша страницы по умолчанию. Временные кэши страниц используются для хранения промежуточных результатов и других временных данных. Временные базы данных используют стандартный кеш страниц и не используют это значение. Невозможно изменить это значение во время выполнения.
YYSTACKDEPTH
Максимальная глубина стека анализатора
Стандартное использование
YYSTACKDEPTH=max-depth
По умолчанию
100
Описание
Устанавливает максимальную глубину стека анализатора, используемого для обработки операторов SQL. Для приложений очень необычно требовать значение больше 25. Если вы обнаружите, что увеличиваете это значение, вам, скорее всего, придется пересмотреть свой стиль SQL.

Размеры и ограничения

При переходе от SQLite 2 к SQLite 3 одной из основных целей разработки было устранение как можно большего количества ограничений. Алгоритмы и структуры данных были разработаны для масштабирования до экстремальных значений. К сожалению, такой подход «без границ» противоречил желанию провести всестороннее тестирование. Во многих случаях было трудно, если не невозможно, проверить крайние пределы. Это приводило к неудобной ситуации, когда SQLite был «известен» как надежный, если ваши шаблоны использования не выходили слишком далеко за пределы проверенных пределов. Если вы рискнули зайти слишком далеко на неизведанную территорию, стабильность и надежность были бы менее известны и менее понятны, и со временем был обнаружен ряд ошибок, когда вещи выходили за рамки разумных значений.

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

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

Хотя эти директивы времени компиляции используются для определения абсолютных максимумов, все эти ограничения могут быть снижены во время выполнения с помощью вызова API sqlite3_limit(). См. запись sqlite3_limit() в приложении G для получения дополнительной информации.

SQLITE_MAX_ATTACHED
Максимальное количество подключенных баз данных
Стандартное использование
SQLITE_MAX_ATTACHED=number-of-databases
По умолчанию
10 баз данных
Описание
Максимальное количество баз данных, которое может быть подключено к одному сеансу. Существует жесткое ограничение - 30.
SQLITE_MAX_COLUMN
Максимальное количество столбцов в любой структуре
Стандартное использование
SQLITE_MAX_COLUMN=number-of-columns
По умолчанию
2000 столбцов
Описание
Максимальное количество столбцов в любой таблице, индексе или представлении базы данных, а также в любых временных таблицах найденных в SELECT. Жесткий предел - 32 767.
SQLITE_MAX_COMPOUND_SELECT
Максимальное количество терминов в составном операторе
Стандартное использование
SQLITE_MAX_COMPOUND_SELECT=number-of-select-terms
По умолчанию
500 терминов
Описание
Максимальное количество терминов SELECT в составном операторе SELECT (оператор, в котором используется UNION, UNION ALL, INTERSECT или EXCEPT).
SQLITE_MAX_DEFAULT_PAGE_SIZE
Верхняя граница автоматического размера страницы
Стандартное использование
SQLITE_MAX_DEFAULT_PAGE_SIZE=bytes
По умолчанию
8192 байта
Описание
Обычно SQLite создает базу данных со страницами размером SQLITE_DEFAULT_PAGE_SIZE. Однако, если драйвер файловой системы указывает, что больший размер может обеспечить лучшую производительность, SQLite может выбрать другой размер страницы по умолчанию. В тех ситуациях, когда SQLite выбирает значение, отличное от значения по умолчанию, фактический размер страницы будет ограничен этим или меньшим размером.
См. также
SQLITE_DEFAULT_PAGE_SIZE
SQLITE_MAX_EXPR_DEPTH
Верхняя граница автоматического размера страницы
Стандартное использование
SQLITE_MAX_EXPR_DEPTH=stack-depth
По умолчанию
1000 уровней
Описание
Максимальная глубина дерева выражения SQL. Это используется для ограничения пространства стека, используемого при синтаксическом анализе большого выражения SQL. Значение 0 означает отсутствие ограничений.
SQLITE_MAX_FUNCTION_ARG
Максимальное количество аргументов функции
Стандартное использование
SQLITE_MAX_FUNCTION_ARG=number-of-arguments
По умолчанию
127 аргументов
Описание
Максимальное количество аргументов в функции SQL. Существует жесткий верхний предел - 1000.
SQLITE_MAX_LENGTH
Максимальный размер строки
Стандартное использование
SQLITE_MAX_LENGTH=bytes
По умолчанию
1000000000 (1 000 000 000) байтов
Описание
Максимальная длина строки, включая любые значения BLOB. Максимальное поддерживаемое значение - 2 147 483 647 байт (2 ГБ).
SQLITE_MAX_LIKE_PATTERN_LENGTH
Максимальная длина шаблона поиска
Стандартное использование
SQLITE_MAX_LIKE_PATTERN_LENGTH=bytes
По умолчанию
50000 байт
Описание
Максимальная длина шаблона поиска LIKE или GLOB. Злонамеренно созданный шаблон поиска может потреблять большое количество ресурсов, поэтому, если вы разрешите произвольные шаблоны поиска пользователей, вы можете подумать о значительном снижении этого значения.
SQLITE_MAX_PAGE_COUNT
Максимальное количество страниц в базе данных
Стандартное использование
SQLITE_MAX_PAGE_COUNT=number-of-pages
По умолчанию
1073741823 (1 073 741 823) страницы или одна гига-страница
Описание

Максимальное количество страниц в одной базе данных. Это вместе с текущим размером страницы определяет максимальный размер файла базы данных. При размере страницы по умолчанию 1024 байта это ограничивает файлы базы данных одним терабайтом. При максимальном размере страницы 32 КБ это ограничивает файлы базы данных 32 терабайтами.

Теоретически это число может быть увеличено до 4 294 967 296 (наибольшее 32-разрядное целое число без знака), но необходимости в увеличении этого значения не должно быть.

См. также
SQLITE_DEFAULT_PAGE_SIZE , max_page_count [PRAGMA, Ap F], page_size [PRAGMA, Ap F]
SQLITE_MAX_PAGE_SIZE
Максимальный размер страницы базы данных
Стандартное использование
SQLITE_MAX_PAGE_SIZE=bytes
По умолчанию
32768 байт
Описание
Максимальный размер страницы базы данных. Значение по умолчанию 32 КБ - это максимум, разрешенный внутренней архитектурой SQLite. Это значение можно только уменьшить.
См. также
SQLITE_DEFAULT_PAGE_SIZE
SQLITE_MAX_SQL_LENGTH
Максимальная длина оператора SQL
Стандартное использование
SQLITE_MAX_SQL_LENGTH=bytes
По умолчанию
1000000 (1 000 000) байт
Описание
Максимальная длина оператора SQL.
SQLITE_MAX_TRIGGER_DEPTH
Максимальная глубина рекурсии триггеров
Стандартное использование
SQLITE_MAX_TRIGGER_DEPTH=depth
По умолчанию
1000
Описание
Максимальный уровень рекурсии для триггеров. Это значение имеет смысл только при включении рекурсивных триггеров.
SQLITE_MAX_VARIABLE_NUMBER
Максимальное количество переменных связывания в операторе SQL
Стандартное использование
SQLITE_MAX_VARIABLE_NUMBER=number-of-variables
По умолчанию
999 переменных
Описание
Максимальное количество связываемых переменных в подготовленном операторе SQL.

Управление и поведение

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

SQLITE_CASE_SENSITIVE_LIKE
Определить LIKE чувствительность к регистру
Стандартное использование
SQLITE_CASE_SENSITIVE_LIKE
По умолчанию
Не определено (без учета регистра)
Описание
Если определено, оператор LIKE по умолчанию будет чувствителен к регистру.
См. также
LIKE [SQL Expr, Ap D], case_sensitive_like [PRAGMA, Ap F]
SQLITE_HAVE_ISNAN
Использовать системную функцию isnan()
Стандартное использование
SQLITE_HAVE_ISNAN
По умолчанию
Не определено (использовать внутреннюю функцию)
Описание
Если определено, SQLite будет использовать системную функцию isnan(), чтобы определить, является ли значение с плавающей запятой допустимым или нет. Обычно SQLite использует внутреннюю версию этой функции.
SQLITE_OS_OTHER
Заменить определение ОС по умолчанию
Стандартное использование
SQLITE_OS_OTHER=<0|1>
По умолчанию
0
Описание
SQLite имеет директиву SQLITE_OS_* для каждой операционной системы, которую она поддерживает изначально. Обычно SQLite пытается определить, в какой операционной системе она работает, проверяя различные директивы автоматического компилятора. Если вы выполняете кросс-компиляцию, вы можете вручную установить SQLITE_OS_OTHER в 1. Это переопределит все другие флаги SQLITE_OS_* и отключит интерфейсы операционной системы по умолчанию. Эта директива в основном интересна людям, работающим над встраиваемыми системами.
SQLITE_SECURE_DELETE
Включить безопасное удаление
Стандартное использование
SQLITE_SECURE_DELETE
По умолчанию
Не определено (без перезаписи)
Описание

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

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

См. также
secure_delete [PRAGMA, Ap F]
SQLITE_THREADSAFE
Указать режим потока
Стандартное использование
SQLITE_THREADSAFE=<0|1|2>
По умолчанию
1 (serialized mode)
Описание

Устанавливает режим потока по умолчанию. SQLite поддерживает три режима потоков.

Значение Режим Смысл
0 Один поток Отключает поддержку потока
1 Полностью сериализован Полная поддержка потока
2 Многопоточность Поддержка базового потока

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

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

Если приложение построено с поддержкой потоков, оно может переключаться между потокобезопасными режимами (1 или 2) при запуске приложения.

См. также
sqlite3_threadsafe() [C API, Ap G]
SQLITE_TEMP_STORE
Указать место временного хранения
Стандартное использование
SQLITE_TEMP_STORE=<0|1|2|3>
По умолчанию
1 (использовать файлы, разрешить переопределение)
Описание

Эта директива контролирует, как хранятся временные файлы. Временные файлы могут храниться на диске или в памяти. Журналы отката и главные журналы всегда хранятся на диске. Этот параметр применяется к временным базам данных (используемых для хранения временных таблиц), материализованным представлениям и подзапросам, временным индексам и временным базам данных, используемым VACUUM. Использование временного хранилища на основе памяти совершенно безопасно.

Значение Временное место хранения
0 Всегда на диске
1 По умолчанию на диске, можно переопределить
2 По умолчанию в памяти, можно переопределить
3 Всегда в памяти
См. также
temp_store [PRAGMA, Ap F], sqlite3_config() [C API, Ap G]

Настройки отладки

SQLite включает небольшой количество директив, используемых для включения различных средств отладки. Их можно активировать, просто указав директиву. Никакого конкретного значения не требуется. Обычно эти директивы отладки используются только для целей тестирования и разработки, поскольку они добавляют значительные накладные расходы и заставляют все работать заметно медленнее. Все эти директивы по умолчанию не определены. Простое определение директивы включит эту функцию.

SQLITE_DEBUG
Общая отладка и проверка работоспособности
Описание
Эта директива используется для отладки SQLite. Определение этой директивы включает большое количество тестов assert, а также некоторые другие средства отладки.
SQLITE_MEMDEBUG
Отладка распределителя памяти
Описание
Эта директива используется для отладки распределителя памяти. Если он определен, то вместо обычной системы динамической памяти используется инструментальный распределитель памяти.

Включение расширений

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

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

По умолчанию все эти директивы не определены. Простое определение директивы активирует эту функцию.

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

Позволяет извлечение метаданных столбца, необходимые для поддержки следующие вызовы C API:

  • sqlite3_column_database_name()
  • sqlite3_column_database_name16()
  • sqlite3_column_table_name()
  • sqlite3_column_table_name16()
  • sqlite3_column_origin_name()
  • sqlite3_column_origin_name16()
  • sqlite3_table_column_metadata()
SQLITE_ENABLE_FTS3
Включить модуль полнотекстового поиска
Описание
Включает модуль полнотекстового поиска. См. «Модуль полнотекстового поиска» для получения более подробной информации.
SQLITE_ENABLE_FTS3_PARENTHESIS
Включить расширенный синтаксис FTS
Описание
Включает расширенный синтаксис запроса FTS3, включая вложенные запросы в скобках и ключевые слова AND и NOT.
SQLITE_ENABLE_ICU
Включить расширение ICU
Описание
Включает международные компоненты для Unicode (http://www.icu-project.org/) поддержка. Эта библиотека позволяет SQLite более разумно работать с неанглоязычными и не-ASCII. Использование этой директивы требует, чтобы библиотека ICU была доступна и связана в любой сборке. См. «Расширение интернационализации ICU» для получения дополнительной информации.
SQLITE_ENABLE_IOTRACE
Включить отладку трассировки ввода-вывода
Описание
Включает вывод отладки ввода-вывода. Если и библиотека SQLite, и утилита sqlite3 скомпилированы с этим параметром, команда .iotrace будет включена в утилиту. Эта команда заставит SQLite записывать все транзакции ввода-вывода в файл трассировки. Эту директиву следует использовать только при создании утилиты sqlite3.
SQLITE_ENABLE_LOCKING_STYLE
Включить расширенную блокировку файлов
Описание
Включает расширенные стили блокировки. Обычно, SQLite, работающий в Unix-подобной системе, будет использовать блокировки на основе POSIX fcntl() для всех файлов. Если этот параметр определен, SQLite изменит свой стиль блокировки в зависимости от типа файловой системы, которую он использует. Этот флаг оказывает существенное влияние только на Mac OS X, где он включен по умолчанию. Дополнительную информацию см. На веб-сайте SQLite (http://sqlite.org/compile.html#enable_locking_style).
SQLITE_ENABLE_MEMORY_MANAGEMENT
Включить расширенное управление памятью
Описание
Включает расширенное управление памятью и отслеживание, что позволяет SQLite освобождать неиспользуемую память по запросу. Этот параметр необходим для того, чтобы функции sqlite3_release_memory() и sqlite3_soft_heap_limit() имели какой-либо эффект.
SQLITE_ENABLE_MEMSYS3
Включить альтернативный распределитель памяти
Описание
Включает один из двух альтернативных распределителей памяти. Только один может быть включен за раз. Альтернативные распределители используются, когда SQLite находится в режиме «кучи». Это позволяет приложению предоставить статический фрагмент памяти, который SQLite будет использовать для всех своих внутренних распределений. Чаще всего это делается со встроенными системами, где необходимо тщательно контролировать использование памяти. MEMSYS3 использует гибридный алгоритм распределения, основанный на dlmalloc().
См. также
SQLITE_ENABLE_MEMSYS5
SQLITE_ENABLE_MEMSYS5
Включить альтернативный распределитель памяти
Описание
Включает один из двух альтернативных распределителей памяти. Только один может быть включен за раз. MEMSYS5 по сути то же самое, что MEMSYS3, только он использует алгоритм степени двойки, первого соответствия. MEMSYS3 имеет тенденцию быть более экономным с памятью, в то время как MEMSYS5 лучше предотвращает фрагментацию. Определение того, какой модуль лучше всего подходит для ваших конкретных нужд, в значительной степени является проблемой тестирования и измерения.
См. также
SQLITE_ENABLE_MEMSYS3
SQLITE_ENABLE_RTREE
Включить пространственный индекс R*Tree
Описание
Включает модуль расширения R*Tree. R*Trees предназначены для запросов диапазона и обычно используются для хранения длительностей, координат или геопространственных данных. См. «Модули R*Trees и Spatial Indexing» для получения более подробной информации.
SQLITE_ENABLE_STAT2
Включить расширенную статистику ANALYZE
Описание
Включает вычисление дополнительной статистики командой ANALYZE. Если присутствует, гистограмма из 10 выборок будет рассчитана для любого анализируемого индекса. Планировщик запросов может использовать эти данные, чтобы оценить, сколько строк будет отфильтровано по условию диапазона.
SQLITE_ENABLE_UPDATE_DELETE_LIMIT
Включить расширенный синтаксис SQL
Описание
Включает расширенный синтаксис для команд UPDATE и DELETE, который позволяет включать предложения ORDER BY и LIMIT. Поскольку поддержка директив требует изменения парсера SQL, эту директиву можно использовать только при построении из полного дерева разработки. Это не сработает при сборке из объединения или исходного дистрибутива.
SQLITE_ENABLE_UNLOCK_NOTIFY
Включить API расширенной блокировки
Описание
Включает API sqlite3_unlock_notify() C. Эта функция позволяет приложению зарегистрировать функцию обратного вызова, которая будет вызываться при снятии блокировки таблицы общего кэша. Эта директива сборки применима только к приложениям, использующим функциональность общего кеша.
YYTRACKMAXSTACKDEPTH
Включить отслеживание стека синтаксического анализатора
Описание
Включает код отслеживания глубины в синтаксическом анализаторе SQL. Это можно использовать для определения разумных значений для директивы YYSTACKDEPTH.

Ограничение возможностей

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

По умолчанию все эти директивы не определены. Простое определение директивы отключит эту функцию.

SQLITE_DISABLE_LFS
Отключить поддержку больших файлов
Описание
Отключает поддержку больших файлов. Если этот параметр отключен, все файловые операции будут ограничены 32-битными смещениями. Это ограничивает файлы двумя гигабайтами.
SQLITE_DISABLE_DIRSYNC
Отключить синхронизацию каталогов
Описание
Отключает синхронизацию каталогов. Обычно, SQLite запросит у операционной системы синхронизацию родительского каталога удаленного файла, чтобы обеспечить немедленное обновление записей каталога на диске. Эта директива отключает эту синхронизацию.
SQLITE_ZERO_MALLOC
Отключить распределитель памяти
Описание
Отключает распределитель памяти и заменяет его выделителем-заглушкой. Этот распределитель-заглушка всегда выходит из строя, что делает библиотеку SQLite непригодной для использования. Приложение может предоставлять свои собственные функции управления памятью и очищать блоки памяти при запуске библиотеки с помощью API sqlite3_config(). Этот вариант сборки позволяет создавать SQLite на платформах, не имеющих собственных процедур управления памятью.

Пропуск основных функций

В дополнение ко всем другим директивам сборки SQLite имеет достаточное количество директив времени компиляции SQLITE_OMIT_*. Они предназначены для удаления основных функций из сборки, чтобы сделать библиотеку основной базы данных как можно более маленькой и компактной. Например, SQLITE_OMIT_ANALYZE полностью исключает поддержку кода для команды ANALYZE (и последующих оптимизаций запросов), а SQLITE_OMIT_VIRTUALTABLE полностью исключает возможность использования виртуальной таблицы.

В общем, эти директивы должны быть интересны только разработчикам встроенных систем, которые считают каждый байт. Наряду с любыми соответствующими флагами пропуска вы должны убедиться, что компилятор настроен на сборку с включенными функциями типа «оптимизация по размеру».

Чтобы использовать большинство из этих директив исключения, вам необходимо собрать SQLite из источников разработки, найденных в дереве управления версиями. Большинство директив пропуска не будут работать правильно при применении к исходному дистрибутиву или к предварительно построенному объединению. Также имейте в виду, что эти директивы времени компиляции официально не поддерживаются в том смысле, что они не являются частью официальной цепочки тестирования. Для любой данной версии SQLite могут быть как проблемы компиляции, так и проблемы во время выполнения, если включены произвольные наборы флагов пропуска. Используйте (и тестируйте) на свой страх и риск.

Полный список директив компилятора без исключения см. на веб-сайте SQLite (http://sqlite.org/compile.html#omitfeatures).

Приложение B
Справочник команд sqlite3

Программа sqlite3 представляет собой интерфейс командной строки или оболочку, которая позволяет пользователю интерактивно вводить команды SQL и отображать результаты. Это можно использовать для проверки запросов, тестирования существующих баз данных, устранения проблем или просто поиграться и изучить SQL. Программа sqlite3 похожа на приложение mysql для MySQL, приложение pgsql для PostgreSQL, приложение sqlplus для Oracle или приложение sqlcmd для SQL Server.

После запуска sqlite3 и открытия базы данных отображается главное приглашение sqlite>. На этом этапе можно вводить операторы SQL. Операторы SQL должны заканчиваться точкой с запятой, и они будут выполнены немедленно. Затем будут отображены результаты (если есть). Когда база данных будет готова к выполнению нового оператора, появится главная командная строка.

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

Исходный код sqlite3 включен в большинство дистрибутивов SQLite как исходный файл shell.c. Это вместе с файлами объединения - все, что требуется для создания приложения sqlite3. Дополнительные сведения о сборке sqlite3 и доступных вариантах см. в разделе «Сборка» и в приложении A.

Для SQLite существует ряд других сторонних универсальных программ типа оболочки, включая ряд приложений на основе графического интерфейса. См. на веб-сайте SQLite (http://www.sqlite.org/cvstrac/wiki?p=ManagementTools) ссылки на другие интерактивные утилиты и инструменты.

Параметры командной строки

Инструмент sqlite3 понимает следующий формат командной строки:

sqlite3 [options...] [database [SQL_string]]

Сначала указываются параметры, за которыми следует необязательное имя файла базы данных. Эта база данных будет открыта как база данных main. Если файл базы данных не существует, он будет создан. Если имя файла базы данных не указано (или дана пустая строка), будет создана временная база данных с файловой поддержкой. Если указано имя базы данных :memory:, будет создана база данных в памяти.

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

Интерактивная последовательность запуска попытается найти и открыть файл инициализации .sqliterc в домашнем каталоге текущего пользователя. Если файл существует, строки будут прочитаны и выполнены перед любой другой обработкой (включая строку SQL командной строки). Файл инициализации может содержать как точечные команды, так и операторы SQL. Все операторы SQL должны заканчиваться точкой с запятой. Файл инициализации обрабатывается до параметров командной строки. Это позволяет параметрам командной строки переопределять любые точечные команды в файле инициализации.

Известные параметры командной строки:

-bail
Включает флаг bail. Пакетные файлы прекратят обработку при обнаружении ошибки. Подробнее см. команду .bail.
-batch
Принудительно запускает пакетный ввод-вывод. Это подавит такие вещи, как приветственный баннер и командные строки.
-column
Устанавливает режим вывода на column. см. команду .mode для получения более подробной информации.
-csv
Устанавливает режим вывода на csv. Подробнее см. команду .mode.
-echo
Включает режим эха. Подробнее см. команду .echo.
-header -noheader
Включает или отключает заголовки. Подробнее см. команду .headers.
-init filename
Если это интерактивный сеанс, используйте этот файл как файл инициализации, а не файл .sqliterc.
-interactive
Принудительный ввод-вывод в интерактивном стиле. Сюда входят такие вещи, как приветственный баннер и командные строки.
-help
Отображает сводку параметров командной строки и завершает работу.
-html
Устанавливает режим вывода на html. Подробнее см. команду .mode.
-line
Устанавливает режим вывода на line. Подробнее см. команду .mode.
-list
Устанавливает режим вывода в list. Подробнее см. команду .mode.
-nullvalue string
Устанавливает строку отображения NULL. Для получения дополнительных сведений см. команду .nullvalue.
-separator string
Устанавливает разделительную строку. Подробнее см. команду .separator.
-version
Распечатывает версию SQLite и завершает работу.

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

Интерактивные точечные команды

В этом разделе рассматриваются точечные команды sqlite3. Точечные команды управляют режимом и конфигурацией утилиты sqlite3 и, в некоторых случаях, базовой библиотеки SQLite. Обычно предполагается, что любой ввод в служебную программу sqlite3 является оператором SQL и передается в библиотеку SQLite для обработки. Чтобы отличать эти команды от операторов SQL, все точечные команды начинаются с точки. Отсюда и название.

В отличие от операторов SQL, команды с точкой не заканчиваются точкой с запятой. Вся команда должна быть дана в одной строке ввода.

Важно понимать, что точечные команды реализуются самой программой sqlite3, а не базовой библиотекой SQLite. Точечные команды не могут быть переданы API операторов SQLite.

Например, основная библиотека SQLite не имеет возможности импорта. Функциональность команды .import обеспечивается кодом sqlite3 с использованием стандартных вызовов API SQLite. Если вы хотите включить функцию импорта в свое приложение, вы можете написать свою собственную, используя стандартные вызовы API SQLite, или вы можете извлечь код из shell.c и попытаться адаптировать его для своих целей.

Некоторые из команд с точкой включают или выключают флаги конфигурации. Если задано число, 0 - ложь или выключено, а все остальные числа считаются истинными или включенными. Строки on и yes распознаются как истинные или on, а все остальные значения принимаются как false или off.

.backup
Выполнение низкоуровневого копирования базы данных в файл
Стандартное использование
.backup [database_name] filename
Описание

Команда .backup выполняет низкоуровневую копию открытой или присоединенной базы данных с указанным именем файла. Эту команду можно безопасно запускать с активной исходной базой данных (хотя она может и не завершиться успешно). Если имя базы данных не указано, будет создана резервная копия базы данных main. Эта команда часто используется для обратной записи активной базы данных в памяти в файл базы данных.

Это противоположно команде .restore.

.bail
Остановить при обнаружении ошибки
Стандартное использование
.bail switch
Описание

Команда .bail управляет обработкой ошибок. Это влияет только на неинтерактивные сеансы, когда команды SQL читаются из файла или из стандартного ввода. Если установлен флаг залога, любая ошибка приведет к остановке обработки.

По умолчанию выключено.

..databases
Список всех подключенных в данный момент баз данных
Стандартное использование
.databases
Описание

Команда .databases генерирует таблицу всех подключенных в данный момент баз данных. Формат таблицы:

Имя столбца Тип столбца Значение
seq Integer Порядковый номер базы данных
name Text Логическое имя базы данных
file Text Путь и имя файла базы данных

Первая база данных (порядковый номер 0) всегда должна иметь имя main. Это база данных, которая была открыта впервые. Вторая база данных (порядковый номер 1), если она есть, всегда должна быть базой данных temp, в которой создаются любые временные объекты. После этого будут перечислены все подключенные базы данных.

.dump
Создает файл дампа SQL
Стандартное использование
.dump [table-pattern ...]
Описание

Команда .dump генерирует серию команд SQL, подходящих для воссоздания одной или нескольких таблиц из базы данных. Если параметры не указаны, каждая таблица в базе данных main будет выгружена. Если задан один или несколько параметров, будут сброшены только те таблицы в базе данных main, которые соответствуют предоставленным шаблонам LIKE. Чтобы записать вывод .dump в файл, используйте команду .output.

Дамп сохранит значения в любом столбце псевдонима ROWID (столбец INTEGER PRIMARY KEY), но не сохранит более общие значения ROWID.

.echo
Включение и выключение команды эха
Стандартное использование
.echo switch
Описание

Команда .echo включает или выключает эхо команды SQL. Если установлено значение on, любые команды SQL будут распечатаны перед выполнением. Это наиболее полезно для неинтерактивных сеансов.

По умолчанию выключено.

.exit
Включение и выключение команды эха
Стандартное использование
.exit
Описание

Команда .exit завершает работу и закрывает приложение sqlite3.

.explain
Формат вывода для команды EXPLAIN SQL
Стандартное использование
.explain [switch]
Описание

Команда .explain устанавливает несколько параметров sqlite3 (например, режим и ширину столбца), упрощая просмотр вывода любых команд EXPLAIN SQL. При выключении восстанавливаются предыдущие настройки. Если параметр не указан, будет включен режим объяснения. Включение объяснения, когда оно уже включено, сбрасывает все параметры оболочки в режим объяснения по умолчанию.

По умолчанию выключено.

.headers
Управление отображением имен и заголовков столбцов
Стандартное использование
.headers switch
Описание

Команда .headers управляет отображением имен и заголовков столбцов. Если включено, sqlite3 будет генерировать строку с именами столбцов перед отображением любого вывода. В некоторых режимах также будет отображаться разделитель между именами столбцов и результирующими данными.

По умолчанию выключено.

.help
Показать справку
Стандартное использование
.help
Описание

Команда .help отображает каждую точечную команду sqlite3 вместе с кратким описанием синтаксиса и цели команды.

.import
Импортировать внешний файл данных в таблицу
Стандартное использование
.import filename table-name
Описание

Команда .import пытается импортировать данные из внешнего файла и вставить их в указанную таблицу. Таблица должна уже существовать. Файл должен быть текстовым с одной строкой в строке. Каждая строка должна состоять из значения, разделенного текущей строкой-разделителем. Значения в каждой строке должны соответствовать количеству и порядку столбцов, возвращаемых командой SELECT * FROM table-name. Функция импорта не поддерживает какие-либо формы цитирования значений или escape-символов, поэтому любой экземпляр разделительной строки внутри значения вызовет ошибку. Любая попытка импортировать файл, который использует цитаты, приведет к тому, что цитаты будут приняты как буквальная часть значения.

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

.indices
Импортировать внешний файл данных в таблицу
Стандартное использование
.indices [table-pattern]
Описание

Команда .indices отображает названия различных индексов в основной базе данных. Если параметр не указан, будет отображаться имя каждого индекса. Если указан необязательный шаблон соответствия таблицы, будет отображаться имя любого индекса, связанного с таблицей, которая соответствует предоставленному шаблону LIKE. Разрешен только один шаблон таблицы. Имена индексов будут возвращаться по одному в каждой строке без дополнительной информации.

.iotrace
Прямая трассировка ввода-вывода в файл
Стандартное использование
.iotrace [filename|-]
Описание

Команда .iotrace управляет отладкой трассировки ввода-вывода. Если указано имя файла, трассировка ввода-вывода будет включена в основной библиотеке и направлена в файл. Если дан одиночный дефис, трассировка ввода-вывода будет перенаправлена на стандартный вывод. Если параметры не указаны, трассировка ввода-вывода будет отключена.

Команда .iotrace доступна только в том случае, если и библиотека SQLite, и sqlite3 скомпилированы с директивой SQLITE_ENABLE_IOTRACE.

.iotrace
Прямая трассировка ввода-вывода в файл
Стандартное использование
.iotrace [filename|-]
Описание

Команда .iotrace управляет отладкой трассировки ввода-вывода. Если указано имя файла, трассировка ввода-вывода будет включена в основной библиотеке и направлена в файл. Если дан одиночный дефис, трассировка ввода-вывода будет перенаправлена на стандартный вывод. Если параметры не указаны, трассировка ввода-вывода будет отключена.

Команда .iotrace доступна только в том случае, если и библиотека SQLite, и sqlite3 скомпилированы с директивой SQLITE_ENABLE_IOTRACE.

.load
Загрузить динамическое расширение
Стандартное использование
.load filename [entry-point]
Описание

Команда .load пытается загрузить и связать динамическое расширение SQLite. Если точка входа не указана, будет использоваться имя sqlite3_extension_init по умолчанию.

Расширения, которые переопределяют существующие функции SQL (включая встроенные функции), должны быть загружены с помощью этой команды. Использование функции SQL load_extension() не будет работать, поскольку расширение не может переопределить существующую функцию во время обработки оператора SQL (например, оператора, выполняющего функцию SQL load_extension()).

.log
Включение или отключение ведения журнала
Стандартное использование
.log (filename|stdout|stderr|off)
Описание

Команда .log обозначает выходной файл для сообщений, отправленных в sqlite3_log(). Библиотека SQLite регистрирует большинство ошибок, чтобы облегчить отладку. Пользовательские расширения (включая пользовательские функции SQL, параметры сортировки и виртуальные таблицы) также могут регистрировать сообщения.

.mode
Установить режим вывода
Стандартное использование
.mode (column[s]|csv|html|insert|line[s]|list|tabs|tcl) [table-name]
Описание

Команда .mode устанавливает режим вывода. Это определяет, как выходные данные форматируются и представляются. Необязательное имя таблицы используется только в режиме insert. Режим по умолчанию - list. Поддерживаемые режимы включают:

column
Вывод в табличном формате, по одной строке в строке. Ширина каждого столбца определяется значениями, предоставленными командой .width. Вывод будет обрезан по ширине столбца. Если заголовки включены, первые две строки будут названиями столбцов, за которыми следуют разделительные тире. В большинстве случаев это самый простой для чтения формат, если для ширины столбцов установлено что-то полезное.
csv
Выходные данные представлены в виде значений, разделенных запятыми, по одной строке в строке. Каждое значение отделяется одним символом запятой без пробела в конце. Если заголовки включены, первая строка будет набором имен столбцов. Если значение содержит запятую, значение будет заключено в двойные кавычки. Если значение содержит символ двойной кавычки, будут использоваться правила SQL для экранирования кавычек, а буквальная двойная кавычка будет заменена двумя последовательными символами двойных кавычек. Однако это не то, чего ожидают многие импортеры CSV.
html
Выходные данные представлены в формате таблицы HTML. Каждая строка базы данных будет выводиться как элемент <tr> (строка таблицы). Если заголовки включены, первый элемент строки таблицы будет содержать имена столбцов. Для более старых версий HTML выходные данные подходят для размещения в элемент <table>.
insert
Вывод - это серия операторов SQL INSERT. Если имя таблицы задано как часть команды .mode, это имя будет использоваться как таблица в операторе INSERT. Если имя таблицы не указано, будет использоваться имя table. Флаг заголовка не действует в этом режиме.
line
Вывод - одно значение на строку в формате column_name = value (с некоторым возможным начальным пробелом перед именем столбца). Каждая строка отделяется пустой строкой. Флаг заголовка не действует в этом режиме.
list
Выходные данные состоят из последовательности значений, по одной строке в строке. Каждое значение разделяется текущей строкой разделителя (по умолчанию |, полоса или вертикальная черта). После последнего значения разделитель не появится. Нет никаких условий для заключения в кавычки или экранирования разделительной строки, если она присутствует в значении. Если заголовки включены, первая строка будет содержать названия столбцов. В общем, это самый простой для синтаксического анализа формат, если используется уникальный разделитель.
tabs
Вывод осуществляется в виде значений, разделенных табуляцией, по одной строке в строке. В значениях нет условий для заключения в кавычки или экранирования символов табуляции. Если заголовки включены, первая строка будет содержать имена столбцов.
tcl
Выходные данные состоят из последовательности значений, по одной строке в строке. Каждое значение разделяется текущей строкой разделителя (по умолчанию |, полоса или вертикальная черта). Разделитель будет добавлен после последнего значения. Все значения будут заключены в двойные кавычки, хотя в значениях не предусмотрены кавычки или экранирование двойных кавычек. Если заголовки включены, первая строка будет содержать имена столбцов. Этот режим предназначен для использования с языком сценариев Tcl.

Если выходные данные sqlite3 передаются непосредственно в сценарий или другую автоматизированную систему, убедитесь, что вы понимаете, как разделяются значения, и что есть положения, позволяющие заключать в кавычки, экранировать или избегать любых разделителей в возвращаемых значениях. Особо следует отметить, что формат CSV не так универсален, как многие думают. Хотя формат хорошо работает для чисел и простых данных, если текстовые значения требуют заключения в кавычки или escape-последовательностей, перед использованием следует проверить совместимость.

.nullvalue
Установить строку, используемую для представления вывода NULL
Стандартное использование
.nullvalue string
Описание
Команда .nullvalue устанавливает строку, используемую для представления вывода NULL в любом режиме вывода, кроме insert. По умолчанию - пустая строка.
.output
Задать место назначения вывода
Стандартное использование
.output (filename|stdout)
Описание

Команда .output устанавливает место назначения вывода. По умолчанию любой вывод команды направляется в интерфейс терминала (в случае интерактивных сеансов) или вывод программы (в случае пакетных сеансов). Учитывая имя файла, команда .output перенаправит любой вывод команды в файл. Если файл не существует, он будет создан. Если файл существует, он будет усечен. Если вывод перенаправляется в файл, он также не будет отображаться в терминале. Команды и командные запросы не будут выводиться в файл, что делает этот способ подходящим для создания файлов дампа SQL с помощью команды .dump.

Чтобы сбросить вывод на интерфейс терминала, установите вывод на stdout (стандартный вывод).

.prompt
Установить командную строку
Стандартное использование
.prompt main [continue]
Описание

Команда .prompt изменяет командные строки sqlite3. Главное приглашение используется для большинства команд, а приглашение продолжить - для многострочных команд SQL.

Основное приглашение по умолчанию - sqlite>, а приглашение продолжения по умолчанию - ...>.

.quit
Закрыть и выйти из приложения sqlite3
Стандартное использование
.quit
Описание
Команда .quit завершает работу и закрывает приложение sqlite3.
.read
Выполнить команды SQL из файла
Стандартное использование
.read filename
Описание
Команда .read считывает и выполняет точечные команды и операторы SQL из файла.
.restore
Выполнение низкоуровневого копирования файла базы данных в базу данных
Стандартное использование
.restore [database_name] filename
Описание

Команда .restore выполняет низкоуровневую копию файла базы данных в открытую или присоединенную базу данных . Если имя базы данных не указано, будет заполнена основная база данных. Эта команда часто используется для заполнения активной базы данных в памяти из файла базы данных. Эту команду можно безопасно запускать с активной исходной базой данных (хотя она может и не завершиться успешно).

Это противоположно команде .backup.

.schema
Отображение команд создания SQL для схемы
Стандартное использование
.schema [table-pattern]
Описание

Команда .schema отображает команды SQL, используемые для создания схемы (таблицы, представления, индексы и т. д.). Если параметр не указан, будет отображаться команда CREATE для каждого объекта в базах данных main и temp. Также можно указать один параметр, и в этом случае будут отображаться только те объекты, связанные с таблицами, которые соответствуют шаблону LIKE.

.separator
Определение строки, используемой в качестве разделителя столбцов
Стандартное использование
.separator string
Описание

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

Режим список является режимом по умолчанию, а строка-разделитель по умолчанию - | (pipe или bar).

.show
Отображение текущих настроек sqlite3
Стандартное использование
.show
Описание
Команда .show отображает текущие значения для небольшого количества настроек sqlite3, включая состояние флага эха, флаг объяснения, флаг заголовков, текущий режим вывода, Строка NULL, место назначения вывода, строка-разделитель и любая настроенная ширина столбца.
.tables
Отображение списка имен таблиц и представлений
Стандартное использование
.tables [table-pattern]
Описание
Команда .tables отображает список имен для всех объектов таблиц и представлений, которые находятся в базах данных main и temp.
.timeout
Установка таймера повтора блокировки
Стандартное использование
.timeout milliseconds
Описание

Команда .timeout используется для установки таймера повтора. Если установлено значение таймера и обнаружена заблокированная база данных, вместо того, чтобы немедленно вернуть ошибку «база данных заблокирована», в большинстве случаев SQLite будет продолжать попытки повторно получить блокировку, пока не истечет время таймера.

Значение 0 или меньше отключает значение тайм-аута.

.timer
Включение или отключение измерения времени ЦП
Стандартное использование
.timer switch
Описание
Команду .timer можно использовать для включения или отключения измерения времени ЦП. Если этот параметр включен, SQLite будет отслеживать и отображать время пользователя и системное время, необходимое для обработки каждого запроса.
.width
Установить ширину отображения для каждого столбца
Стандартное использование
.width numb [numb ...]
Описание

Команда .width используется для установки ширины по умолчанию для каждого столбца. Значения ширины используются для форматирования дисплея, когда режим вывода установлен на column. Первое значение используется для ширины первого столбца, второе значение используется для второго столбца и так далее. Если значения не заданы, будет использоваться ширина столбца 10.

Можно указать до 100 значений ширины столбцов.

Приложение C
Справочник команд SQLite SQL

В этом приложении перечислены команды и синтаксис SQL, поддерживаемые SQLite. Операторы SQL состоят из одной команды и любых обязательных параметров. Операторы команд разделяются точкой с запятой. Технически автономные операторы не должны заканчиваться точкой с запятой, но в большинстве интерактивных сред требуется использование точки с запятой, чтобы указать, что текущий оператор команды завершен и должен быть выполнен. Например, для C API sqlite3_exec() не требуется, чтобы операторы команд заканчивались точкой с запятой, но интерактивное использование sqlite3 требует завершения каждого оператора точкой с запятой.

В большинстве случаев, когда вызывается имя таблицы, вместо него можно использовать имя представления. Как отмечено в синтаксических диаграммах, в большинстве случаев, когда используется любой идентификатор объекта (имя таблицы, имя представления и т. д.), Имя может быть дополнено логическим именем базы данных, чтобы предотвратить любую двусмысленность между объектами в разных базах данных, которые имеют аналогичные имя (см. ATTACH DATABASE в этом приложении). Если объект неквалифицирован, он будет найден во базе данных temp, затем в базе данных main, а затем в каждой присоединенной базе данных по порядку. Если неквалифицированный идентификатор появляется в операторе CREATE, объект будет создан в основной базе данных, если оператор не содержит синтаксиса CREATE TEMPORARY какого-либо типа. Идентификаторы объектов, в которых используются нестандартные символы, должны быть заключены в кавычки. См. «Базовый синтаксис» для получения дополнительной информации.

Команды SELECT, UPDATE и DELETE содержат предложения, которые используются для определения критериев поиска в строках таблицы. Эти ссылки на таблицы могут включать нестандартные фразы INDEXED BY или NOT INDEXED, чтобы указать, должен ли оптимизатор запросов (или не должен) пытаться использовать индекс для удовлетворения условия поиска. Эти расширения включены в SQLite, чтобы помочь с тестированием, отладкой и ручной настройкой запросов. Их использование в производственном коде не рекомендуется, и поэтому они не включены в синтаксические диаграммы или объяснения команд в этом приложении. Для получения дополнительной информации см. веб-сайт SQLite (http://www.sqlite.org/lang_indexedby.html).

Наконец, имейте в виду, что синтаксические диаграммы, представленные с каждой командой, не должны восприниматься как окончательная спецификация для полного синтаксиса команды. Некоторые редко используемые нестандартные синтаксисы (например, расширение INDEXED BY, рассмотренное в предыдущем абзаце) не включены в эти диаграммы. Точно так же существуют возможные комбинации синтаксиса, которые, как будут указаны на диаграммах, возможны, но фактически не образуют логических утверждений. Например, согласно синтаксическим диаграммам, оператор JOIN может содержать как условие NATURAL с префиксом, так и завершающее условие ON или USING. На практике это невозможно, поскольку соединение ограничено только одним типом условия. Хотя можно было бы представить диаграмму только с разрешенным синтаксисом, диаграмма стала бы намного больше и намного сложнее. В таких ситуациях было решено, что сделать диаграмму более простой для понимания важнее, чем заставить ее идти по абсолютной линии по тому, что разрешено, а что нет. К счастью, такие ситуации достаточно редки. Только не думайте, что, поскольку синтаксический анализатор может анализировать, это означает, что команда имеет смысл для механизма базы данных.

Команды SQLite SQL

SQLite поддерживает следующие команды и синтаксис SQL.

ALTER TABLE
Изменить существующую таблицу
Синтаксис
Стандартное использование
ALTER TABLE database_name.table_name RENAME TO new_table_name; ALTER TABLE database_name.table_name ADD COLUMN column_def...;
Описание
Команда ALTER TABLE изменяет существующую таблицу без выполнения полного дампа и перезагрузки данных. Версия SQLite ALTER TABLE поддерживает две основные операции. Вариант RENAME используется для изменения имени таблицы, в то время как ADD COLUMN используется для добавления нового столбца в существующую таблицу. Обе версии команды ALTER TABLE сохранят все существующие данные в таблице.
RENAME

Вариант RENAME используется для «перемещения» или переименования существующей таблицы. Команда ALTER TABLE...RENAME может изменять только таблицу на месте, ее нельзя использовать для перемещения таблицы в другую базу данных. Имя базы данных может быть указано при указании имени исходной таблицы, но только имя таблицы должно быть указано при указании имени новой таблицы.

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

Определения представлений и операторы триггеров, которые ссылаются на таблицу по имени, не будут изменены. Эти операторы необходимо отбросить и создать заново или создать заменяющую таблицу.

ADD COLUMN

Вариант ADD COLUMN используется для добавления нового столбца в конец определения таблицы. Новые столбцы всегда должны располагаться в конце определения таблицы. Существующие строки таблицы фактически не изменяются, а это означает, что добавленные столбцы подразумеваются до тех пор, пока строка не будет изменена. Это означает, что команда ALTER TABLE...ADD COLUMN выполняется довольно быстро даже для больших таблиц, но это также означает, что существуют некоторые ограничения на столбцы, которые можно добавить.

Добавленный столбец:

  • не может иметь ограничения PRIMARY KEY
  • не может иметь ограничения UNIQUE
  • должен иметь буквальное, ненулевое значение по умолчанию, если задано ограничение NOT NULL
  • не может иметь значение по умолчанию CURRENT_TIME, CURRENT_DATE или CURRENT_TIMESTAMP

Если внешний ключ ограничения включены, и добавленный столбец определяется как внешний ключ (он имеет предложение REFERENCES), новый столбец должен иметь значение по умолчанию NULL.

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

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

См. также
CREATE TABLE
ANALYZE
Вычислить метаданные индекса
Синтаксис
Стандартное использование
ANALYZE; ANALYZE database_name; ANALYZE database_name.table_name;
Описание

Команда ANALYZE вычисляет и записывает статистические данные об индексах базы данных. Если доступны, эти данные используются оптимизатором запросов для вычисления наиболее эффективного плана запроса.

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

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

Данные, созданные ANALYZE, хранятся в одной или нескольких таблицах с именем sqlite_stat#, начиная с sqlite_stat1. Эти таблицы нельзя отбросить вручную, но данные внутри можно изменить с помощью стандартных команд SQL. Как правило, это не рекомендуется, за исключением удаления любых данных ANALYZE, которые больше не действительны или не желательны.

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

Если SQLite скомпилирован с директивой SQLITE_ENABLE_STAT2, то ANALYZE также сгенерирует таблицу sqlite_stat2, содержащую гистограмму распределения индекса. Это используется для расчета стоимости таргетинга на диапазон значений.

Есть одна известная проблема с командой ANALYZE. При создании таблицы sqlite_stat1 ANALYZE должен вычислить количество уникальных значений в индексе. Для этого команда ANALYZE использует стандартный SQL-тест на эквивалентность значений индекса. Это означает, что если индекс с одним столбцом содержит несколько записей NULL, каждая из них будет считаться неэквивалентным уникальным значением (поскольку NULL != NULL). В результате, если индекс содержит большое количество значений NULL, данные ANALYZE будут неправильно считать индекс более уникальным, чем на самом деле. Это может неправильно повлиять на оптимизатор, чтобы выбрать поиск на основе индекса, когда сканирование всей таблицы было бы менее затратным. Из-за этого поведения, если индекс содержит заметный процент записей NULL (скажем, От 10 до 15% или более), и обычно запрашивают все строки NULL (или не NULL), рекомендуется отбросить данные ANALYZE для этого индекса.

См. также
CREATE INDEX, SELECT
ATTACH DATABASE
Присоединить файл базы данных
Синтаксис
Стандартное использование
ATTACH DATABASE 'filename' AS database_name;
Описание

Команда ATTACH DATABASE связывает файл базы данных filename с текущим подключением к базе данных под логическим именем базы данных database_name. Если файл базы данных filename не существует, он будет создан. После присоединения все ссылки на конкретную базу данных выполняются через логическое имя базы данных, а не имя файла. Все имена баз данных должны быть уникальными, но (когда режим общего кэша не включен) подключение одного и того же имени файла несколько раз под разными именами базы данных поддерживается должным образом.

Имя базы данных main зарезервировано для первичной базы данных (той, которая использовалась для создания соединения с базой данных). Имя базы данных temp зарезервировано для базы данных, содержащей временные таблицы и другие временные объекты данных. Оба этих имени базы данных существуют для каждого соединения с базой данных.

Если указано имя файла :memory:, будет создана и присоединена новая база данных в памяти. Можно присоединить несколько баз данных в памяти, но каждая из них будет уникальной. Если указано пустое имя файла (''), будет создана временная база данных с файловой поддержкой. Как и база данных в памяти, каждая база данных уникальна, и все временные базы данных автоматически удаляются при закрытии. В отличие от базы данных в памяти, файловые временные базы данных могут увеличиваться до больших размеров без чрезмерного использования памяти.

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

Если база данных main была открыта с помощью sqlite3_open_v2(), каждая присоединенная база данных будет открываться с одинаковыми флагами. Если база данных main была открыта только для чтения, все присоединенные базы данных также будут доступны только для чтения.

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

Если какие-либо операции записи выполняются в какой-либо базе данных, главный файл журнала будет создан в ассоциации с базой данных main. Если база данных main расположена в области только для чтения, главный файл журнала не может быть создан, и операция завершится ошибкой. Если некоторые базы данных доступны только для чтения, а некоторые - для чтения и записи, убедитесь, что база данных main является одной из баз данных, расположенных в области чтения/записи.

В любом месте, где SQLite ожидает имя таблицы, он будет принимать формат database_name.table_name. Это можно использовать для ссылки на таблицу в конкретной базе данных, которая в противном случае могла бы быть неоднозначной.

См. также
DETACH DATABASE, encoding [PRAGMA, Ap F], temp_store [PRAGMA, Ap F], sqlite3_open() [C API, Ap G]
BEGIN TRANSACTION
Открыть явную транзакцию
Синтаксис
Стандартное использование
BEGIN; BEGIN EXCLUSIVE TRANSACTION;
Описание

Команда BEGIN TRANSACTION запускает явную транзакцию. После запуска явная транзакция будет оставаться открытой до тех пор, пока транзакция не будет зафиксирована с помощью COMMIT TRANSACTION, или пока не будет выполнен откат с помощью ROLLBACK TRANSACTION. Транзакции используются для группировки нескольких дискретных команд (например, последовательности команд INSERT, которые вставляют строки в таблицы с перекрестными ссылками) в одну логическую команду.

Транзакции совместимы с ACID в том смысле, что они атомарны, согласованы, изолированы и надежны. Они являются важной частью правильного проектирования и использования базы данных. Для получения дополнительной информации см. «Язык управления транзакциями».

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

Команда BEGIN выключает режим автоматической фиксации, открывает транзакцию и оставляет ее открытой до тех пор, пока она не будет явно подтверждена или откат. Это позволяет объединить несколько команд (и несколько модификаций) в одну транзакцию. После выдачи COMMIT или ROLLBACK соединение с базой данных снова переводится в режим автоматической фиксации.

Транзакции не могут быть вложенными. Для этой функции используйте SAVEPOINT. Выполнение команды BEGIN, когда соединение с базой данных уже находится в транзакции, приведет к ошибке, но не изменит состояние уже существующей транзакции.

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

В SQLite транзакции контролируются блокировками. Вы можете указать желаемое поведение блокировки с помощью модификатора DEFERRED, IMMEDIATE или EXCLUSIVE. Режим по умолчанию - DEFERRED, в котором блокировки не устанавливаются, пока они не потребуются. Это обеспечивает высочайший уровень параллелизма, но также означает, что транзакции может потребоваться блокировка, которую она не может получить, и может потребоваться откат. В режиме IMMEDIATE предпринимается попытка немедленно получить зарезервированную блокировку, позволяя другим соединениям продолжать чтение из базы данных, но оставляя за собой право повысить себя до состояния записи в любое время. Запуск EXCLUSIVE транзакции будет пытаться захватить монопольную блокировку, разрешая полный доступ к базе данных, но запрещая доступ для любого другого соединения с базой данных. Более высокие уровни блокировки означают большую уверенность в том, что транзакция может быть успешно зафиксирована за счет более низких уровней параллелизма.

Для получения дополнительной информации о модели блокировки и параллелизма SQLite см. http://sqlite.org/lockingv3.html.

См. также
COMMIT TRANSACTION, ROLLBACK TRANSACTION, SAVEPOINT, RELEASE SAVEPOINT
COMMIT TRANSACTION
Завершить и зафиксировать транзакцию
Синтаксис
Стандартное использование
COMMIT;
Описание

Команда COMMIT TRANSACTION пытается закрыть и зафиксировать любые изменения, сделанные во время текущей транзакции. Также можно использовать псевдоним END TRANSACTION. Если команда COMMIT выполняется, когда SQLite находится в режиме автоматической фиксации, будет выдана ошибка.

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

Если фиксация не удалась, транзакция может быть или не может быть отменена, в зависимости от типа ошибки. Если транзакция не откатывается, обычно можно просто повторно ввести команду COMMIT. Если транзакция откатывается, все изменения, сделанные как часть транзакции, теряются. Вы можете определить конкретное состояние подключения к базе данных с помощью вызова API sqlite3_get_autocommit() или попытавшись выполнить команду BEGIN. Если база данных возвращает логическую ошибку в результате команды BEGIN, база данных все еще находится в допустимой транзакции. Вы также можете выполнить команду ROLLBACK, которая либо откатит транзакцию, если она все еще на месте, либо вернет ошибку, если транзакция уже была отменена.

Совершение транзакции связано со значительными затратами. См. BEGIN TRANSACTION для получения дополнительной информации о том, как снизить эту стоимость.

См. также
BEGIN TRANSACTION, ROLLBACK TRANSACTION, END TRANSACTION, sqlite3_get_autocommit() [C API, Ap G]
CREATE INDEX
Определение и создание нового индекса таблицы
Синтаксис
Стандартное использование
CREATE INDEX index_name ON table_name ( column_name COLLATE NOCASE ); CREATE UNIQUE INDEX database_name.index_name ON table_name ( col1, col2 ,... );
Описание

Команда CREATE INDEX создает определяемый пользователем индекс. После создания индекс заполняется из существующих данных таблицы. После создания индекс будет автоматически поддерживаться, так что изменения в указанной таблице будут отражены в индексе. Оптимизатор запросов автоматически рассмотрит все созданные индексы. Индексы не могут быть созданы для виртуальных таблиц или представлений.

Индекс может ссылаться на несколько столбцов, но все столбцы должны быть из одной таблицы. В случае многоколоночных индексов индекс будет построен в том же порядке, что и список столбцов. Для индексов, связанных с производительностью, порядок столбцов может быть очень важным. См. «Порядок действий» для получения более подробной информации. Таблица должна находиться в той же базе данных, что и индекс. Чтобы создать индекс для временной таблицы, создайте индекс в базе данных temp.

Если таблица удаляется, все связанные индексы также удаляются. Пользовательский индекс также может быть явно удален с помощью команды DROP INDEX.

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

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

Кроме того, каждый индексированный столбец может указывать порядок сортировки по возрастанию (ASC) или убыванию (DESC). По умолчанию все проиндексированные столбцы будут отсортированы в порядке возрастания. Для использования индексов по убыванию требуется современный формат файла. Если база данных по-прежнему использует устаревший формат файла, нисходящие индексы не будут поддерживаться, а ключевое слово DESC будет автоматически игнорироваться.

Индексы SQLite включают полную копию проиндексированных данных. Будьте осторожны с размером вашей базы данных при индексировании столбцов, которые состоят из большого текста или значений BLOB. Как правило, индексы следует создавать только для столбцов с относительно уникальным набором значений. Если какое-либо отдельное значение появляется более чем в 10–15% строк, индекс обычно не рекомендуется. Почти всегда неразумно индексировать логический столбец или любой аналогичный столбец, содержащий относительно небольшое количество значений. Поддержание индексов связано с расходами, поэтому их следует создавать только тогда, когда они служат какой-либо цели.

Создание уже существующего индекса обычно вызывает ошибку. Если предоставляется необязательное предложение IF NOT EXISTS, эта ошибка игнорируется. Это оставляет исходное определение (и данные) на месте.

См. также
DROP INDEX, ANALYZE, REINDEX, CREATE TABLE, COLLATE [SQL Expr, Ap D]
CREATE TABLE
Определить и создать новую таблицу
Синтаксис

column-def:

type-name:

column-constraint:

table-constraint:

foreign-key-clause:

conflict-clause:

Стандартное использование
CREATE TABLE database_name.table_name ( c1_name c1_type, c2_name c2_type... ); CREATE TABLE database_name.table_name AS SELECT * FROM... ; CREATE TABLE tbl ( a, b, c ); CREATE TABLE people ( people_id INTEGER PRIMARY KEY, name TEXT ); CREATE TABLE employee ( employee_id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, start_date TEXT NOT NULL DEFAULT CURRENT_DATE, parking_spot INTEGER UNIQUE );
Описание

Команда CREATE TABLE используется для определения новой таблицы. Это одна из самых сложных команд SQL, понятных SQLite, хотя почти весь синтаксис является необязательным.

Новую таблицу можно создать в конкретной базе данных, уточнив имя таблицы с явным именем базы данных. Если присутствует одно из необязательных ключевых слов TEMP или TEMPORARY, любое имя базы данных, указанное как часть имени таблицы, будет проигнорировано, и новая таблица будет создана в базе данных temp.

Создание таблицы, которая уже существует, обычно вызывает ошибку. Если указано необязательное предложение IF NOT EXISTS, эта ошибка игнорируется. Это оставляет исходное определение (и данные) на месте.

Есть два варианта CREATE TABLE. Разница в том, как определены столбцы. Наименее распространенный вариант использует простой подзапрос AS SELECT для определения структуры и начального содержимого таблицы. Количество столбцов и имена столбцов будут взяты из набора результатов подзапроса. Строки набора результатов будут загружены в таблицу как часть процесса создания таблицы. Поскольку этот вариант не дает возможности определить сходство столбцов (типичные типы данных), ключи или ограничения, он обычно ограничивается определением «быстрых и грязных» временных таблиц. Чтобы быстро создать и загрузить структурированные данные, часто лучше создать таблицу, используя стандартную нотацию, а затем использовать команду INSERT...SELECT для загрузки таблицы. Стандартная запись явно определяет список столбцов и ограничений таблицы.

Базовый формат

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

За списком определений столбцов следует список ограничений на уровне таблицы. По большей части ограничения уровня столбца и ограничения уровня таблицы очень похожи. Основное отличие состоит в том, что ограничения столбца применяются к значениям, найденным в одном столбце, тогда как ограничения таблицы могут иметь дело с одним или несколькими столбцами. Можно определить большинство ограничений столбца как ограничения уровня таблицы, которые ссылаются только на один столбец. Например, первичный ключ с несколькими столбцами должен быть определен как ограничение таблицы, но первичный ключ с одним столбцом может быть определен либо как ограничение таблицы, либо как ограничение столбца.

Имя столбца - это стандартный идентификатор. Если используются нестандартные символы (например, пробел или дефис), идентификатор должен быть заключен в кавычки в операторе CREATE TABLE, а также в любой другой ссылке.

За именем столбца следует индикатор типа. В SQLite тип не является обязательным, поскольку почти любой столбец может содержать любой тип данных. Столбцы SQLite технически не имеют типов, а скорее имеют сродство типов. Сродство описывает наиболее предпочтительный тип столбца и позволяет SQLite в некоторых случаях выполнять неявные преобразования. Однако сродство не ограничивает столбец определенным типом. Использование аффинности также объясняет тот факт, что формат типа чрезвычайно гибок, что позволяет использовать имена типов практически из любого диалекта SQL. Дополнительные сведения о том, как определяется и используется сродство типов, см. в разделе «Типы столбцов».

Если вы хотите убедиться, что используется определенное сродство, наиболее простые имена типов - INT, REAL, TEXT или BLOB. SQLite не использует внутренние ограничения по точности или размеру. Все целочисленные значения представляют собой 64-битные значения со знаком, все значения с плавающей запятой - 64-битные значения, а все текстовые и BLOB-значения имеют переменную длину.

Все таблицы имеют подразумеваемый корневой столбец, известный как ROWID, который используется внутри базы данных для индексации и хранения структуры таблицы базы данных. Этот столбец обычно не отображается и не возвращается в запросах, но к нему можно получить доступ напрямую, используя имя ROWID, _ROWID_ или OID. Альтернативные имена предоставлены для совместимости с другими механизмами баз данных. Как правило, значения ROWID никогда не следует использовать или манипулировать напрямую, а также столбец ROWID не должен использоваться непосредственно в качестве ключа таблицы. Чтобы использовать ROWID в качестве значения ключа, он должен быть связан с определяемым пользователем столбцом. См. «Ограничение PRIMARY KEY».

Ограничения столбца

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

Большинство ограничений столбца легко понять. Однако ограничение PRIMARY KEY немного уникально и обсуждается ниже в отдельном разделе.

Ограничение NOT NULL запрещает столбцу содержать записи NULL. Ограничение UNIQUE требует, чтобы все значения столбца были уникальными. Для этого ограничения для столбца будет создан автоматический уникальный индекс. Имейте в виду, что UNIQUE не подразумевает NOT NULL, а уникальные столбцы могут иметь более одной записи NULL. Это означает, что столбцы с ограничением UNIQUE имеют тенденцию также иметь ограничение NOT NULL.

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

CHECK ( typeof( column_name ) == 'integer' )

Ограничение DEFAULT определяет значение по умолчанию для столбца. Это значение используется, когда оператор INSERT не включает конкретное значение для этого столбца. DEFAULT может быть буквальным значением или выражением, если оно заключено в круглые скобки. Любое выражение должно иметь постоянное значение. Вы также можете использовать специальные значения CURRENT_TIME, CURRENT_DATE или CURRENT_TIMESTAMP. Они вставят соответствующее текстовое значение, указывающее время первого создания строки. Если ограничение DEFAULT не задано, значение по умолчанию будет NULL.

Ограничение COLLATION используется для присвоения столбцу определенного сопоставления. Это не только определяет порядок сортировки для столбца, но также определяет, как значения проверяются на равенство (что важно для таких вещей, как ограничения UNIQUE). SQLite включает три встроенных сопоставления: BINARY (по умолчанию), NOCASE и RTRIM. BINARY обрабатывает все значения как двоичные данные, которые должны точно совпадать. NOCASE похож на двоичный, только он нечувствителен к регистру для текстовых значений ASCII (в частности, коды символов <128). Также включен RTRIM (обрезка по правому краю), которая похожа на BINARY, но обрезает любые конечные пробелы из значений TEXT перед выполнением сравнений.

Наконец, столбцы могут содержать ограничение внешнего ключа REFERENCES. Если задано как ограничение столбца, ссылка на стороннюю таблицу может содержать не более одного имени внешнего столбца. Если ссылки на столбцы не указаны, внешняя таблица должна иметь первичный ключ с одним столбцом. Для получения дополнительной информации о внешних ключах см. раздел «Внешние ключи». Обратите внимание, что ограничение внешнего ключа на уровне столбца на самом деле не содержит слов FOREIGN KEY. Этот синтаксис предназначен для ограничений внешнего ключа на уровне таблицы.

Ограничения таблицы

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

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

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

Наконец, внешние ключи с несколькими столбцами определяются с ограничением FOREIGN KEY. Список столбцов локальной таблицы должен быть того же размера и в том же порядке, что и список внешних столбцов, предоставленный предложением REFERENCES. Дополнительную информацию о внешних ключах см. в разделе «Внешние ключи».

Ограничение PRIMARY KEY

Ограничение PRIMARY KEY используется для определения первичного ключа (или PK) для таблицы. С точки зрения дизайна и теории базы данных желательно, чтобы каждая таблица имела первичный ключ. Первичный ключ определяет основную цель таблицы, определяя конкретные точки данных, которые делают каждую строку уникальной и полной записью.

С практической точки зрения SQL не требует наличия PK в таблице. Фактически, SQL не требует, чтобы строки в таблице были уникальными. Тем не менее, у определения первичного ключа есть некоторые преимущества, особенно при использовании внешних ключей. В большинстве случаев внешний ключ в одной таблице будет ссылаться на первичный ключ другой таблицы, и явное определение первичного ключа может облегчить установление этой связи. SQLite также предоставляет некоторые дополнительные функции для первичных ключей с одним столбцом.

В таблице может быть только одно ограничение PRIMARY KEY. Его можно определить либо на уровне столбца, либо на уровне таблицы, но в каждой таблице может быть только один. Ограничение PRIMARY KEY подразумевает ограничение UNIQUE. Как и в случае автономного ограничения UNIQUE, это приведет к созданию автоматического уникального индекса (за одним исключением). В большинстве систем баз данных PRIMARY KEY также подразумевает NOT NULL, но из-за давней ошибки SQLite позволяет использовать записи NULL в столбце первичного ключа. Для правильного поведения убедитесь, что по крайней мере один столбец первичного ключа определен как NOT NULL.

Если столбец имеет идентификатор типа INTEGER (он может быть в верхнем или нижнем регистре, но должно быть точным словом «integer»), сортировку по возрастанию (по умолчанию) и имеет ограничение PRIMARY KEY для одного столбца, то этот столбец станет псевдонимом для столбца ROWID. За кулисами это делает столбец INTEGER PRIMARY KEY корневым столбцом, используемым внутри для индексации и хранения таблицы базы данных. Использование псевдонима ROWID обеспечивает очень быстрый доступ к строке, не требуя вторичного индекса. Кроме того, SQLite автоматически присваивает неиспользуемое значение ROWID любой строке, вставленной без явного значения столбца.

Столбцы, определенные как INTEGER PRIMARY KEY, действительно могут содержать только целые значения. Кроме того, в отличие от других столбцов первичного ключа, им присуще ограничение NOT NULL. Значения по умолчанию назначаются с использованием стандартного алгоритма распределения ROWID. Этот алгоритм автоматически присвоит значение, которое на единицу больше, чем наибольшее используемое в настоящее время значение ROWID. Если максимальное значение достигнуто, будет выбрано случайное (неиспользуемое) значение ROWID. По мере добавления и удаления строк из таблицы это позволяет повторно использовать значения ROWID.

Хотя повторное использование значений не является проблемой для внутренних значений ROWID, оно может вызвать проблемы для эталонных значений, которые могут скрываться в другом месте базы данных. Чтобы избежать проблем, ключевое слово AUTOINCREMENT можно использовать с INTEGER PRIMARY KEY, чтобы указать, что автоматически сгенерированные значения не должны повторно использоваться. Значения по умолчанию, присвоенные AUTOINCREMENT, будут на единицу больше, чем самое большое значение ROWID, которое когда-либо использовалось, но не зависят от каждого используемого значения. При достижении максимального значения возвращается ошибка.

При использовании псевдонима ROWID для автоматической генерации ключей обычной практикой является вставка новой строки и вызов функции SQL last_insert_rowid() или функции C sqlite3_last_insert_rowid() для получения только что присвоенного значения ROWID. Это значение можно использовать для вставки или обновления строк, которые ссылаются на вновь вставленную строку. Также всегда можно вставить строку с определенным значением ROWID (или псевдонимом ROWID).

Оговорка о конфликте

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

Действие по умолчанию - ON CONFLICT ABORT, которое будет пытаться отменить любые изменения, сделанные командой, которая вызвала нарушение ограничения, но в противном случае будет пытаться оставить любую текущую транзакцию на месте и действительной. Дополнительные сведения о других вариантах разрешения конфликтов см. в разделе UPDATE. Обратите внимание, что предложение разрешения конфликтов в UPDATE и INSERT применяется к действиям, предпринимаемым самими командами UPDATE и INSERT. Любое предложение разрешения конфликтов, обнаруженное в операторе CREATE TABLE, применяется к любой команде, работающей с таблицей.

См. также
DROP TABLE, INSERT, UPDATE, CREATE INDEX
CREATE TRIGGER
Создание нового действия триггера
Синтаксис
Стандартное использование
CREATE TRIGGER database_name.trigger_name BEFORE INSERT ON table_name FOR EACH ROW BEGIN stmt1; stmt2; END; CREATE TRIGGER access_audit BEFORE UPDATE ON access FOR EACH ROW BEGIN INSERT INTO audit_trail VALUES ( OLD.level, NEW.level, CURRENT_TIMESTAMP ); END;
Описание

Команда CREATE TRIGGER создает триггер и связывает его с таблицей или представлением. Когда условия, определенные триггером, выполняются, триггер «срабатывает», автоматически выполняя операторы, найденные в теле триггера (часть между BEGIN и END). С таблицей или представлением может быть связано любое количество триггеров, включая несколько триггеров одного типа.

Если присутствует необязательное ключевое слово TEMP или TEMPORARY, в базе данных temp будет создан триггер. Триггер также можно сделать временным, указав имя триггера с именем базы данных temp. Если имя триггера уточнено именем базы данных, указание TEMP или TEMPORARY приведет к ошибке, даже если заданное имя базы данных - temp.

Временные триггеры могут быть прикреплены к временным или стандартным (невременным) таблицам. Можно выбрать конкретный экземпляр таблицы, уточнив имя таблицы с именем базы данных. Во всех остальных случаях триггер и таблица должны находиться в одной базе данных. Имя триггера или имя таблицы можно дополнить именем базы данных (или обоими, если они совпадают).

Триггеры, связанные с таблицами, могут быть триггерами BEFORE или AFTER. Если время не указано, используется BEFORE. Время указывает, срабатывает ли триггер до или после определенного действия триггера. В обоих случаях действие проверяется до срабатывания триггера. Например, триггер BEFORE INSERT не сработает, если вставка вызовет нарушение ограничения.

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

Триггеры, связанные с представлениями, должны быть триггерами INSTEAD OF. По умолчанию время просмотра по-прежнему равно BEFORE, поэтому необходимо указать INSTEAD OF. Как видно из названия, триггеры INSTEAD OF срабатывают вместо определенного действия. Хотя представления в SQLite доступны только для чтения, определение одного или нескольких триггеров INSTEAD OF DELETE, INSERT, или UPDATE позволит запускать эти команды для представления. Очень часто представления содержат целую серию триггеров INSTEAD OF для обработки различных комбинаций обновлений столбцов.

Стандарт SQL определяет как триггеры FOR EACH ROW, так и FOR EACH STATEMENT. SQLite поддерживает только триггеры FOR EACH ROW, которые срабатывают один раз для каждой строки, затронутой указанным условием. Это делает предложение FOR EACH ROW необязательным в SQLite. Однако некоторые популярные базы данных, поддерживающие оба типа триггеров, по умолчанию будут использовать триггеры FOR EACH STATEMENT, поэтому рекомендуется явное использование предложения FOR EACH ROW.

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

Само тело триггера состоит из одного или нескольких операторов INSERT, UPDATE, DELETE или SELECT. Первые три команды можно использовать как обычно. Оператор SELECT может использоваться для вызова пользовательских функций. Любые результаты, возвращаемые автономным оператором SELECT, будут проигнорированы. Идентификаторы таблиц в теле триггера не могут быть уточнены именем базы данных. Все идентификаторы таблиц должны быть из той же базы данных, что и таблица триггеров.

И предложение WHEN, и тело триггера имеют доступ к некоторым дополнительным квалификаторам столбцов. Столбцы, связанные с таблицей триггеров (или представлением), могут быть квалифицированы с помощью псевдоидентификатора NEW (в случае триггеров INSERT и UPDATE) или OLD (в случае триггеров UPDATE и DELETE). Они представляют собой значения до и после рассматриваемой строки и действительны только для текущей строки, которая вызвала срабатывание триггера.

Команды, находящиеся в теле триггера, также могут использовать выражение RAISE для создания исключения. Это можно использовать для игнорирования, отката, прерывания или отказа текущей строки в случае ошибки. Для получения дополнительной информации см. разделы RAISE и UPDATE.

Есть некоторые дополнительные ограничения на триггерные тела. В теле триггера команды UPDATE и DELETE не могут использовать переопределения индекса (INDEXED BY, NOT INDEXED), а также не поддерживается синтаксис ORDER BY...LIMIT (даже если поддержка была включена должным образом). Синтаксис INSERT...DEFAULT VALUES также не поддерживается. Если триггер срабатывает в результате команды с явным предложением ON CONFLICT, разрешение конфликтов более высокого уровня переопределит любое предложение ON CONFLICT, обнаруженное в теле триггера.

Если триггер изменяет строки из той же таблицы, к которой он присоединен, настоятельно рекомендуется использовать триггеры AFTER. Если триггер BEFORE изменяет строки, которые являются частью исходного оператора (той, которая вызвала срабатывание триггера), результаты могут быть неопределенными. Кроме того, значение NEW.ROWID недоступно для триггеров BEFORE INSERT, если не указано явное значение.

Если таблица удаляется, все ее триггеры автоматически удаляются. Точно так же, если таблица переименована (с помощью ALTER TABLE), все связанные триггеры будут обновлены. Однако удаление или изменение таблицы не приведет к обновлению ссылок, найденных в теле триггера. Если таблица удаляется или переименовывается, убедитесь, что все триггеры, которые на нее ссылаются, также обновлены. В противном случае при срабатывании триггера возникнет ошибка.

Создание уже существующего триггера обычно вызывает ошибку. Если предоставляется необязательное предложение IF NOT EXISTS, эта ошибка игнорируется. Это оставляет исходное определение (и данные) на месте.

И последнее замечание. Некоторые синтаксисы и многие функциональные ограничения CREATE TRIGGER проверяются при выполнении, а не при создании. Тот факт, что команда CREATE TRIGGER вернулась без ошибок, не означает, что описание триггера допустимо. Настоятельно рекомендуется проверять и тестировать все триггеры. Если триггер обнаруживает ошибку, эта ошибка будет передана оператору, вызвавшему срабатывание триггера. Это может вызвать недоумение, например, команды, вызывающие ошибки в таблицах или столбцах, которые не являются частью исходного оператора. Если команда вызывает необъяснимую или странную ошибку, проверьте, нет ли неисправных триггеров, связанных с какой-либо из таблиц, на которые ссылается команда.

См. также
DROP TRIGGER, CREATE TABLE, CREATE VIEW, INSERT, UPDATE, DELETE, RAISE [SQL Expr, Ap D]
======================== 341 дальше не читал
CREATE VIEW
Создать новое представление
Синтаксис
Стандартное использование
CREATE VIEW database_name.view_name AS SELECT...;
Описание

Оператор CREATE VIEW устанавливает новое представление в указанной базе данных. Представление действует как предварительно упакованный оператор подзапроса, и к нему можно обращаться и ссылаться, как если бы это была таблица. Представление на самом деле не является экземпляром данных, но динамически создается при каждом доступе к нему.

Если присутствует необязательное ключевое слово TEMP или TEMPORARY, представление будет создано в базе данных temp. Указание TEMP или TEMPORARY в дополнение к явному имени базы данных приведет к ошибке, если имя базы данных не является temp.

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

Создание уже существующего представления обычно вызывает ошибку. Если предоставляется необязательное предложение IF NOT EXISTS, эта ошибка игнорируется. Это оставляет исходное определение на месте.

См. также
DROP VIEW, CREATE TABLE, SELECT
CREATE VIRTUAL TABLE
Создать новую виртуальную таблицу
Синтаксис
Стандартное использование
CREATE VIRTUAL TABLE database_name.table_name USING weblog( access.log ); CREATE VIRTUAL TABLE database_name.table_name USING fts3( );
Описание

Команда CREATE VIRTUAL TABLE создает виртуальную таблицу. Виртуальные таблицы - это источники данных, которые определяются кодом и могут представлять высоко оптимизированные источники данных или внешние источники данных. Стандартный дистрибутив SQLite включает реализации виртуальных таблиц для полнотекстового поиска, а также систему индексирования на основе R * Tree.

Виртуальные таблицы подробно рассматриваются в главе 10.

Виртуальная таблица удаляется стандартной командой DROP TABLE.

См. также
sqlite3_create_module() [C API, Ap G], DROP TABLE
DELETE
Удалить строки из таблицы
Синтаксис
Стандартное использование
DELETE FROM database_name.table_name; DELETE FROM database_name.table_name WHERE id = 42;
Описание

Команда DELETE безвозвратно удаляет строки из таблицы. Любая строка, удовлетворяющая выражению WHERE, будет удалена. Условие WHERE, при котором не удаляются никакие строки, не считается ошибкой. Если условие WHERE не указано, предполагается, что оно всегда истинно, и каждая строка в таблице будет удалена.

Команда DELETE без предложения WHERE удалит каждую строку в таблице.

Если предложение WHERE не указано, в некоторых ситуациях SQLite может просто усечь всю таблицу. Это намного быстрее, чем удаление каждой строки по отдельности, но при этом пропускается любая обработка каждой строки. Усечение произойдет только в том случае, если таблица не имеет триггеров и не является частью отношения внешнего ключа (при условии, что поддержка внешнего ключа включена). Усечение также можно отключить, если авторизатор вернет SQLITE_IGNORE для операции удаления (см. Sqlite3_set_authorizer()).

Если библиотека SQLite была скомпилирована с необязательной директивой SQLITE_ENABLE_UPDATE_DELETE_LIMIT, необязательное предложение ORDER BY...LIMIT может использоваться для удаления определенного количества строк. Более подробную информацию см. на веб-сайте SQLite.

Когда в теле триггера появляется DELETE, применяются дополнительные ограничения. См. СОЗДАТЬ ТРИГГЕР.

Удаление данных из таблицы не приведет к уменьшению размера файла базы данных, если не включен режим автоматического вакуумирования. Чтобы восстановить пространство, ранее занятое удаленными данными, необходимо запустить команду VACUUM.

См. также
INSERT, UPDATE, VACUUM, auto_vacuum [PRAGMA, Ap F], CREATE TRIGGER
DETACH DATABASE
Отсоединить файл базы данных
Синтаксис
Стандартное использование
DETACH DATABASE database_name;
Описание

Команда DETACH DATABASE отключает указанную базу данных от соединения с базой данных. Если один и тот же файл был прикреплен несколько раз, это только отсоединит названное вложение. Если база данных находится в оперативной памяти или является временной базой данных, база данных будет уничтожена, а ее содержимое потеряно.

Вы не можете отсоединить основную или временную базы данных. Команда DETACH завершится ошибкой, если будет введена внутри транзакции.

См. также
ATTACH DATABASE
DROP INDEX
Удалить индекс таблицы из базы данных
Синтаксис
Стандартное использование
DROP INDEX database_name.index_name;
Описание

Команда DROP INDEX удаляет явно созданный индекс. Индекс и все содержащиеся в нем данные удаляются из базы данных. Таблица, на которую ссылается индекс, не изменяется. Вы не можете отбросить автоматически сгенерированные индексы, например те, которые применяют уникальные ограничения, объявленные в определениях таблиц.

Удаление несуществующего индекса обычно вызывает ошибку. Если предоставляется необязательное предложение IF EXISTS, эта ошибка игнорируется.

См. также
CREATE INDEX, CREATE TABLE, DROP TABLE
DROP TABLE
Удаление таблицы из базы данных
Синтаксис
Стандартное использование
DROP TABLE database_name.table_name;
Описание

Команда DROP TABLE удаляет таблицу из базы данных. Таблица и все содержащиеся в ней данные безвозвратно удаляются из базы данных. Также удаляются все связанные индексы и триггеры. Представления, которые могут ссылаться на таблицу, не удаляются. Триггеры удаления не срабатывают.

Команда DROP TABLE также может использоваться для удаления виртуальных таблиц. В этом случае запрос на уничтожение отправляется модулю таблицы, который может делать то, что считает нужным.

Если внешние ключи включены, команда DROP TABLE выполнит эквивалент DELETE для каждой строки в таблице. Это происходит после того, как любые связанные триггеры были отброшены, поэтому это не приведет к срабатыванию каких-либо триггеров удаления. Если какие-либо непосредственные ограничения ключа нарушаются, команда DROP TABLE завершится ошибкой. Если какие-либо отложенные ограничения нарушены, при фиксации транзакции будет возвращена ошибка.

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

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

См. также
CREATE TABLE, ALTER TABLE, DROP INDEX, DROP TRIGGER, auto_vacuum [PRAGMA, Ap F], VACUUM
DROP TRIGGER
Удалить действие триггера из базы данных
Синтаксис
Стандартное использование
DROP TRIGGER database_name.trigger_name;
Описание

Команда DROP TRIGGER удаляет триггер из базы данных. Триггер также будет удален при удалении связанной таблицы.

Удаление несуществующего триггера обычно вызывает ошибку. Если предоставляется необязательное предложение IF EXISTS, эта ошибка игнорируется.

См. также
CREATE TRIGGER, DROP TABLE
DROP VIEW
Удаление представления из базы данных
Синтаксис
Стандартное использование
DROP VIEW database_name.view_name;
Описание

Команда DROP VIEW удаляет представление из базы данных. Хотя представление больше не будет доступно, никакие данные, на которые имеются ссылки, не будут изменены. Удаление представления также удалит все связанные с ним триггеры.

Удаление несуществующего представления обычно вызывает ошибку. Если предоставляется необязательное предложение IF EXISTS, эта ошибка игнорируется.

См. также
CREATE VIEW, CREATE TABLE, DROP TRIGGER
END TRANSACTION
Завершение и фиксация транзакции
См. также
COMMIT TRANSACTION
EXPLAIN
Описание плана запроса
Синтаксис
Стандартное использование
EXPLAIN INSERT ...; EXPLAIN QUERY PLAN SELECT ...;
Описание

Команда EXPLAIN дает представление о внутренней работе базы данных. Размещение EXPLAIN перед любым оператором SQL (кроме самого себя) возвращает информацию о том, как SQLite будет выполнять данный оператор SQL. Оператор SQL фактически не выполняется.

Сама по себе EXPLAIN вернет набор результатов, который описывает внутреннюю последовательность инструкций VDBE, используемую для выполнения предоставленного оператора SQL. Чтобы понять результат, требуется изрядное количество знаний.

Полный вариант EXPLAIN QUERY PLAN будет возвращать высокоуровневую сводку плана запроса с использованием англоязычных объяснений того, как будет собран запрос, и будут ли данные доступны при сканировании таблицы или поиске по индексу. Команда EXPLAIN QUERY PLAN наиболее полезна для настройки операторов SELECT и корректировки размещения индекса.

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

INSERT
Вставить новые строки в таблицу
Синтаксис
Стандартное использование
INSERT INTO database_name.table_name ( col1, col2 ) VALUES ( val1, val2 ); INSERT INTO table_name VALUES ( val1, val2, val3... ); INSERT INTO table_name ( col1, col2 ) SELECT c1, c2 FROM...; INSERT INTO table_name DEFAULT VALUES; INSERT OR IGNORE INTO table_name ( col1, col2 ) VALUES ( val1, val2 ); REPLACE INTO table_name ( col1, col2 ) VALUES ( val1, val2 );
Описание

Команда INSERT добавляет новые строки в таблицы. Отдельная команда INSERT может вставлять строки только в одну таблицу, и в большинстве случаев может вставлять только одну строку за раз. Есть несколько вариантов команды ВСТАВИТЬ. Основные различия касаются того, как указываются столбцы и значения.

Основной формат команды INSERT начинается с командного слова INSERT, за которым следует необязательное условие разрешения конфликтов (OR ROLLBACK и т. д.). Предложение разрешения конфликта INSERT идентично предложению, найденному в команде UPDATE. См. ОБНОВЛЕНИЕ для получения дополнительной информации о различных вариантах разрешения конфликтов и их поведении. Также можно использовать командное слово REPLACE, которое является просто сокращением для INSERT OR REPLACE. Более подробно это обсуждается ниже.

После предложения о конфликте команда объявляет, с какой таблицей она действует. Обычно за этим следует список столбцов в круглых скобках. Этот список столбцов определяет, какие столбцы будут иметь значения, установленные во вновь вставленной строке. Если список столбцов не указан, предполагается, что список столбцов по умолчанию включает все столбцы таблицы в том порядке, в котором они указаны в определении таблицы. Список по умолчанию не будет включать необработанный столбец ROWID, но будет включен любой столбец псевдонима ROWID (INTEGER PRIMARY KEY). Если указан явный список столбцов, список может содержать любой столбец (включая столбец ROWID) в любом порядке.

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

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

Любому столбцу таблицы, который не отображается в списке столбцов вставки, назначается значение по умолчанию. Если в определении таблицы не указано иное, значением по умолчанию является NULL. Если у вас есть столбец псевдонима ROWID, которому вы хотите присвоить автоматическое значение, вы должны использовать явный список столбцов, и этот список не должен включать псевдоним ROWID. Если столбец содержится в списке столбцов явно или по умолчанию, для этого столбца должно быть указано значение. Невозможно указать значение по умолчанию, кроме как оставить столбец вне списка столбцов или узнать значение по умолчанию и явно вставить его.

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

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

Из-за расходов, связанных с фиксацией транзакции, очень распространено группирование нескольких вставок в одну транзакцию. Особенно в случае массовых вставок нередко объединять 1000 или даже 10000 или более команд INSERT в одну транзакцию, что обеспечивает гораздо более высокую скорость вставки. Просто поймите, что если возникнет ошибка, бывают ситуации, когда откатывается вся транзакция. Хотя это может быть приемлемо для загрузки массовых данных из файла (которые можно просто запустить повторно), это может быть неприемлемо для данных, которые вставляются из потока данных в реальном времени. Пакетные транзакции значительно ускоряют работу, но они также могут значительно усложнить восстановление после ошибки.

Последнее слово о команде ВСТАВИТЬ ИЛИ ЗАМЕНИТЬ. Этот тип команды часто используется в системах отслеживания событий, где требуется отметка времени «последнего посещения». Когда событие обрабатывается, системе необходимо либо вставить новую строку (если этот тип события никогда раньше не наблюдался), либо обновить существующую запись. Хотя вариант INSERT OR REPLACE кажется идеальным для этого, он имеет некоторые специфические ограничения. Самое главное, команда действительно является «вставкой или заменой», а не «вставкой или обновлением». Параметр REPLACE полностью удаляет все старые строки перед обработкой запроса INSERT, что делает неэффективным обновление подмножества столбцов. Чтобы эффективно использовать опцию INSERT OR REPLACE, INSERT должен иметь возможность полностью заменять существующую строку, а не просто обновлять подмножество столбцов.

См. также
UPDATE, BEGIN TRANSACTION
PRAGMA
Найти или изменить конфигурацию
Синтаксис
Стандартное использование
PRAGMA page_size; PRAGMA cache_size = 5000; PRAGMA table_info( table_name );
Описание

Команда PRAGMA настраивает и настраивает внутренние компоненты SQLite. Это что-то вроде команды catchall, используемой для настройки или запроса параметров конфигурации как для ядра базы данных, так и для файлов базы данных. Его также можно использовать для запроса информации о базе данных, например, список таблиц, индексы, информация о столбцах и другие аспекты схемы.

Команда PRAGMA - единственная команда, кроме SELECT, которая может возвращать несколько строк.

В приложении F подробно описаны различные команды PRAGMA.

См. также
Приложение F
REINDEX
Перестроить индекс из исходных данных
Синтаксис
Стандартное использование
REINDEX collation_name; REINDEX database_name.table_name; REINDEX database_name.index_name;
Описание

Команда REINDEX удаляет данные в индексе и перестраивает структуру индекса из данных исходной таблицы. Таблица, на которую ссылается индекс, не изменяется.

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

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

См. также
CREATE INDEX, DROP INDEX
RELEASE SAVEPOINT
Удаление и освобождение точки сохранения из журнала транзакций
Синтаксис
Стандартное использование
RELEASE savepoint_name;
Описание

Команда RELEASE SAVEPOINT удаляет точку сохранения из журнала транзакций. Это указывает на то, что любые изменения, сделанные с момента установки указанной точки сохранения, были приняты логикой приложения.

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

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

См. также
SAVEPOINT, ROLLBACK TRANSACTION, COMMIT TRANSACTION, BEGIN TRANSACTION
REPLACE
Удаление и повторная вставка существующей строки
Описание
Команда REPLACE является псевдонимом для варианта INSERT OR REPLACE команды INSERT.
См. также
INSERT
ROLLBACK TRANSACTION
Отменить часть или всю текущую транзакцию
Синтаксис
Стандартное использование
ROLLBACK; ROLLBACK TO SAVEPOINT savepoint_name;
Описание

Команда ROLLBACK используется для отката состояния транзакции. Это аналогично функции отмены для транзакций.

Есть две формы ROLLBACK. Самая простая форма не имеет предложения TO и вызывает откат всей транзакции. Все изменения и модификации, внесенные в базу данных в рамках транзакции, отменяются, транзакция освобождается, а соединение с базой данных возвращается в режим автоматической фиксации без активной транзакции.

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

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

См. также
BEGIN TRANSACTION, COMMIT TRANSACTION, SAVEPOINT, RELEASE SAVEPOINT
SAVEPOINT
Поместите маркер точки сохранения в последовательность команд транзакции
Синтаксис
Стандартное использование
SAVEPOINT savepoint_name;
Описание

Команда SAVEPOINT создает маркер точки сохранения в журнале транзакций. Если в процессе нет активной транзакции, точка сохранения будет отмечена, и будет выдано неявное действие BEGIN DEFERRED TRANSACTION.

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

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

См. также
RELEASE SAVEPOINT, ROLLBACK TRANSACTION, BEGIN TRANSACTION
SELECT
Запрос данных из базы данных
Синтаксис

result-column:

join-source:

single-source:

ordering-term:

compound-operator:

Стандартное использование
SELECT * FROM tbl; SELECT name FROM employees WHERE employee_id = 54923; SELECT 5 + 6;
Описание

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

Вся глава 5 посвящена команде SELECT.

Базовый формат

Основная команда SELECT следует простому шаблону, который можно грубо описать как фильтр SELECT output FROM input WHERE. Раздел вывода описывает данные, составляющие набор результатов, раздел ввода описывает, какие таблицы, представления, подзапросы и т. д. Будут действовать как источники данных, а раздел фильтра устанавливает условия, при которых строки являются частью набора результатов и какие строки отфильтрованы.

SELECT может иметь значение SELECT ALL (по умолчанию) или SELECT DISTINCT. Ключевое слово ALL возвращает все строки в наборе результатов, независимо от их состава. Ключевое слово DISTINCT заставит оператор select исключить повторяющиеся результаты в наборе результатов. Обычно вычисление больших результатов DISTINCT приводит к значительному снижению производительности.

Столбцы набора результатов определяются серией выражений, разделенных запятыми. Каждый оператор SELECT должен иметь хотя бы одно результирующее выражение. Эти выражения часто состоят только из имени исходного столбца, хотя могут быть любым общим выражением. Символ * означает «вернуть все столбцы» и будет включать все столбцы стандартной таблицы из всех исходных таблиц. Все стандартные столбцы конкретной исходной таблицы могут быть возвращены в формате имя_таблицы. *. В обоих случаях столбец ROWID не будет включен, хотя столбцы псевдонима ROWID будут включены. Виртуальные таблицы также могут помечать некоторые столбцы как скрытые. Как и столбец ROWID, скрытые столбцы не будут возвращаться по умолчанию, но могут быть явно названы в качестве столбца результатов.

Столбцам результатов можно дать явные имена с помощью необязательного предложения AS (фактическое ключевое слово AS также является необязательным). Если не указано предложение AS, имя выходного столбца остается на усмотрение механизма базы данных. Если приложение зависит от сопоставления имен определенных выходных столбцов, столбцам следует дать явные имена с предложением AS.

Предложение FROM определяет, откуда берутся данные и как они перемешиваются. Если предложение FROM не указано, оператор SELECT вернет только одну строку. Каждый источник объединяется запятой или операцией JOIN. Запятая действует как безусловное CROSS JOIN. Различные источники, включая таблицы, подзапросы или другие операторы JOIN, могут быть сгруппированы вместе в большую временную таблицу, которая передается через остальную часть оператора SELECT и в конечном итоге используется для создания набора результатов. Дополнительную информацию о конкретных операторах JOIN см. в разделе «Предложение FROM».

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

Предложение WHERE используется для фильтрации строк. Концептуально предложение FROM вместе с объединениями используется для определения большой таблицы, состоящей из всех возможных комбинаций строк. Предложение WHERE оценивается для каждой из этих строк, передавая только те строки, которые имеют значение true. Предложение WHERE также можно использовать для определения условий соединения, эффективно заставляя предложение FROM производить декартово произведение двух таблиц, и используйте предложение WHERE, чтобы отфильтровать только те строки, которые соответствуют условию соединения.

Дополнительные предложения

Помимо SELECT, FROM и WHERE, оператор SELECT может выполнять дополнительную обработку с помощью GROUP BY, HAVING, ORDER BY и LIMIT.

Предложение GROUP BY позволяет сворачивать наборы строк в наборе результатов в отдельные строки. Группы строк, которые имеют одинаковые значения во всех выражениях, перечисленных в предложении GROUP BY, будут сжаты в одну строку. Обычно каждая ссылка на исходный столбец в выражениях набора результатов должна быть столбцом или выражением, включенным в предложение GROUP BY, или столбец должен отображаться как параметр агрегатной функции. Значение любого другого исходного столбца - это значение последней строки в группе, которая должна быть обработана, что фактически делает результат неопределенным. Если выражение GROUP BY является буквальным целым числом, предполагается, что это индекс столбца для набора результатов. Например, предложение GROUP BY 2 сгруппирует набор результатов, используя значения во втором столбце результатов.

Предложение HAVING можно использовать только вместе с предложением GROUP BY. Как и предложение WHERE, выражение HAVING используется в качестве фильтра строк. Ключевое отличие состоит в том, что выражение HAVING применяется после любой манипуляции с GROUP BY. Эта последовательность позволяет выражению HAVING фильтровать совокупные выходные данные. Имейте в виду, что предложение WHERE обычно более эффективно, так как оно может исключать строки ранее в конвейере SELECT. Если возможно, фильтрацию следует выполнять в предложении WHERE, сохраняя предложение HAVING для фильтрации агрегированных результатов.

Предложение ORDER BY сортирует набор результатов в определенном порядке. Обычно порядок вывода не определен. Строки возвращаются по мере того, как они становятся доступными, и не предпринимается никаких попыток вернуть результаты в каком-либо определенном порядке. Предложение ORDER BY может использоваться для обеспечения определенного порядка вывода. Вывод сортируется по каждому выражению в предложении, в свою очередь, от наиболее конкретного к наименее конкретному. Тот факт, что вывод SELECT может быть упорядочен, является одним из ключевых различий между таблицей SQL и набором результатов. Как и в случае с GROUP BY, если одно из выражений ORDER BY является буквальным целым числом, предполагается, что оно является индексом столбца для набора результатов.

Наконец, предложение LIMIT может использоваться для управления количеством возвращаемых строк, начиная с определенного смещения. Если смещение не указано, LIMIT будет начинаться с начала набора результатов. Обратите внимание, что два варианта синтаксиса (запятая или OFFSET) предоставляют параметры в обратном порядке.

Поскольку порядок строк результата не определен, LIMIT чаще всего используется вместе с предложением ORDER BY. Хотя это не является строго обязательным, включение ORDER BY придает некоторый смысл предельным значениям и значениям смещения. Очень мало случаев, когда имеет смысл использовать LIMIT без какого-либо наложенного порядка.

Составные операторы

Составные операторы позволяют объединить один или несколько подзапросов SELECT...FROM...WHERE...GROUP BY...HAVING с помощью операций над множествами. SQLite поддерживает составные операторы UNION, UNION ALL, INTERSECT и EXCEPT. Каждый оператор SELECT в составном SELECT должен возвращать одинаковое количество столбцов. Имена столбцов набора результатов будут взяты из первого оператора SELECT.

Оператор UNION возвращает объединение операторов SELECT. По умолчанию оператор UNION является правильным оператором набора и возвращает только отдельные строки (включая строки из одной таблицы). UNION ALL, напротив, вернет полный набор строк, возвращаемых каждым SELECT. Оператор UNION ALL значительно дешевле, чем оператор UNION, поэтому по возможности рекомендуется использовать UNION ALL.

Команда INTERSECT вернет набор строк, которые появляются в обоих операторах SELECT. Как и UNION, оператор INTERSECT является правильной операцией над набором и возвращает только один экземпляр каждой уникальной строки, независимо от того, сколько раз эта строка появляется в обоих наборах результатов отдельных операторов SELECT.

Составной оператор EXCEPT действует как оператор вычитания по множеству. Будут возвращены все уникальные строки в первом SELECT, которые не отображаются во втором SELECT.

См. также
CREATE TABLE, INSERT, UPDATE, DELETE
UPDATE
Изменение существующих строк в таблице
Синтаксис
Стандартное использование
UPDATE database_name.table_name SET col5 = val5, col2 = val2 WHERE id = 42;
Описание

Команда UPDATE изменяет одно или несколько значений столбцов в существующих строках таблицы. Команда начинается с предложения о разрешении конфликтов, за которым следует имя таблицы, содержащей строки, которые мы обновляем. За именем таблицы следует список имен столбцов и новых значений. Последнее предложение WHERE определяет, какие строки обновляются. Условие WHERE, при котором не обновляются никакие строки, не считается ошибкой. Если предложение WHERE не указано, каждая строка в таблице обновляется.

Команда UPDATE без предложения WHERE обновит каждую строку в таблице.

Значения, которые используются для обновления строки, могут быть заданы в виде выражений. Эти выражения оцениваются в контексте значений исходной строки. Это позволяет выражению значения ссылаться на старое значение строки. Например, чтобы увеличить значение столбца на единицу, вы можете найти SQL, похожий на UPDATE...SET col = col + 1 WHERE.... Столбцы и значения могут быть указаны в любом порядке, если они появляются парами. Любой столбец, включая ROWID, может быть обновлен. Любые столбцы, которые не отображаются в команде UPDATE, остаются неизменными. Невозможно вернуть столбцу значение по умолчанию.

Если библиотека SQLite была скомпилирована с необязательной директивой SQLITE_ENABLE_UPDATE_DELETE_LIMIT, необязательное предложение ORDER BY...LIMIT может использоваться для обновления определенного количества строк. Дополнительную информацию см. на веб-сайте SQLite (http://www.sqlite.org/lang_update.html).

Необязательное предложение разрешения конфликтов в начале команды UPDATE (или INSERT) является нестандартным расширением, контролирующим реакцию SQLite на нарушение ограничения. Например, если столбец должен быть уникальным, любая попытка обновить значение этого столбца до значения, которое уже используется другой строкой, приведет к нарушению ограничения. Предложение разрешения ограничений определяет, как разрешается эта ситуация.

ROLLBACK
ROLLBACK выполняется немедленно, откатывая любую текущую транзакцию. Вызывающему процессу возвращается ошибка SQLITE_CONSTRAINT. Если в настоящее время не выполняется явная транзакция, поведение будет идентично ABORT.
ABORT
Это поведение по умолчанию. Изменения, вызванные текущей командой, отменяются, и возвращается SQLITE_CONSTRAINT, но ROLLBACK не выполняется. Например, если при четвертом из десяти возможных обновлений строки обнаруживается нарушение ограничения, первые три строки будут отменены, но текущая транзакция останется активной.
FAIL
Команда завершится ошибкой и вернет SQLITE_CONSTRAINT, но любые строки, которые были ранее изменены, не будут возвращены. Например, если нарушение ограничения обнаруживается при четвертом из десяти возможных обновлений строки, первые три модификации останутся на месте, и дальнейшая обработка будет прервана. Текущая транзакция не будет зафиксирована или отменена.
IGNORE
Любая ошибка нарушения ограничения игнорируется. Обновление строки не будет обработано, но ошибка не будет возвращена. Например, если нарушение ограничения обнаруживается в четвертой из десяти возможных строк, остаются не только первые три модификации строки, но и обработка продолжается с оставшимися строками.
REPLACE

Конкретное действие, предпринимаемое разрешением REPLACE, зависит от того, какой тип ограничения нарушен.

Если ограничение UNIQUE нарушается, существующие строки, вызывающие нарушение ограничения, сначала удаляются, а затем разрешается обработка UPDATE (или INSERT). Ошибка не возвращается. Это позволяет выполнить команду, но может привести к удалению одной или нескольких строк. В этом случае любые триггеры удаления, связанные с этой таблицей, не сработают, если не включены рекурсивные триггеры. В настоящее время ловушка обновления не вызывается для автоматически удаляемых строк, и при этом не увеличивается счетчик изменений. Однако эти два поведения могут измениться в будущей версии.

Если ограничение NOT NULL нарушается, NULL заменяется значением по умолчанию для этого столбца. Если значение по умолчанию не определено, используется разрешение ABORT.

Если ограничение CHECK нарушается, используется разрешение IGNORE.

Любое предложение разрешения конфликта, найденное в команде UPDATE (или INSERT), переопределит любое предложение разрешения конфликта, найденное в определении таблицы.

См. также
INSERT, DELETE, CREATE TABLE
VACUUM
Восстановление свободного места и оптимизация базы данных
Синтаксис
Стандартное использование
VACUUM;
Описание

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

Когда объекты данных (строки, целые таблицы, индексы и т. д. ) удаляются или удаляются из базы данных, размер файла остается неизменным. Любые страницы базы данных, восстановленные из удаленных объектов, просто помечаются как свободные и доступные для любых будущих потребностей в хранении базы данных. В результате при обычных операциях размер файла базы данных может только увеличиваться.

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

Наконец, когда строки вставляются, обновляются и удаляются, неиспользуемые блоки данных и другие «дыры» могут появляться на отдельных страницах базы данных. Это уменьшает количество записей, которые могут храниться на одной странице, увеличивая общее количество страниц, необходимых для хранения таблицы. Фактически это увеличивает накладные расходы на хранилище для таблицы, увеличивая время чтения / записи и снижая производительность кеша.

Процесс вакуумирования решает все три проблемы путем копирования всех данных из файла базы данных в отдельную временную базу данных. Эта передача данных выполняется на довольно высоком уровне, имея дело с логическими элементами базы данных. В результате отдельные страницы базы данных «перепаковываются», объекты данных дефрагментируются, а свободные страницы игнорируются. Это оптимизирует пространство для хранения, сокращает время поиска и восстанавливает любое свободное пространство из файла базы данных. Как только все это будет сделано, содержимое временного файла базы данных копируется обратно в исходный файл.

Поскольку команда VACUUM восстанавливает файл базы данных с нуля, VACUUM также можно использовать для изменения многих параметров конфигурации, специфичных для базы данных. Например, вы можете настроить размер страницы, формат файла, кодировку по умолчанию и ряд других параметров, которые обычно становятся фиксированными после создания файла базы данных. Чтобы что-то изменить, просто установите новые значения прагмы базы данных по умолчанию на все, что вы хотите, и очистите базу данных.

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

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

По логике, содержимое базы данных должно оставаться неизменным из ВАКУУМА. Единственное исключение - значения ROWID. Столбцы, помеченные как INTEGER PRIMARY KEY, будут сохранены, но значения ROWID без элайсинга могут быть сброшены. Также, индексы перестраиваются с нуля, а не копируются, поэтому VACUUM выполняет эквивалент REINDEX для каждого индекса в базе данных.

Как правило, любую достаточно динамичную базу данных следует периодически очищать. Хорошее практическое правило - рассматривать полный ВАКУУМ каждый раз, когда изменяется от 30% до 40% содержимого базы данных. Также может быть полезно ВАКУУМОВАТЬ базу данных после удаления большой таблицы или индекса.

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

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

См. также
auto_vacuum [PRAGMA, Ap F], temp_store [PRAGMA, Ap F], temp_store_directory [PRAGMA, Ap F], REINDEX

Приложение D
Справочник по выражениям SQL SQLite

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

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

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

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

Если вы хотите поиграть с оператором или конструкцией выражения вне более крупного запроса, помните, что вы можете выполнить любое произвольное выражение, просто поместив его в оператор SELECT:

SELECT 1 + 1;

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

Литеральные выражения

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

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

NULL

NULL представлен простым ключевым словом NULL.

NULL
Целое число (Integer)

Целое число представлено простой последовательностью числовых цифр. Все целые числа должны быть даны по основанию 10. Префикс из нулевых цифр не представляет восьмеричные числа, и не поддерживаются шестнадцатеричные целые числа. Разделители величин (например, запятая после цифры тысяч) не допускаются. Перед числом можно поставить знак + или -, чтобы обозначить знак числа:

8632  -- Eight thousand, six hundred, thirty-two
0032  -- Thirty-two
-5    -- Negative five
+17   -- Seventeen
Вещественное или с плавающей запятой (Real или floating-point)

Действительное число представлено простой последовательностью числовых цифр, за которой следует точка (десятичная точка), за которой следует другая последовательность числовых цифр. Любая из числовых последовательностей слева или справа от десятичной точки может быть опущена, но не обе сразу. SQLite всегда использует точку в качестве десятичной точки, независимо от настроек интернационализации. Перед числом можно поставить знак "+" или "-", обозначающий знак числа.

За начальным набором чисел может следовать необязательная экспонента, используется для обозначения научных обозначений. Он представлен буквой E (в верхнем или нижнем регистре), за которой следует необязательный + или -, за которым следует последовательность цифровых цифр. Число нормализовать не нужно.

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

32.4      -- 32.4
-535.     -- -535.0
.43       -- 0.43
4.5e+1    -- 45.0
78.34E-5  -- 0.0007834
7e2       -- 700.0
Текст или строка (Text или string)

Текстовое значение представлено строкой символов, заключенной в одинарные кавычки (''). Двойные кавычки ("") используются для заключения идентификаторов и не должны использоваться для заключения буквальных значений. Чтобы избежать одинарной кавычки внутри текстового литерала, используйте два символа одинарной кавычки подряд. Символ обратной косой черты (\), используется в C и многих других языках как escape-символ, не считается особенным в SQL и не может использоваться для экранирования кавычек в текстовых литералах. Текстовое значение нулевой длины - это не то же самое, что NULL:

'Jim has a dog.'       Jim has a dog.
'Jim''s dog is big.'   Jim's dog is big.
'C:\data\'             C:\data\
''                     (zero-length text value)
BLOB

Значение BLOB представлено как X (в верхнем или нижнем регистре), за которым следует текстовый литерал, состоящий из шестнадцатеричных символов (0–9, A – F, a – f). Для каждого полного байта требуются два шестнадцатеричных символа, поэтому количество символов должно быть четным. Длина BLOB (в байтах) будет числом шестнадцатеричных символов, разделенным на два. Как и текстовые значения, байтовые значения даются в порядке:

X'7c'
X'8A26E855'
x''

Имейте в виду, что это входные форматы, распознаваемые синтаксическим анализатором команд SQL. Они не обязательно являются выходным форматом, используемым для отображения значений. Формат отображения зависит от среды SQL, такой как утилита sqlite3. Чтобы вывести значения как допустимые литералы SQL, см. функцию SQL quote().

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

CURRENT_TIME
Текстовое значение в формате ЧЧ: ММ: СС.
CURRENT_DATE
Текстовое значение в формате ГГГГ-ММ-ДД.
CURRENT_TIMESTAMP
Текстовое значение в формате ГГГГ-ММ-ДД ЧЧ: ММ: СС.

Все время и даты указаны в формате UTC, а не в вашем часовом поясе.

Наконец, в любом месте, где SQLite примет буквальное выражение, он также примет параметр инструкции. Параметры инструкции - это заполнители, аналогичные внешним переменным. При использовании C API можно подготовить оператор, затем связать значения с параметрами и выполнить оператор. Оператор может быть сброшен, новые значения могут быть связаны, и оператор может быть выполнен снова. Параметры оператора позволяют один раз подготовить часто повторно используемые операторы (например, многие операторы INSERT) и использовать их снова и снова, просто привязывая новые значения к параметрам оператора. Этот процесс дает ряд преимуществ в плане производительности и безопасности, но он применим только для тех, кто использует программный интерфейс для SQLite.

SQLite поддерживает следующий синтаксис для параметров оператора:

?
Одиночный знак вопроса. SQLite автоматически присвоит индекс каждому параметру.
?numb
Один знак вопроса, за которым следует число. Номер станет индексом параметра. Один и тот же индекс может использоваться более одного раза.
:name
Один символ двоеточия, за которым следует имя. API предоставляет способ поиска индекса по имени. Одно и то же имя может использоваться более одного раза.
@name
Одиночный символ (@), за которым следует имя. API предоставляет способ поиска индекса по имени. Одно и то же имя может использоваться более одного раза. Это нестандартный вариант.
$name
Одинарный знак доллара, за которым следует имя. API предоставляет способ поиска индекса по имени. Одно и то же имя может использоваться более одного раза. Этот вариант нестандартен и поддерживает специальный синтаксис, предназначенный для использования с переменными Tcl.

Параметры инструкции можно использовать только для замены литералов. Их нельзя использовать для замены идентификаторов, таких как имена таблиц или столбцов. См. Раздел «Связанные параметры» на странице 133 для получения дополнительных сведений о том, как использовать параметры оператора с C API.

Логические представления

SQL и SQLite имеют большое количество логических операций. Многие операторы, такие как <= (проверка на меньшее или равное), выполняют сравнение типов или поиск между выражениями параметров и возвращают логическое значение, то есть истинное или ложное. Ряд команд SQL используют эти логические значения для управления тем, как команды применяются к базе данных. Например, Предложение WHERE команды SELECT в конечном итоге вычисляет логическое значение, чтобы определить, включена ли данная строка в набор результатов или нет.

Несмотря на то, что обычно используются логические значения, SQLite не имеет собственного логического типа данных (например, Boolean). Скорее, логические значения выражаются как целые числа. Ноль означает ложь, а единица (или любое другое ненулевое целочисленное значение) - истина.

Для других типов данных SQLite использует стандартные правила преобразования для преобразования текста или значения BLOB в целое число, прежде чем определять, является ли значение истинным или ложным. Это означает, что большинство нечисловых строк (например, abc) будут преобразованы в false.

Единственное исключение - NULL. После истины и ложи NULL считается третьим логическим состоянием. Это требует концепции трехзначной логики, или 3VL. Для целей трехзначной логики NULL считается неизвестным или неопределенным состоянием. Дополнительные сведения см. В разделе «Трехзначная логика» на странице 31. В общем, это означает, что, как только в выражение вводится NULL, он имеет тенденцию распространяться через выражение. Почти все унарные и бинарные операторы вернут NULL, если любое из выражений параметров равно NULL.

Унарные выражения

Унарные операторы - это самый простой тип оператора выражения. Они принимают выражение с одним (или унитарным) параметром и каким-либо образом модифицируют или изменяют это выражение. Во всех случаях, если выражение параметра равно NULL, оператор также вернет NULL.

SQLite поддерживает следующие операторы унарных выражений:

- Знак отрицания (Sign negation)
Унарный отрицательный знак меняет знак числового выражения и эквивалентен умножению на 1. Положительные выражения становятся отрицательными, а отрицательные - положительными. Любое выражение параметра, отличное от NULL, перед преобразованием будет преобразовано в числовой тип.
+ Знак плюс (Positive sign)

По логике, этот оператор не является операцией. Он не заставляет числа быть положительными (для этого используйте SQL-функцию abs()), он просто поддерживает текущий знак. Его можно использовать с любым типом данных, включая типы текста и BLOB, и он просто возвращает значение без преобразования.

Хотя этот оператор не изменяет значение выражения параметра, выражение результата по-прежнему считается «вычисленным» выражением. Применение этого оператора к идентификатору столбца отделит результирующее выражение от исходной таблицы. Это меняет способ обработки выражения оптимизатором запросов. Например, оптимизатор не будет пытаться использовать какие-либо индексы, связанные с исходным столбцом или столбцом вычисленных результатов.

~ Битовая инверсия (Bit inversion)
Инвертирует или инвертирует все биты выражения параметра. Любое выражение параметра, отличное от NULL, будет преобразовано в целое число перед инверсией битов.
NOT Логическая инверсия (Logic inversion)
Оператор NOT используется для инверсии значения любого логического выражения. Любое выражение, отличное от NULL, будет преобразовано в целочисленное значение. Все ненулевые значения вернут 0, а значение 0 вернет 1. Не путайте этот унарный оператор с необязательным NOT в некоторых бинарных операторах. Конечный результат тот же, но порядок синтаксиса немного отличается.

Наряду с выражением COLLATE эти операторы имеют наивысший приоритет.

Двоичные выражения

Бинарные операторы принимают два значения в качестве выражений параметров и комбинируют или сравнивают их каким-либо образом, что дает выходное значение. Этот раздел включает в себя все операторы с представлениями, не являющимися ключевыми словами, а также И и ИЛИ. За исключением AND и OR, все эти операторы будут давать результат NULL, если любое из выражений параметра равно NULL.

SQLite поддерживает следующие операторы двоичных выражений:

|| Конкатенация строк (String concatenation)
Оператор || используется для объединения текстовых значений. Этот оператор определен стандартом SQL. Хотя некоторые базы данных поддерживают нестандартный оператор + для объединения текстовых значений, SQLite этого не делает. Выражения параметров, отличные от NULL, сначала будут преобразованы в текстовые значения.
* Умножение (Multiplication)
Стандартное числовое умножение двух чисел. Выражения параметров, отличные от NULL, сначала будут преобразованы в числовые значения. Если оба выражения являются целыми числами, результат также будет целым числом.
/ Деление (Division)
Стандартное числовое деление двух чисел. Результатом будет левый оператор, деленный на правый. Выражения параметров, отличные от NULL, сначала будут преобразованы в числовые значения. Если оба выражения являются целыми числами, будет использоваться целочисленное деление, и результат также будет целым числом.
% Деление по модулю или остаток от деления (Modulo or remainder)
Стандартное числовое значение по модулю двух чисел. Значение выражения будет остатком от левого оператора, деленного на правый. Если выражение любого параметра является действительным числом, результатом будет действительное число. Результатом будет целое число от 0 до значения на единицу меньше, чем значение преобразованного правого выражения. Выражения параметров, отличные от NULL, сначала будут преобразованы в числовые значения.
+ Сложение (Addition)
Стандартное числовое сложение двух чисел. Если оба выражения параметра являются целыми числами, результат также будет целым числом. Выражения параметров, отличные от NULL, сначала будут преобразованы в числовые значения.
- Вычитание (Subtraction)
Стандартное числовое вычитание двух чисел. Если оба выражения параметра являются целыми числами, результат также будет целым числом. Выражения параметров, отличные от NULL, сначала будут преобразованы в числовые значения.
<< >> Битовые сдвиги (Bit shifts)
Двоичный битовый сдвиг. Левое выражение сдвигается вправо (>>) или влево (<<) на количество битов, указанное в правом операторе. Любые выражения параметров, отличные от NULL, сначала будут преобразованы в целые числа. Эти операторы должны быть знакомы программистам на C, но они являются нестандартными операторами в SQL.
& | Двоичное AND,

OR

(Binary AND,

OR

)
Двоичные операции AND и

OR

побитовые операции. Любое выражение параметра, отличное от NULL, сначала будет преобразовано в целые числа. В логических выражениях не следует использовать эти операторы, вместо них следует использовать И или ИЛИ. Как и битовые сдвиги, это нестандартные операторы.
<<= =>> Варианты больше, меньше (Greater-than, less-than variations)
Сравнивает выражения параметров и возвращает логическое значение 0, 1 или NULL, в зависимости от того, какое выражение больше, меньше или равно. Выражения параметров не обязательно должны быть числовыми, и они не будут преобразованы. Фактически, они даже не обязательно должны быть одного типа. Результаты могут зависеть от сопоставлений, связанных с выражениями параметров.
= == Равно (Equal)
Сравнивает операнды на равенство и возвращает логическое значение 0, 1 или NULL. Как и большинство логических операторов, равенство связано правилами трехзначной логики. В частности, NULL == NULL приведет к NULL, а не к истине (1). Конкретное определение равенства для текстовых значений зависит от сопоставлений, связанных с выражениями параметров. Обе формы (одинарный или двойной знак равенства) абсолютно одинаковы.
! = <> Не равно (Not equal)
Сравнивает выражения неравенства и возвращает логическое значение 0, 1 или NULL. Подобно равенству, неравенство также связано правилами трехзначной логики, поэтому NULL! = NULL равно NULL, а не false (0). Конкретное определение неравенства зависит от параметров сортировки, связанных с выражениями параметров. Обе формы одинаковы.
AND OR Логические AND, OR (Logical AND, OR)

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

Операторы И и ИЛИ являются одними из единственных операторов, которые могут возвращать целочисленное логическое значение, когда одно из выражений их параметров равно ПУСТО (NULL). См. «Трехзначная логика» для получения более подробной информации о том, как И и ИЛИ работают в трехзначной логике.

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

Вызов функций

Помимо операторов, SQLite поддерживает как встроенные, так и пользовательские вызовы функций. Есть две категории вызовов функций. Скалярные функции вызываются с определенным набором параметров и возвращают значение, как и функции практически на любом другом языке выражений. Скалярные функции могут использоваться практически в любом контексте в любом выражении SQLite. Примером скалярной функции является abs(), которая возвращает абсолютное значение числового параметра.

Существуют также агрегатные функции, которые используются для сворачивания или суммирования групп строк. Агрегатные функции могут использоваться только в выражениях, которые определяют набор результатов или предложение HAVING оператора SELECT. Агрегаты часто используются вместе с предложениями GROUP BY. По сути, агрегатная функция вызывается много раз с разными входными значениями, но возвращает только одно значение для каждого набора данных. Примером агрегатной функции является avg(), которая вычисляет среднее значение для последовательности числовых входных данных.

Синтаксис вызова функции выглядит следующим образом:

Как и во многих языках выражений, функцию можно вызвать, присвоив ей имя и предоставив список из нуля или более выражений параметров, разделенных запятыми, в скобках. В некоторых контекстах вместо списка параметров можно также использовать специальный синтаксис одного символа *. Как и в определении набора результатов оператора SELECT, это подразумевает значение «все».

Необязательное ключевое слово DISTINCT также может быть включено перед первым параметром. Это актуально только для агрегатных функций. Если он присутствует, он проверяет, что каждый набор параметров, переданных в агрегат, будет уникальным и отличным. Ключевое слово не действует при использовании со скалярными функциями.

Полный список всех встроенных функций, поддерживаемых SQLite, см. в приложении E.

Имена столбцов

Одним из наиболее распространенных типов выражений является имя столбца. Общий формат довольно прост, состоящий только из имени столбца. Если между разными таблицами существует какая-либо неоднозначность, вы можете добавить к имени столбца необязательное имя таблицы или имя базы данных и таблицы. Идентификаторы (имена баз данных, имена таблиц или имена столбцов), которые содержат нестандартные символы, могут быть заключены в двойные кавычки («») или квадратные скобки ([]), чтобы избежать их. Например, [имя таблицы]. [Имя столбца].

Выражения имени столбца всегда оцениваются в контексте определенного типа. Например, если вы формулируете выражение WHERE, которое является частью оператора SELECT, определенное в нем выражение будет оцениваться один раз для каждой возможной строки в наборе результатов. По мере обработки каждой строки значение столбца для этой строки будет помещено в выражение, и выражение будет оценено. Контекст определяет, какие ссылки на столбцы доступны для каждого конкретного выражения.

Помимо фактических столбцов таблицы, многие выражения в операторе SELECT могут также ссылаться на столбцы из набора результатов, ссылаясь на псевдоним, назначенный в предложении AS. Точно так же, если исходной таблице в предложении FROM назначается псевдоним таблицы, этот псевдоним должен использоваться в любой ссылке на таблицу. Использование псевдонимов таблиц особенно распространено при формулировании выражений условий соединения для самосоединений (таблица, присоединенная к самой себе) и в других ситуациях, когда вам нужно обратиться к конкретному экземпляру конкретной таблицы. Псевдоним таблицы также может быть назначен безымянному подзапросу.

Общие выражения

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

AND
Логическое И
См. также
См. «Двоичные выражения», AND
BETWEEN
Включение в диапазон
Синтаксис
Описание
Выражение BETWEEN проверяет, находится ли значение тестового выражения между минимальным выражением и максимальным выражением включительно. Выражение логически эквивалентно (test >= min AND test <= max), хотя тестовое выражение оценивается только один раз. Оператор BETWEEN вернет логическое значение 0, 1 или NULL в соответствии с правилами трехзначной логики.
См. также
IN, EXISTS
CASE
Условное выражение оценки
Синтаксис
Стандартное использование
CASE WHEN col IS NULL THEN 'null' WHEN NOT col THEN 'false' -- convert to logic value WHEN NOT NOT col THEN 'true' -- convert to logic value ELSE 'unknown' END
Описание

Выражение CASE похоже на оператор C switch или на серию операторов if-then. Выражение состоит из необязательного тестового выражения, за которым следует одно или несколько предложений WHEN...THEN. Оператор завершается необязательным предложением ELSE и обязательным ключевым словом END.

Каждое предложение WHEN...THEN оценивается по порядку. Первое выражение WHEN, эквивалентное проверочному выражению, приведет к тому, что все выражение CASE примет значение соответствующего возвращаемого выражения. Если эквивалентного выражения WHEN не найдено, используется значение выражения по умолчанию. Если предложение ELSE не указано, предполагается, что выражение по умолчанию имеет значение NULL.

Если тестовое выражение не указано, будет использоваться первое выражение WHEN, которое имеет значение true.

CAST
Принудительное преобразование типа
Синтаксис
Описание

Синтаксис CAST Приложение D: Справочник по выражениям SQLite SQL | 351 Описание Оператор CAST принудительно применяет выражение приведения к типу данных, описываемому именем типа. Фактическое представление (целое, вещественное, текстовое, BLOB) будет производным от имени типа путем поиска (без учета регистра) этих конкретных подстрок. Первая совпадающая строка будет определять тип. Если совпадений не найдено, выражение приведения будет преобразовано в наиболее подходящее числовое значение.

Подстрока Тип данных
INT Integer
CHAR Text
CLOB Text
TEXT
BLOB BLOB
REAL Float
FLOA Float
DOUB Float

Обратите внимание, что строка типа FLOATING POINT будет интерпретироваться как целое, а не как действительное, поскольку первая перечисленная подстрока будет соответствовать INT в конце название. Эта первая запись имеет приоритет над последующими подстроками, что привело к неожиданному приведению. Лучше всего использовать несколько стандартных типов баз данных, чтобы обеспечить правильное преобразование.

После определения конкретного типа данных фактическое преобразование выполняется с использованием стандартных правил. См. подробности в Таблице 7-1.

См. также
CREATE TABLE [SQL Cmd, Ap C]
COLLATE
Связывание определенного сопоставления с выражением
Синтаксис
Описание

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

Если два текстовых выражения участвуют в проверке равенства или порядка, сопоставление определяется по следующим правилам:

  1. Если левое (первое) выражение имеет явное сопоставление, оно используется.
  2. Если правое (второе) выражение имеет явное сопоставление, оно используется.
  3. Если левое (первое) выражение является прямой ссылкой на столбец с сопоставлением, используется сопоставление столбцов.
  4. Если правое (второе) выражение является прямой ссылкой на столбец с сопоставлением, используется сопоставление столбцов.
  5. Используется BINARY сопоставление по умолчанию.

Например:

'abc'                == 'ABC'                 => 0 (false)
'abc' COLLATE NOCASE == 'ABC'                 => 1 (true)
'abc'                == 'ABC' COLLATE NOCASE  => 1 (true)
'abc' COLLATE NOCASE == 'ABC' COLLATE BINARY  => 1 (true)
'abc' COLLATE BINARY == 'ABC' COLLATE NOCASE  => 0 (false)

Для получения дополнительной информации о сопоставлениях см. «Функции сопоставления».

См. также
CREATE TABLE, sqlite3_create_collation() [C API, Ap G]
EXISTS
Проверить, существует ли строка
Синтаксис
Описание
Оператор EXISTS возвращает логическое значение (целое число 0 или 1) в зависимости от того, возвращает ли инструкция SELECT какие-либо строки. Количество столбцов и их значения не имеют значения. Пока оператор SELECT возвращает хотя бы одну строку, состоящую хотя бы из одного столбца, оператор будет возвращать истину, даже если значение столбца равно NULL.
См. также
IN, BETWEEN
GLOB
Сопоставление текстовых значений с использованием шаблонов
Синтаксис
Описание

Оператор GLOB используется для сопоставления текстовых значений с шаблоном. Если выражение поиска можно сопоставить с выражением шаблона, оператор GLOB вернет истину (1). Все выражения параметров, отличные от NULL, будут преобразованы в текстовые значения. GLOB чувствителен к регистру, поэтому 'GLOB' A 'ложно.

Синтаксис выражения шаблона основан на общих подстановочных знаках командной строки, также известных как подстановка файлов. Символ * в шаблоне соответствует нулю или большему количеству символов в поисковом выражении. ? символ будет соответствовать точно одному из любого символа, тогда как подстановочный знак списка ([]) будет соответствовать ровно одному символу из его набора символов. Все остальные символы в шаблоне принимаются как литералы. Шаблоны GLOB не имеют escape-символа.

Например, шаблон '* .xml' будет соответствовать всем поисковым выражениям, которые заканчиваются четырьмя символами .xml, а шаблон '??' будет соответствовать любому текстовому значению длиной ровно два символа. Шаблон '[abc]' будет соответствовать любому одиночному символу a, b или c.

Подстановочный знак списка позволяет указать диапазон символов. Например, [az] будет соответствовать любому одиночному буквенному символу в нижнем регистре, а [a-zA-Z0-9] будет соответствовать любому одиночному буквенно-цифровому символу в верхнем или нижнем регистре.

Вы можете сопоставить любой символ, кроме указанных в списке, поместив ^ в начале списка. Например, шаблон [^ 0-9] будет соответствовать любому одиночному символу, кроме числового символа.

Чтобы сопоставить буквальные символы *,? Или [, поместите их в подстановочный знак списка; например [*] или [[]. Чтобы найти ^ внутри списка, не ставьте его первым. Чтобы соответствовать (или не совпадать) литералу] внутри списка, сделайте его первым символом после открывающего [или [^. Чтобы соответствовать литералу внутри диапазона, сделайте его последним символом в списке.

Оператор GLOB реализуется функцией SQL glob(). Таким образом, его поведение можно изменить, зарегистрировав новую функцию glob().

См. также
glob() [SQL Func, Ap E], LIKE, MATCH, REGEXP
IN
Проверить, задано ли значение
Синтаксис
Стандартное использование
col IN ( test1, test2, test3 ) col IN ( SELECT c FROM t ) col IN temp.in_test
Описание

Оператор IN проверяет, равно ли тестовое выражение (или не равно) любое из значений, найденных в правой части выражения. Это трехзначный логический оператор, возвращающий 0, 1 или NULL. NULL будет возвращен, если NULL будет найден с левой стороны или если NULL появится где-нибудь в несопоставленной тестовой группе.

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

Последний способ определить тестовую группу - указать имя таблицы. Таблица должна состоять только из одного столбца. Вы не можете предоставить таблицу и столбец, это должна быть таблица с одним столбцом. Этот последний стиль чаще всего используется с временными таблицами. Если вам нужно выполнить один и тот же тест несколько раз, может быть более эффективным создать временную таблицу (например, с CREATE TEMP TABLE...AS SELECT) и использовать ее снова и снова, а не использовать подзапрос как часть выражения IN.

См. также
BETWEEN, EXISTS
IS
Проверка равенства, включая NULL
Синтаксис
Описание

Операторы IS и IS NOT очень похожи на операторы равенства (= или ==) и не равенства (! = Или <>). Основное отличие заключается в том, как обрабатываются NULL. Стандартные тесты на равенство подчиняются правилам трехзначной логики: что приведет к NULL, если выражение любого параметра равно NULL.

Оператор IS рассматривает NULL как «просто другое значение» и всегда возвращает 0 или 1. Например, выражение NULL IS NULL считается истинным (1), а выражение NULL IS 6 считается ложным (0).

См. также
ISNULL
ISNULL
Проверка синтаксиса NULL
Синтаксис
Описание
IS NULL и другие операторы, показанные здесь, используются для проверки выражений NULL или ненулевых выражений. Это синтаксические вариации IS NULL и IS NOT NULL, которые представляют собой правильно сформированные выражения IS.
См. также
IS
LIKE
Установить ширину отображения для каждого столбца
Синтаксис
Описание

Оператор LIKE используется для сопоставления текстовых значений с шаблоном. Если выражение поиска можно сопоставить с выражением шаблона, оператор LIKE вернет true (1). Все выражения параметров, отличные от NULL, будут преобразованы в текстовые значения. LIKE - это стандартизированный оператор SQL.

По умолчанию LIKE не чувствителен к регистру, поэтому 'a' LIKE 'A' истинно. Однако эта нечувствительность к регистру применяется только к стандартным 7-битным символам ASCII. По умолчанию LIKE не поддерживает Unicode. См. «Расширение интернационализации ICU» на стр. 167 для получения дополнительной информации. Чувствительность к регистру LIKE можно настроить с помощью PRAGMA case_sensitive_like.

Синтаксис шаблона LIKE поддерживает два подстановочных знака. Символ% соответствует нулю или более символам, а символ _ соответствует ровно одному. Все остальные символы в шаблоне будут приняты как литералы. Буквальный% или _ может быть сопоставлен, продолжая его с символом, определенным в необязательном escape-выражении. Первый символ этого выражения будет использоваться как escape-символ. Не существует escape-символа по умолчанию (в частности, символ \ в стиле C не используется по умолчанию).

Например, шаблон «% .xml» будет соответствовать всем выражениям поиска, которые заканчиваются четырьмя символами .xml, а шаблон «__» будет соответствовать любому текстовому значению длиной ровно два символа. Невозможно сопоставить буквальный% или _ без определения escape-символа.

Оператор LIKE реализуется функцией SQL like(). Таким образом, его поведение можно изменить, зарегистрировав новую функцию like().

См. также
like() [SQL Func, Ap E], case_sensitive_like [PRAGMA, Ap F], GLOB, MATCH, REGEXP
MATCH
Сопоставление текстовых значений с использованием шаблонов
Синтаксис
Описание
Цель оператора MATCH - поддерживать определяемое пользователем сопоставление с образцом алгоритм. Однако реализации по умолчанию не существует, поэтому любая попытка использовать оператор MATCH без предварительного определения функции SQL match() приведет к ошибке.
См. также
match() [SQL Func, Ap E], LIKE, GLOB, REGEXP
NOTNULL
Тест на ненулевое значение
См.:
ISNULL
OR
Логическое ИЛИ
См.:
«Двоичные выражения», OR
RAISE
Укажите состояние ошибки
Синтаксис
Описание
Выражение RAISE не является выражением в традиционном смысле. Вместо того, чтобы создавать значение, он предоставляет средства для возникновения исключения ошибки. Выражение RAISE можно использовать только в теле триггера. Обычно он используется для обозначения нарушения ограничения или аналогичной проблемы. Обычно выражение RAISE используется вместе с выражением CASE, или какое-либо другое выражение, которое выборочно выполняет подвыражения в зависимости от логики оператора SQL.
См. также
CREATE TRIGGER [SQL Cmd, Ap C]
REGEXP
Сопоставление текстовых значений с использованием шаблонов
Синтаксис
Стандартное использование
ALTER TABLE database_name.table_name RENAME TO new_table_name; ALTER TABLE database_name.table_name ADD COLUMN column_def...;
Описание
Цель оператора REGEXP - поддерживать определяемый пользователем алгоритм сопоставления текста с регулярным выражением. Однако реализации по умолчанию не существует, поэтому любая попытка использовать оператор REGEXP без предварительного определения функции SQL regexp() приведет к ошибке. Обычно это делается с помощью сторонней библиотеки регулярных выражений.
См. также
regex() [SQL Func, Ap E], LIKE, GLOB, MATCH
SELECT
Извлечь значение выражения из базы данных
Синтаксис
Описание

Выражение SELECT - это подзапрос в круглых скобках. Оператор SELECT должен возвращать только один столбец. Значение выражения становится значением первой строки (и только первой строки). Если строки не возвращаются, выражение равно ПУСТО (NULL).

SQLite поддерживает несколько разных типов подзапросов, поэтому вам нужно быть осторожным с тем, какой стиль вы используете. Некоторые выражения, такие как IN, допускают прямой подзапрос как часть своего синтаксиса. Точно так же несколько команд SQL, например CREATE TABLE, поддерживают синтаксис подзапроса. В этих случаях подзапрос возвращает набор данных, а не одно значение. Когда вы используете эту форму подзапроса как отдельное выражение, она вернет только одно значение.

Иногда это может иметь неожиданные результаты. Например, рассмотрим эти два выражения:

col IN (   SELECT c FROM t   );
col IN ( ( SELECT c FROM t ) );

Единственное различие между этими двумя выражениями - это дополнительный набор скобок вокруг подзапроса во второй строке. В первом случае выражение IN рассматривает подзапрос как прямую часть выражения IN. Это позволяет IN проверять столбец на соответствие каждой строке, возвращаемой подзапросом.

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

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

См. также
IN, EXISTS

Приложение E
SQLite Справочник по функциям SQL

SQLite включает ряд встроенных функций SQL. Многие из этих функций основаны на стандарте SQL, тогда как другие специфичны для среды SQLite.

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

Агрегатные функции можно использовать только в выражениях заголовка SELECT и предложениях HAVING. Агрегатные функции объединяют значения столбцов из нескольких строк для вычисления одного возвращаемого значения. Часто агрегатные функции используются вместе с предложением GROUP BY.

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

Скалярные функции

abs()
Вычисление числового абсолютного значения
Стандартное использование
abs( expression )
Описание
Функция abs() возвращает абсолютное значение выражения. Если выражение является целым числом, возвращается целочисленное абсолютное значение. Если выражение является числом с плавающей запятой, возвращается абсолютное значение с плавающей запятой. Если number равно NULL, возвращается NULL. Все остальные типы данных сначала преобразуются в значения с плавающей запятой, затем возвращается абсолютное значение преобразованного числа с плавающей запятой.
changes()
Получить количество строк, измененных последней командой SQL
Стандартное использование
changes( )
Описание

Функция changes() возвращает целое число, которое указывает количество строк базы данных, которые были изменены последними выполненными операциями INSERT, UPDATE или DELETE. команда, выполняемая этим подключением к базе данных.

Эта функция SQL является оболочкой для функции sqlite3_changes() языка Си и имеет те же ограничения и условия.

См. также
total_changes(), last_insert_rowid(), sqlite3_changes() [C API, Ap G]
coalesce()
Возвращает первый аргумент, отличный от NULL
Стандартное использование
coalesce( param1, param2, ... )
Описание
Функция coalesce() принимает два или дополнительные параметры и возвращает первый параметр, отличный от NULL. Если все значения параметров равны NULL, возвращается NULL.
См. также
ifnull(), nullif()
date()
Декодировать строку времени в дату
Стандартное использование
date( timestring, modifier, ... )
Описание
Функция date() принимает значение временной строки плюс ноль или более значений модификатора и возвращает текстовое значение в формате ГГГГ-ММ-ДД. Это эквивалентно вызову strftime ('% Y-% m-% d', timestring, ...).
См. также
strftime(), datetime(), time()
datetime()
Преобразование строки времени в дату и время
Стандартное использование
datetime( timestring, modifier, ... )
Описание
Функция datetime() принимает значение строки времени плюс ноль или более значений модификатора и возвращает текстовое значение в формате ГГГГ-ММ-ДД ЧЧ: ММ: СС. Это эквивалентно вызову strftime ('% Y-% m-% d% H:% M:% S', timestring, ...).
См. также
strftime(), date(), time()
glob()
Реализуйте оператор GLOB
Стандартное использование
glob( pattern, string )
Описание

Функция glob() реализует алгоритм сопоставления, используемый шаблоном GLOB строки выражения SQL, и обычно не вызывается напрямую из пользовательских выражений SQL. Эта функция существует для того, чтобы ее можно было переопределить с помощью определяемой пользователем функции SQL, предоставляя определяемый пользователем оператор GLOB.

Обратите внимание, что порядок параметров отличается в выражении GLOB и функции glob().

См. также
GLOB [SQL Expr, Ap D], like(), match(), regex()
ifnull()
Возвращает первый аргумент, отличный от NULL
Стандартное использование
ifnull( param1, param2 )
Описание
Функция ifnull(), по сути, является фиксированной двумя -параметрическая версия coalesce(). Если param1 не равен NULL, он возвращается. Если param1 равен NULL, возвращается param2.
См. также
coalesce(), nullif()
hex()
Дамп BLOB как шестнадцатеричного
Стандартное использование
hex( data )
Описание

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

Будьте осторожны при использовании hex() с большими BLOB-объектами. Текстовое представление UTF-8 вдвое больше исходного значения BLOB, а представление UTF-16 в четыре раза больше.

См. также
quote()
julianday()
Возвращает число юлианских дней
Стандартное использование
julianday( timestring, modifier, ... )
Описание
Функция julianday() принимает значение временной строки плюс ноль или более значений модификатора и возвращает значение с плавающей запятой, представляющее количество дней с полудня по гринвичскому времени, 24 ноября 4714 г. до н.э. по пролептическому григорианскому календарю. Это эквивалентно вызову strftime ('% J', timestring, ...), за исключением того, что julianday() возвращает значение с плавающей запятой, а strftime ('% J', ...) возвращает текстовое представление эквивалентное значение с плавающей запятой.
См. также
strftime()
last_insert_rowid()
Возвращает ROWID последней вставленной строки
Стандартное использование
last_insert_rowid( )
Описание

Функция last_insert_rowid() возвращает целочисленное значение ROWID (или псевдоним ROWID) последней успешно завершенной строки INSERT. Цель этой функции - обнаружить автоматически сгенерированный ROWID, часто с целью вставки внешнего ключа, который ссылается на этот ROWID. Для целей этой функции INSERT считается успешно завершенным, даже если это происходит внутри незафиксированной транзакции.

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

Эта функция SQL является оболочкой для функции sqlite3_last_insert_rowid() языка C и имеет все те же ограничения и условия.

См. также
changes(), total_changes(), sqlite3_last_insert_rowid() [C API, Ap G]
length()
Возвращает количество символов в строке
Стандартное использование
length( data )
Описание
Функция length() возвращает целочисленное значение, указывающее длину ее параметра. Если данные представляют собой текстовое значение, возвращается количество символов независимо от кодировки. Если данные представляют собой значение BLOB, возвращается количество байтов в BLOB. Если данные NULL, возвращается NULL. Числовые типы сначала преобразуются в текстовое значение.
like()
Реализация оператора LIKE
Стандартное использование
like( pattern, string )
Описание

Функция like() реализует алгоритм сопоставления, используемый строкой выражения SQL LIKE pattern, и обычно не вызывается непосредственно из пользовательских выражений SQL. Эта функция существует для того, чтобы ее можно было переопределить с помощью определяемой пользователем функции SQL, предоставляя определяемый пользователем оператор LIKE.

Обратите внимание, что порядок параметров отличается в выражении LIKE и функции like().

См. также
LIKE [SQL Expr, Ap D], glob(), match(), regex()
load_extension()
Загрузить динамически связанное расширение SQLite
Стандартное использование
load_extension( extension ) load_extension( extension, entry_point )
Описание

Функция load_extension() пытается загрузить и динамически связать расширение файла как расширение SQLite. Функция с именем entry_point вызывается для инициализации расширения. Если точка_входа не указана, предполагается, что это sqlite3_extension_init. Оба параметра должны быть текстовыми значениями.

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

См. также
sqlite3_load_extension() [C API, Ap G]
lower()
Преобразование всех символов ASCII в нижний регистр
Стандартное использование
lower( text )
Описание

Функция lower() возвращает копию текста со всеми символами букв, преобразованными в нижний регистр. Встроенная реализация этой функции работает только с символами ASCII (теми, которые имеют значение меньше 128).

Расширение ICU обеспечивает реализацию функции lower() с поддержкой Unicode.

См. также
upper()
ltrim()
Обрезка символов спереди (слева) строки
Стандартное использование
ltrim( text ) ltrim( text, extra )
Описание

Функция ltrim() возвращает копию текста, лишенного любого префикса, который состоит исключительно из персонажей из доп. Символы из extra, содержащиеся в теле текста, остаются нетронутыми.

Если extra не предоставляется, предполагается, что он состоит из символа пробела ASCII (0x20). По умолчанию символы табуляции и другие пробелы не обрезаются.

См. также
rtrim(), trim()
match()
Реализация оператора MATCH
Стандартное использование
match( pattern, string )
Описание

Функция match() реализует алгоритм сопоставления, используемый строкой выражения SQL Шаблон MATCH, и обычно не вызывается напрямую предоставленные пользователем выражения SQL. Реализации по умолчанию для этой функции не существует. Чтобы использовать оператор MATCH, необходимо зарегистрировать функцию, определяемую приложением.

Обратите внимание, что порядок параметров отличается в выражении MATCH и функции match().

См. также
MATCH [SQL Expr, Ap D], like(), glob(), regex()
max()
Возвращает аргумент с наибольшим значением
Стандартное использование
max( param1, param2, ... )
Описание

При наличии двух или более параметров функция max() возвращает параметр с наибольшим значением. Если какой-либо параметр имеет значение NULL, будет возвращено значение NULL. В противном случае считается, что значения BLOB имеют наибольшее значение, за которыми следуют текстовые значения. За ними следуют числовые типы (смешанные целочисленные значения и значения с плавающей запятой), отсортированные вместе в их естественном порядке.

Если вы хотите, чтобы при сравнении использовалось определенное сопоставление, используйте выражение COLLATE, чтобы присоединить явное сопоставление к входным значениям. Например, max (param1 COLLATE NOCASE, param2).

Также существует агрегированная версия max(), которая принимает единственный параметр.

См. также
min() [Agg SQL Func], max() [Agg SQL Func], COLLATE [SQL Expr, Ap D]
min()
Возвращает аргумент с наименьшим значением
Стандартное использование
min( param1, param2, ... )
Описание

При наличии двух или более параметров функция min() вернет параметр с наименьшим значением. Если какой-либо параметр имеет значение NULL, будет возвращено значение NULL. В противном случае числовые типы (смешанные целые числа и числа с плавающей запятой) считаются наименьшими и отсортированы вместе в их естественном порядке. За ними следуют текстовые значения, за которыми следуют значения BLOB.

Если вы хотите, чтобы при сравнении использовалось определенное сопоставление, используйте выражение COLLATE, чтобы присоединить явное сопоставление к входным значениям. Например, min (param1 COLLATE NOCASE, param2).

Существует также агрегированная версия min(), которая принимает единственный параметр.

См. также
max(), min(), COLLATE [SQL Expr, Ap D]
nullif()
Возвращает NULL, если параметры равны
Стандартное использование
nullif( param1, param2 )
Описание
Функция nullif() возвращает NULL, если параметры равны. Если два параметра не равны, возвращается первый параметр. Если оба параметра равны NULL, возвращается NULL.
См. также
ifnull(), coalesce()
quote()
Возвращает литеральное представление SQL значения
Стандартное использование
quote( value )
Описание

Функция quote() возвращает текстовое значение, которое представляет литеральное представление SQL значения. Числа возвращаются в их строковом представлении с достаточным количеством цифр для сохранения точности. Текстовые значения возвращаются в одинарных кавычках с правильным экранированием любых внутренних одинарных кавычек. BLOB возвращаются как шестнадцатеричные литералы. NULL возвращаются как строка NULL.

В SQLite v3.6.23.1 и ранее эта функция использовалась VACUUM для внутренних целей. Настоятельно рекомендуется не отменять реализацию по умолчанию.

См. также
hex(), VACUUM [SQL Cmd, Ap C]
random()
Возвращает случайное 64-битное целое число со знаком
Стандартное использование
random( )
Описание
Функция random() возвращает псевдослучайное, 64 -битовое целое число со знаком. Это дает число в диапазоне от -9,223,372,036,854,775,808 до +9,223,372,036,854,775,807. Эти значения генерируются автоматическим внутренним генератором псевдослучайных чисел для обеспечения качества.
См. также
randomblob()
randomblob()
Возвращает BLOB, состоящий из случайных данных
Стандартное использование
randomblob( size )
Описание
Функция randomblob() возвращает значение BLOB, состоящее из байтов размера. Все байты имеют псевдослучайные значения. Эти значения генерируются автоматическим внутренним генератором псевдослучайных чисел для обеспечения качества.
См. также
random(), zeroblob()
regex()
Реализация оператора REGEX
Стандартное использование
regex( pattern, string )
Описание

Функция regex() реализует алгоритм сопоставления, используемый шаблоном строки выражения SQL REGEX, и обычно не вызывается напрямую предоставленные пользователем выражения SQL. Реализации по умолчанию для этой функции не существует. Чтобы использовать оператор REGEX, должна быть предоставлена функция, определяемая приложением.

Обратите внимание, что порядок параметров отличается в выражении REGEX и функции regex().

См. также
REGEXP [SQL Expr, Ap D], like(), glob(), match()
replace()
Найти и заменить подстроки
Стандартное использование
replace( value, search, replacement )
Описание

Функция replace() возвращает копию значения с заменой каждого экземпляра подстроки поиска на замену. Если какой-либо из параметров равен NULL, возвращается NULL. Если поиск представляет собой пустую строку, значение будет возвращено без изменений. В противном случае все параметры будут преобразованы в текстовые значения.

Функция replace() также работает со значениями BLOB, хотя результат возвращается в виде текстового значения и должен быть преобразован обратно в BLOB.

См. также
substr()
round()
Округление числовых значений
Стандартное использование
round( value ) round( value, precision )
Описание

Функция round() возвращает значение с плавающей запятой, которое представляет значение, округленное до десятичных знаков точности после запятой. точка. Предполагается, что параметр value является значением с плавающей запятой, а параметр precision - положительным целым числом. Значения округляются до ближайшего представления, а не до нуля.

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

rtrim()
Обрезать символы с конца (справа) строки
Стандартное использование
rtrim( text, extra ) rtrim( text )
Описание

Функция rtrim() возвращает копию текста, лишенного любого суффикса, состоящего исключительно из символов из extra. Символы из extra, содержащиеся в теле текста, остаются нетронутыми.

Если extra не указан, предполагается, что он состоит из символа пробела ASCII (0x20). По умолчанию символы табуляции и другие пробелы не обрезаются.

См. также
ltrim(), trim()
sqlite_compileoption_get()
Список доступа параметров времени компиляции, используемых для построения библиотеки SQLite
Стандартное использование
sqlite_compileoption_get( index )
Описание

Функция sqlite_compileoption_get() возвращает текстовое значение, которое описывает одну из директив сборки, используемых при построении этого экземпляра SQLite библиотека. Перебирая значения индекса (начиная с нуля), вы можете просмотреть весь список директив. Возвращаемые значения будут в формате имя_директивы, если директива представляет собой простой флаг включения / выключения, или имя_ директивы = значение, если директива используется для установки значения. Префикс SQLITE_ не будет включен в возвращаемое значение. Если индекс отрицательный или вне допустимого диапазона, sqlite_compileoption_get() вернет NULL.

Эти директивы, используемые для установки значений по умолчанию и максимальных ограничений, не будут сообщаться. Если с директивой связано значение, формат будет следующим: имя = значение.

Эта функция SQL является оболочкой для функции sqlite3_compileoption_get() языка C и имеет те же ограничения и условия.

См. также
sqlite_compileoption_used(), sqlite3_compileoption_get() [Ap G], sqlite3_limit() [Ap G]
sqlite_compileoption_used()
Посмотрите, использовалась ли опция времени компиляции для построения библиотеки SQLite
Стандартное использование
sqlite_compileoption_used( option_name )
Описание

Учитывая имя директивы построения в качестве текстового значения, функция sqlite_compile option_used() возвращает 1, если директива использовалась при построении этого экземпляра библиотеки SQLite. Эта функция только укажет, использовалась ли директива, но не укажет, какое (если есть) значение было установлено. Если данная директива не использовалась или не была распознана иным образом, эта функция вернет 0. Префикс SQLITE_ в имени директивы необязателен.

Эти директивы, используемые для установки значений по умолчанию и максимальных ограничений, не будут сообщаться. Если с директивой связано значение, вы можете проверить конкретное значение, выполнив поиск по полному формату name = value. Если и имя, и значение совпадают, возвращается 1.

Эта функция SQL является оболочкой для функции sqlite3_compileoption_used() языка C и имеет те же ограничения и условия.

См. также
sqlite_compileoption_get(), sqlite3_compileoption_used() [C API, Ap G]
sqlite_source_id()
Возвращает значение идентификации источника текущей библиотеки SQLite
Стандартное использование
sqlite_source_id( )
Описание

Функция sqlite_source_id() возвращает текстовое значение, состоящее из идентификатора возврата исходного кода, используемого для создания библиотеки SQLite. Идентификатор состоит из даты, отметки времени и хэша SHA1 источника из исходного репозитория.

Эта функция SQL является оболочкой для функции sqlite3_sourceid() языка C и имеет те же ограничения и условия.

См. также
sqlite_version(), sqlite3_sourceid() [C API, Ap G]
sqlite_version()
Возвращает строку версии текущей библиотеки SQLite
Стандартное использование
sqlite_version( )
Описание

Функция sqlite_version() возвращает текстовое значение, состоящее из номера версии Библиотека SQLite. Типичная строка - это что-то вроде «3.7.2».

Эта функция SQL является оболочкой для функции sqlite3_libversion() языка C и имеет те же ограничения и условия.

См. также
sqlite_source_id(), sqlite3_libversion() [C API, Ap G]
strftime()
Декодирование строки времени в любой формат
Стандартное использование
strftime( format, timestring, modifier1, modifier2, ... )
Описание

Функция strftime() возвращает отформатированную строку, беря строку времени (с модификаторами) и форматируя ее в соответствии с форматом, спецификацией формата стиля printf(). Если какой-либо из параметров имеет неправильный или недопустимый формат, возвращается NULL.

Строка формата может содержать любой из следующих маркеров:

  • % d - день месяца (DD), 01-31
  • % f - секунды с дробной частью (SS.sss), 00-59 плюс десятичная часть
  • % H - час (ЧЧ), 00-23
  • % j - день года (NNN), 001-366
  • % J - номер дня по юлианскому календарю (DDDDDDD.ddddddd)
  • % m - месяц (MM), 01-12
  • % M - минута (MM) , 00-59
  • % s - секунды с 01.01.1970 (эпоха Unix)
  • % S - секунды (SS), 00-59
  • % w - день недели (N), начиная с воскресенья как 0
  • % W - неделя года (WW), 00-53
  • % Y - год (YYYY)
  • %% - буквальный%

Значение временной строки может быть в любом из следующих форматов:

  • YYYY-MM-DD
  • YYYY-MM- ДД ЧЧ: ММ
  • ГГГГ-ММ-ДД ЧЧ: ММ: СС
  • ГГГГ-ММ-ДД ЧЧ: ММ: СС.ссс
  • ГГГГ-ММ-ДДТЧЧ: ММ
  • ГГГГ-ММ-ДДТЧЧ: ММ: СС
  • ГГГГ-ММ -DDTHH: MM: SS.sss
  • ЧЧ: MM
  • ЧЧ: MM: SS
  • ЧЧ: MM: SS.sss
  • сейчас
  • DDDDDDD
  • DDDDDDD.ddddddd

Все значения часов указаны в 24-часовом формате. Любое значение, которое не указано, будет подразумеваться. Любые подразумеваемые часы, минуты, секунды или субсекунды равны нулю. Любой подразумеваемый месяц или день месяца - 1. Любой подразумеваемый год - 2000. Полные форматы даты и времени допускают использование пробела или буквального символа T в верхнем регистре между датой и временем. Последние два значения (одно большое целое число или число с плавающей запятой) предоставляют дату (или дату и время), выраженную в юлианских днях.

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

Модификаторы могут быть в следующих форматах:

  • [+ -] NNN день [с]
  • [+ -] NNN час [с]
  • [+ -] NNN минута [с]
  • [+ -] NNN секунда [с]
  • [ + -] NNN.nnn секунда [с]
  • [+ -] NNN месяц [с]
  • [+ -] NNN год [с]
  • начало месяца
  • начало года
  • начало дня
  • день недели N
  • unixepoch
  • местное время
  • utc

Первый набор модификаторов добавляет или вычитает заданную единицу времени из исходного значения временной строки. Например, дата звонка ('2001-01-01', '+2 дня') вернет '2001-01-03'. Манипуляции выполняются на очень буквальном уровне, а даты нормализуются до допустимых значений после применения каждого модификатора. Например, date ('2001-01-01', '-2 дня') возвращает "2000-12-30".

Процесс нормализации может привести к неожиданным результатам. Например, рассмотрите дату ('2001-01-31', '+1 месяц'). Первоначально вычисляется дата "2001-02-31" или 31 февраля. Поскольку в феврале никогда не бывает 31 дня, эта дата нормирована на март. В случае 2001 года (вне високосного года) окончательный результат - «2001-03-03». Также важно, что нормализация выполняется после применения каждого модификатора. Например, дата звонка ('2001-01-31', '+1 месяц', '-1 месяц' ) приведет к '2001-02-03'.

Модификаторы start of ... устанавливают для всех единиц времени, которые меньше указанной единицы, их минимальное значение. Например, datetime ('2001-02-28 12:30:59', 'начало месяца') приведет к '2001-02-01 00:00:00', установив все меньше месяца (день, час , минута, секунда) до минимального значения.

Модификатор дня недели сдвигает текущую дату вперед по времени от нуля до шести дней, так что день будет приходиться на N-й день недели (воскресенье = 0).

Модификатор unixepoch работает только как начальный модификатор и только тогда, когда дата задана как одно числовое значение. Этот модификатор заставляет интерпретировать дату как счетчик эпох Unix, а не как традиционный юлианский день.

Функции даты и времени SQLite не отслеживают данные часового пояса. Если не указано иное, все даты предполагаются в формате UTC. Например, временная строка «сейчас» будет выдавать значения даты и времени в формате UTC. Чтобы преобразовать метку времени UTC в местное время, можно применить модификатор местного времени. И наоборот, если известно, что строка времени относится к местному часовому поясу, можно использовать модификатор utc для преобразования метки времени в UTC.

Все функции даты и времени предназначены для работы с датами от 0000-01-01 00:00:00 до 9999-12-31 23:59:59 (числа юлианских дней от 1721059,5 до 5373484,5). Любое использование значений за пределами этого диапазона может привести к неопределенному поведению. Значения эпохи Unix действительны только до даты / времени 5352-11-01 10:52:47.

См. также
date(), time(), datetime(), julianday()
substr()
Извлечь подстроку
Стандартное использование
substr( string, index, count ) substr( string, index )
Описание

Функция substr() извлекает и возвращает подстроку из строки. Положение подстроки определяется индексом, а ее длина - счетчиком.

Если какой-либо параметр равен NULL, возвращается NULL. В противном случае, если строка не является BLOB, она будет считаться текстовым значением. Параметры index и count будут интерпретироваться как целые числа. Если число не указано, оно будет эффективно установлено на бесконечно большое положительное значение.

Если index положительный, он используется для индексации символов с начала строки. Первый символ имеет индекс 1. Если индекс отрицательный, он используется для индексации символов с конца строки. В этом случае последний символ имеет индекс -1. Индекс 0 приведет к неопределенному поведению.

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

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

См. также
replace()
time()
Декодирование строки времени в время суток
Стандартное использование
time( timestring, modifier, ... )
Описание
Функция time() принимает значение строки времени плюс ноль или более значений модификатора и возвращает текстовое значение в формате «ЧЧ: ММ: СС». Это эквивалентно вызову strftime ('% H:% M:% S', строка времени, ...).
См. также
strftime(), datetime(), date()
total_changes()
Возвращает общее количество измененных строк
Стандартное использование
total_changes( )
Описание

Функция total_changes() возвращает целое число, которое указывает количество строк базы данных, которые были изменены любым завершите команду INSERT, UPDATE или DELETE, выполняемую этим подключением к базе данных с момента его открытия.

Эта функция SQL является оболочкой для функции sqlite3_total_changes() языка C и имеет те же ограничения и условия.

См. также
changes(), last_insert_rowid(), sqlite3_total_changes() [C API, Ap G]
trim()
Обрезка символов с обоих концов строки
Стандартное использование
trim( text ) trim( text, extra )
Описание

Функция trim() возвращает копию текста, лишенного любого префикса или суффикса, состоящего исключительно из персонажей из доп. Символы из extra, содержащиеся в теле текста, остаются нетронутыми.

Если extra не указан, предполагается, что он состоит из символа пробела ASCII (0x20). По умолчанию символы табуляции и другие пробелы не обрезаются.

См. также
ltrim(), rtrim()
typeof()
Возвращает тип данных значения
Стандартное использование
typeof( param )
Описание
Функция typeof() возвращает текстовое значение, указывающее на собственный тип данных param. Возможные возвращаемые значения включают null, integer, real, text и blob.
См. также
CAST [SQL Expr, Ap D]
upper()
Преобразование всех символов ASCII в верхний регистр
Стандартное использование
upper( text )
Описание

Функция upper() возвращает копию текста, в котором все символы букв преобразованы в верхний регистр. Встроенная реализация этой функции работает только с символами ASCII (теми, которые имеют значение менее 128).

Расширение ICU обеспечивает реализацию функции upper() с поддержкой Unicode.

См. также
lower()
zeroblob()
Возвращает большой двоичный объект, состоящий из обнуленных байтов
Стандартное использование
zeroblob( size )
Описание

Функция zeroblob() возвращает значение большого двоичного объекта, состоящее из байтов размера. Все байты устанавливаются в нулевое значение.

Фактическое значение BLOB не помещается в память. Это делает безопасным создание больших двоичных объектов, превышающих возможности среды. Эта функция чаще всего используется для создания новых значений BLOB как части INSERT или UPDATE. После того, как значение BLOB надлежащего размера было записано в базу данных, API инкрементного ввода-вывода BLOB может использоваться для доступа или обновления подразделов значения BLOB.

См. также
randomblob()

Агрегатные функции

avg()
Вычисление среднего числового значения
Стандартное использование
avg( number )
Описание

Агрегат avg() вычисляет среднее (среднее) числовое значение для всех числовых значений, отличных от NULL. Агрегат пытается преобразовать любой текст или значения BLOB в числовое значение. Если преобразование невозможно, эти строки будут подсчитаны со значением 0.

Если все числовые значения равны NULL, avg() вернет NULL.

См. также
count(), sum(), total()
count()
Подсчитать количество строк
Стандартное использование
count( expression ) count( * )
Описание
Агрегат count() подсчитывает количество строк с выражением, отличным от NULL. Если выражение указано в виде звездочки (*), будет возвращено общее количество строк в совокупной группе, независимо от значения. В некоторых ситуациях вычисление синтаксиса count (*) может быть оптимизировано, и значение результата может быть вычислено без фактического сканирования данных.
См. также
sum(), total()
group_concat()
Объединение значений строк
Стандартное использование
group_concat( element ) group_concat( element, separator )
Описание

Агрегат group_contact() возвращает текстовое значение, которое является объединенным текстовым представлением каждого элемента, отличного от NULL, разделены разделителем.

Если разделитель не указан, предполагается, что это односимвольная строка «,» (запятая) без конечного пробела.

max()
Возвращает наибольшее значение
Стандартное использование
max( value )
Описание

Агрегат max() возвращает наибольшее значение, отличное от NULL. Обычно это используется для поиска наибольшего числового значения, но агрегат будет отсортировать все типы данных. Считается, что значения BLOB имеют наибольшее значение, за которым следуют текстовые значения. За ними следуют числовые типы (смешанные целочисленные значения и значения с плавающей запятой), которые сортируются вместе в их естественном порядке.

Если значение, отличное от NULL, не найдено, max() вернет NULL.

Также существует скалярная версия max(), которая принимает два или более параметров.

См. также
min() [Scalar SQL Func], max() [Scalar SQL Func]
min()
Возвращает наименьшее значение
Стандартное использование
min( value )
Описание

Агрегат min() возвращает наименьшее значение, отличное от NULL. Обычно это используется для поиска наименьшего числового значения, но агрегат отсортирует все типы данных. Числовые типы (смешанные целые числа и числа с плавающей запятой) считаются наименьшими и будут сортироваться вместе в их естественном порядке. За ними следуют текстовые значения, за которыми следуют значения BLOB.

Если значение, отличное от NULL, не найдено, min() вернет NULL.

Также существует скалярная версия min(), которая принимает два или более параметров.

См. также
max(), min()
sum()
Возвращает числовое суммирование
Стандартное использование
sum( number )
Описание

Агрегат sum() вычисляет сумму или итог всех числовых значений, отличных от NULL. Любое число, отличное от NULL, которое не является целым, будет интерпретироваться как значение с плавающей запятой.

Если все числовые значения, отличные от NULL, являются целыми числами, sum() вернет целое число. В противном случае sum() вернет значение с плавающей запятой. Если число, отличное от NULL, не найдено, sum() вернет NULL.

См. также
total(), count()
total()
Возвращает числовой итог
Стандартное использование
total( number )
Описание

Агрегат total() вычисляет сумму или итог всех числовых значений, отличных от NULL. Любое число, отличное от NULL, которое не является целым числом, будет интерпретироваться как значение с плавающей запятой. Агрегат total() специфичен для SQLite.

В отличие от sum() агрегат total() всегда возвращает значение с плавающей запятой. Даже если все числовые выражения равны NULL, total() все равно вернет 0,0.

См. также
sum(), count()

Приложение F
Справочник по SQLite SQL PRAGMA

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

Хотя некоторые прагмы доступны только для чтения, большинство команд прагм можно использовать для запроса текущего значения или установки нового значения. Чтобы запросить текущее значение, просто укажите имя прагмы:

PRAGMA pragma_name;

В большинстве случаев это вернет набор результатов из одного столбца и одной строки с текущим значением. Чтобы установить новое значение, используйте следующий синтаксис:

PRAGMA pragma_name = value;

Хотя несколько прагм вернут обновленное значение, в большинстве случаев установленный синтаксис ничего не вернет. Любая нераспознанная или неверно сформированная прагма вернется без ошибок. Будьте очень осторожны при правильном написании команд прагмы, иначе SQLite сочтет прагму нераспознанной - это ошибка приложения, которую может быть чрезвычайно сложно найти и исправить.

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

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

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

PRAGMA database.pragma_name;
PRAGMA database.pragma_name = value;

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

Ряд значений прагмы - это простые логические значения true/false или значения состояния включения / выключения. В этом приложении эти значения называются «переключателями». Для установки переключателя распознаются несколько различных значений:

Значения True или On Значения False или Off
1 0
YES NO
TRUE FALSE
ON OFF

Прагма switch почти всегда возвращает простое целочисленное значение 0 или 1.

Как правило, любое значение, состоящее из числового значения или константы (например, TRUE), может давать без кавычек. Известные идентификаторы, такие как имя присоединенной базы данных или имя таблицы, также можно указывать без кавычек, если они не содержат зарезервированных символов. В этом случае их следует заключать в двойные кавычки, как если бы вы использовали их в любой другой команде SQL. Другие текстовые значения, такие как пути к файловой системе, следует заключать в одинарные кавычки.

Есть также несколько прагм, предназначенных только для чтения, которые возвращают полный набор результатов из нескольких столбцов и нескольких строк. Эти результаты могут быть обработаны в коде так же, как результат SELECT, с использованием повторных вызовов sqlite3_step() и sqlite3_column_xxx(). Некоторые из этих прагм требуют дополнительных параметров, которые заключены в круглые скобки, как при вызове функции. Например, вот синтаксис прагмы table_info:

PRAGMA database.table_info( table_name );

Для получения дополнительных сведений см. Описание отдельных прагм.

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

К счастью, заголовок файла базы данных обычно не инициализируется и фактически записывается на диск до тех пор, пока он не станет абсолютно необходимым. Эта задержка инициализации позволяет создать пустой файл базы данных путем открытия или присоединения нового файла. Пока все соответствующие прагмы установлены до использования базы данных, все работает, как ожидалось. Обычно инициализация запускается первым оператором CREATE TABLE. Другой случай - подключение дополнительной базы данных. Основная база данных (та, которая открыта для создания соединения с базой данных) должна быть инициализирована до того, как любая другая база данных будет присоединена к соединению с базой данных. Если основная база данных не инициализирована, выполнение команды ATTACH сначала вызовет инициализацию основной (и только основной) базы данных.

И последнее предостережение при использовании инструкций прагмы из C API. То, как и когда прагма вступит в силу, очень зависит от конкретной прагмы и того, как она используется. В некоторых случаях, прагма вызовет изменение, когда будет подготовлен оператор прагмы. В других случаях вы должны пройти через заявление. То, как и когда именно прагмы делают свое дело, зависит от конкретной прагмы и, как известно, меняется от одной версии SQLite к другой. В результате рекомендуется не подготавливать предварительные инструкции прагмы, как это может быть сделано с другими операторами SQL. Скорее, любой оператор прагмы должен быть подготовлен, пошагово и завершен за один проход, когда вы хотите, чтобы прагма вступила в силу. При использовании статического оператора прагмы также было бы совершенно безопасно использовать sqlite3_exec().

SQLite PRAGMAs

auto_vacuum
Настройка параметров автоматического вакуумирования
Стандартное использование
PRAGMA [database.]auto_vacuum; PRAGMA [database.]auto_vacuum = mode;
Описание

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

Значения Смысл
0 или NONE Автоматический вакуум отключен
1 или FULL Автоматический вакуум включен и полностью автоматический
2 или INCREMENTAL Автоматический вакуум включен, но должен быть активирован вручную

Режим установки может быть либо именем, либо целочисленным эквивалентом. Возвращаемое значение всегда будет целым числом.

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

Автоматический вакуум позволяет уменьшить размер файла базы данных по мере удаления данных из базы данных. В ПОЛНОМ автоматическом режиме вакуумирования, свободные страницы автоматически заменяются активной страницей в конце файла базы данных. Затем файл усекается, чтобы освободить неиспользуемое пространство. В режиме FULL в базе данных никогда не должно быть страниц в свободном списке.

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

Замена свободных страниц и обновление ссылок также требует времени обработки, а в режиме FULL это выполняется в конце каждой транзакции. В ИНКРЕМЕНТАЛЬНОМ режиме поддерживаются справочные данные, но бесплатные страницы не меняются местами и не освобождаются - они просто помещаются в список бесплатных. Страницы можно восстановить с помощью прагмы incremental_vacuum. Это намного быстрее, чем полный ВАКУУМ, и позволяет частичное восстановление пространства по запросу. Это также можно сделать без дополнительного свободного места.

Режим можно в любой момент изменить с FULL на FULL. Если база данных находится в режиме INCREMENTAL и переключена в FULL режим, любые страницы в свободном списке будут автоматически восстановлены. Поскольку для режимов FULL и INCREMENTAL требуются дополнительные данные, которые NONE не поддерживает, можно только перейти от NONE к FULL или INCREMENTAL до инициализации базы данных. После инициализации базы данных единственный способ отойти от NONE - это установить прагму и выполнить VACUUM. По аналогии, единственный способ перейти от FULL или INCREMENTAL к NONE - это VACUUM. В этом случае VACUUM требуется, даже если база данных все еще не инициализирована.

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

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

См. также
incremental_vacuum, VACUUM [SQL Cmd, Ap C]
cache_size
Установить размер кэша страницы базы данных
Стандартное использование
PRAGMA [database.]cache_size; PRAGMA [database.]cache_size = pages;
Описание

Прагма cache_size может получить или временно установить максимальный размер кэша страниц в памяти. Значение страниц представляет количество страниц в кеше. Обычно каждая присоединенная база данных имеет независимый кеш.

Корректировки, сделанные с помощью прагмы cache_size, сохраняются только на время существования соединения с базой данных. Обычно эта прагма используется для временного увеличения размера кэша для операций с интенсивным вводом-выводом. При построении нового индекса для большой таблицы увеличение размера кэша (иногда до 100 × или даже 1000 × размера по умолчанию) часто может привести к значительному увеличению производительности.

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

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

Встроенный кеш страниц требует некоторых накладных расходов. Точный размер накладных расходов кэша зависит от платформы, но составляет около 100 байт. Максимальный объем памяти, занимаемый кэшем, можно приблизительно определить по формуле (page_size + 128) * cache_size.

См. также
default_cache_size, page_size
case_sensitive_like
Управление чувствительностью к регистру оператора LIKE
Стандартное использование
PRAGMA case_sensitive_like = switch;
Описание

Прагма case_sensitive_like управляет чувствительностью к регистру во встроенном выражении LIKE. По умолчанию эта прагма имеет значение false, что указывает на то, что встроенный оператор LIKE игнорирует регистр букв. Эта прагма применяется ко всем базам данных, подключенным к соединению с базой данных.

Это прагма только для записи. Невозможно запросить текущее состояние, кроме выполнения оператора SQL, такого как SELECT 'A' NOT LIKE 'a' ;. Если case_sensitive_like истинно, этот оператор вернет 1.

Эта прагма применима только к встроенной версии LIKE. Если поведение по умолчанию было переопределено пользовательской функцией SQL like(), эта прагма игнорируется.

См. также
like() [SQL Func, Ap E], LIKE [SQL Expr, Ap D]
collation_list
Список текущих параметров сортировки
Стандартное использование
PRAGMA collation_list;
Описание

Прагма collation_list перечисляет все активные параметры сортировки в текущем соединении с базой данных. Эта прагма вернет таблицу с двумя столбцами с одной строкой на активное сопоставление.

Имя столбца Тип столбца Значение
seq Integer Имя порядкового номера
name Text Имя сопоставления

Если приложение не определило дополнительные сопоставления, список будет ограничен встроенными сопоставлениями NOCASE, RTRIM и BINARY.

См. также
database_list, sqlite3_create_collation() [C API, Ap G]
count_changes
Включить счетчик изменений для INSERT, UPDATE и DELETE
Стандартное использование
PRAGMA count_changes; PRAGMA count_changes = switch;
Описание

Прагма count_changes получает или устанавливает возвращаемое значение операторов управления данными, таких как INSERT, UPDATE и DELETE. По умолчанию это прагма false, и эти операторы ничего не возвращают. Если установлено значение true, каждый оператор обработки данных будет возвращать таблицу с одним столбцом и одной строкой, состоящую из одного целочисленного значения. Это значение указывает, сколько строк было изменено оператором.

Возвращаемые значения очень похожи на значения, возвращаемые функцией SQL changes(), с одним небольшим отличием. Строки, которые изменяются триггером INSTEAD OF в представлении, будут подсчитаны в возвращаемом значении инструкции, но не будут подсчитаны функцией changes().

Эта прагма влияет на все операторы, обрабатываемые данным подключением к базе данных.

См. также
changes() [SQL Func, Ap E], sqlite3_changes() [C API, Ap G]
database_list
Список подключенных в настоящее время баз данных
Стандартное использование
PRAGMA database_list;
Описание

Прагма database_list перечисляет все присоединенные базы данных в этом соединении с базой данных. Эта прагма вернет таблицу с тремя столбцами с одной строкой на открытую или присоединенную базу данных.

Имя столбца Тип столбца Значение
seq Integer Имя порядкового номера базы данных
name Text Логическое имя базы данных
file Text Путь и имя файла базы данных

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

См. также
ATTACH DATABASE [SQL Cmd, Ap C]
default_cache_size
Установить размер кэша страниц по умолчанию для этой базы данных
Стандартное использование
PRAGMA [database.]default_cache_size; PRAGMA [database.]default_cache_size = pages;
Описание
Прагма default_cache_size управляет размером кэша по умолчанию для данной базы данных. Значение страниц хранится в файле базы данных, что позволяет этому значению сохраняться при подключении к базе данных. Размер кеша по умолчанию можно временно изменить с помощью прагмы cache_size. Установка этого значения также устанавливает ограничение на текущий кеш страницы.
См. также
cache_size, page_size
encoding
Управление кодировкой текста по умолчанию
Стандартное использование
PRAGMA encoding; PRAGMA encoding = format;
Описание

Директива encoding управляет тем, как строки кодируются и хранятся в файле базы данных. Любое строковое значение, записанное в базу данных, сначала перекодируется в этот формат.

Значение формата может быть одним из «UTF-8», «UTF-16le» или «UTF-16be». Кроме того, если задано значение «UTF-16», будет использоваться либо «UTF-16le», либо «UTF-16be», как определено собственным порядком байтов процессора. Любая версия SQLite, работающая на любой платформе, должна иметь возможность читать файл базы данных, независимо от кодировки.

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

См. также
ATTACH DATABASE [SQL Cmd, Ap C]
foreign_keys
Включение ограничений внешнего ключа
Стандартное использование
PRAGMA foreign_keys; PRAGMA foreign_keys = switch;
Описание

Прагма foreign_keys контролирует соблюдение ограничений внешнего ключа во всех подключенных базах данных. Если установлено значение off, ограничения внешнего ключа игнорируются. Значение по умолчанию выключено.

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

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

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

См. также
foreign_key_list
foreign_key_list
Список всех внешних ключей в данной таблице
Стандартное использование
PRAGMA [database.]foreign_key_list( table_name );
Описание

Стандартное использование PRAGMA [база данных.] Foreign_key_list (имя_таблицы); Описание Прагма foreign_key_list перечисляет все ссылки на внешние ключи, которые являются частью указанной таблицы. Этот список будет содержать по одной строке для каждого столбца каждого внешнего ключа, содержащегося в таблице.

Имя столбца Тип столбца Значение
id Integer ID внешнего ключа
seq Integer Порядковый номер столбца для этого ключа
table Text Имя внешней таблицы
from Text Имя локального столбца
to Text Имя внешнего столбца
on_update Text Действие ON UPDATE
on_delete Text Действие ON UPDATE
match Text Всегда NONE
См. также
foreign_keys
freelist_count
Возвращает количество свободных страниц в файле базы данных
Стандартное использование
PRAGMA [database.]freelist_count;
Описание
Прагма freelist_count возвращает одно целое число, указывающее, сколько страниц базы данных в настоящее время помечены как свободные и доступные (не содержат действительных данных). Эти страницы можно восстановить, очистив базу данных.
См. также
auto_vacuum, incremental_vacuum, VACUUM [SQL Cmd, Ap C]
full_column_names
Управляющий формат имени столбца, используемый в запросах
Стандартное использование
PRAGMA full_column_names; PRAGMA full_column_names = switch;
Описание

Прагма full_column_names в сочетании с прагмой short_column_names, управляет тем, как соединение с базой данных определяет и форматирует имена столбцов в наборах результатов. Если full_col umn_name включен, выражения выходного столбца, состоящие из одного именованного столбца таблицы, будут расширены до полного формата table_name.column_name. По умолчанию он выключен.

Общие правила для выходных имен следующие:

  • Если выходной столбец имеет предложение имени AS, используется имя.
  • Если включено short_column_names и выходной столбец является неизмененным исходным столбцом, имя столбца набора результатов - column_name.
  • Если включено full_column_names и выходной столбец является неизмененным исходным столбцом, имя столбца набора результатов будет table_name.column_name.
  • Имя столбца набора результатов - это текст выражения столбца, как указано.

Если включены и full_column_names, и short_column_names, short_column_names переопределит full_column_names.

Обратите внимание, что нет гарантии, что имена столбцов набора результатов будут соответствовать будущим версиям SQLite. Если ваше приложение зависит от определенных, узнаваемых имен столбцов, вы всегда должны использовать предложение AS.

См. также
short_column_names
fullfsync
Управление уровнем синхронизации диска
Стандартное использование
PRAGMA fullfsync; PRAGMA fullfsync = switch;
Описание
Прагма fullfsync включает метод синхронизации файловой системы F_FULLFSYNC. Это обеспечивает дополнительный уровень надежной синхронизации файлов. В настоящее время Mac OS X - единственная операционная система, поддерживающая этот метод.
См. также
synchronous
ignore_check_constraints
Отключить ограничения CHECK
Стандартное использование
PRAGMA ignore_check_constraints; PRAGMA ignore_check_constraints = switch;
Описание

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

Это недокументированная прагма.

incremental_vacuum
Активация инкрементального вакуума
Стандартное использование
PRAGMA [database.]incremental_vacuum( pages );
Описание

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

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

См. также
auto_vacuum, freelist_count, VACUUM [SQL Cmd, Ap C]
index_info
Список таблиц и столбцов, содержащихся в индексе
Стандартное использование
PRAGMA [database.]index_info( index_name );
Описание

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

Имя столбца Тип столбца Значение
seq Integer Порядковый номер столбца
cid Integer Индекс столбца в таблице
name Text Имя столбца
См. также
index_list, table_info
index_list
Список всех индексов, связанных с таблицей
Стандартное использование
PRAGMA [database.]index_list( table_name );
Описание

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

Имя столбца Тип столбца Значение
seq Integer Порядковый номер индекса
name Text Имя индекса
unique Integer Является UNIQUE index
См. также
index_info, table_info
integrity_check
Инициировать проверку целостности файла базы данных
Стандартное использование
PRAGMA [database.]integrity_check; PRAGMA [database.]integrity_chcek( max_errors );
Описание

Прагма Integrity_check запускает самопроверку структуры базы данных. Проверка проходит через серию тестов, которые проверяют целостность файла базы данных, его структуру и содержимое. Ошибки возвращаются в виде текстовых описаний в таблице с одним столбцом. Максимум max_errors сообщается до прерывания проверки целостности. По умолчанию max_errors равно 100.

Если ошибок не обнаружено, будет возвращена одна строка, состоящая из текстового значения ok.

К сожалению, если обнаружена ошибка, обычно очень мало что можно сделать для ее исправления. Хотя вы можете извлечь некоторые данные, лучше всего иметь регулярные дампы или резервные копии.

См. также
quick_check
journal_mode
Управление созданием и очисткой файлов журнала
Стандартное использование
PRAGMA journal_mode; PRAGMA journal_mode = mode; PRAGMA database.journal_mode; PRAGMA database.journal_mode = mode;
Описание

Прагма journal_mode получает или устанавливает режим журнала. Режим журнала контролирует, как файл журнала хранится и обрабатывается.

Файлы журнала используются SQLite для отката транзакций из-за явной команды ROLLBACK или из-за неисправимой ошибки (например, нарушения ограничения). Обычно, файл журнала необходим для обработки любой команды SQL, которая вызывает изменение файла базы данных. Активное время жизни файла журнала простирается от первой модификации базы данных до конца процесса фиксации. Файлы журнала требуются как для транзакций с автоматической фиксацией (отдельные команды SQL), так и для явных транзакций. Файлы журнала - важная часть процесса транзакции.

Существует пять поддерживаемых режимов журнала:
DELETE
Это нормальное поведение. При завершении транзакции файл журнала удаляется.
TRUNCATE
Размер файла журнала усекается до нулевой длины. Во многих файловых системах это немного быстрее, чем удаление.
PERSIST
Файл журнала остается на месте, но заголовок перезаписывается, чтобы указать, что журнал больше не действителен. В некоторых файловых системах это может быть еще быстрее, поскольку блоки файлов остаются выделенными.
MEMORY
Запись журнала хранится в памяти, а не на диске. Этот вариант очень быстрый, но в то же время несколько опасный. Если приложение аварийно завершает работу или прекращает работу во время выполнения транзакции, это может привести к повреждению файла базы данных.
OFF
Журнал не ведется. Как и в режиме журнала MEMORY, если приложение выйдет из строя или будет убито, база данных, скорее всего, будет повреждена. Кроме того, если журнал полностью выключен, база данных не имеет возможности отката. Команду ROLLBACK не следует использовать для базы данных, в которой отключен журнал.

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

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

Все версии прагмы journal_mode вернут текущее значение в виде текстового значения в нижнем регистре.

Базы данных в памяти поддерживают только режимы MEMORY и OFF. Любая попытка установить для базы данных inmemory другой режим автоматически завершится ошибкой, и будет возвращен существующий режим.

См. также
journal_size_limit, synchronous, ROLLBACK TRANSACTION [SQL Cmd, Ap C]
journal_size_limit
Установить максимальный размер для выпущенных файлов журнала
Стандартное использование
PRAGMA [database.]journal_size_limit; PRAGMA [database.]journal_size_limit = max_bytes;
Описание

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

Если установлен предел размера журнала, SQLite найдет время, чтобы изучить любой файл журнала, который обычно остается на месте. Если размер файла журнала превышает max_bytes, файл обрезается до max_bytes. Это предохраняет очень большие файлы журнала (например, те, что остались после ВАКУУМА) от дальнейшего использования чрезмерного дискового пространства.

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

Оба синтаксиса get и set вернут текущий предел.

См. также
journal_mode, locking_mode
legacy_file_format
Определяет формат по умолчанию для новых файлов базы данных
Стандартное использование
PRAGMA legacy_file_format; PRAGMA legacy_file_format = switch;
Описание

Прагма legacy_file_format получает или устанавливает флаг устаревшей совместимости. Этот флаг используется для определения того, какой формат файла будет использоваться при создании новых файлов базы данных. Если для флага установлено значение true, SQLite создаст новые файлы базы данных в «устаревшем» формате файлов, который обратно совместим до SQLite v3.0.0. Если флаг не установлен, SQLite создаст новые файлы в самом актуальном формате, поддерживаемом этой библиотекой.

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

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

Основное различие между исходным форматом файла и самым последним форматом файла заключается в поддержке нисходящих индексов. Нисходящие индексы нельзя использовать вместе с устаревшим форматом файлов. В новом формате также используется более эффективное представление на диске целочисленных значений 0 и 1 (используемых для логических значений), что позволяет экономить один байт на значение.

Обратите внимание, что использование этой прагмы не гарантирует обратной совместимости. Если база данных создается с использованием устаревшего формата файла, но схема (CREATE TABLE и т. д.) Использует более современные функции языка SQL, вы не сможете открыть базу данных с помощью более старой версии SQLite.

Когда был впервые представлен последний формат файла (формат v4), этот флаг имел значение по умолчанию false. Это вызвало множество проблем в средах, в которых выполнялись инкрементные или спорадические обновления. Чтобы избежать таких проблем, значение по умолчанию было изменено на true. Вполне возможно, что значение по умолчанию для этой прагмы может измениться в будущем. Формат файла v4 обратно совместим с версией SQLite 3.3.0.

См. также
VACUUM [SQL Cmd, Ap C], CREATE INDEX [SQL Cmd, Ap C]
locking_mode
Управляет тем, как база данных снимает блокировки чтения/записи
Стандартное использование
PRAGMA locking_mode; PRAGMA locking_mode = mode; PRAGMA database.locking_mode; PRAGMA database.locking_mode = mode;
Описание

Прагма lock_mode управляет управлением блокировками файлов базы данных. Режим может быть НОРМАЛЬНЫМ или ЭКСКЛЮЗИВНЫМ. В нормальном режиме соединение с базой данных устанавливает и снимает соответствующие блокировки с каждой транзакцией. В монопольном режиме блокировки устанавливаются обычным образом, но не снимаются после завершения транзакции.

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

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

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

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

Есть два способа снять блокировки базы данных в монопольном режиме. Во-первых, базу данных можно просто закрыть (или отсоединить). Это снимет все блокировки. База данных может быть повторно открыта (или повторно подключена). Даже если режим блокировки по умолчанию является эксклюзивным, блокировки не будут получены до тех пор, пока база данных не пройдет цикл транзакции.

Второй способ снять блокировки - это перевести базу данных в нормальный заблокированный режим и выполнить некоторую команду SQL, которая вызовет цикл транзакции блокировки / разблокировки. Простой перевод базы данных в нормальный режим не снимает уже установленных блокировок.

См. также
journal_mode, lock_status, ATTACH DATABASE [SQL Cmd, Ap C], DETACH DATABASE [SQL Cmd, Ap C]
lock_proxy_file
Установить каталог блокировки прокси
Стандартное использование
PRAGMA lock_proxy_file; PRAGMA lock_proxy_file = ':auto:'; PRAGMA lock_proxy_file = 'file_path';
Описание

Прагма lock_proxy_file получает или устанавливает файл, используемый для блокировок прокси. Значение: auto: вернет его к автоматическому значению по умолчанию. Система блокировки прокси доступна только в Mac OS X и применяется только к базам данных, размещенным в общих папках Apple Filing Protocol.

Это недокументированная прагма.

lock_status
Отображение состояния блокировки каждой базы данных
Стандартное использование
PRAGMA lock_status;
Описание

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

Имя столбца Тип столбца Значение
database Text Имя базы данных
status Text Статус блокировки

Могут отображаться следующие состояния блокировки:

Значение Смысл
unlocked База данных не имеет блокировок
shared База данных имеет общую блокировку чтения
reserved База данных имеет зарезервированную блокировку с возможностью записи
pending База данных имеет ожидающую блокировку разрешения на запись
exclusive База данных имеет эксклюзивную блокировку записи
closed Файл базы данных не открыт
unknown Состояние блокировки неизвестно

Первые пять состояний соответствуют стандартным блокировкам, которые SQLite использует для поддержки транзакций чтения и записи. Закрытое значение обычно возвращается, когда новый файл базы данных был создан, но еще не инициализирован. Поскольку блокировка выполняется через файловую систему, любая база данных, не имеющая связанного файла, вернет unknown. Сюда входят как базы данных inmemory, так и а также некоторые временные базы данных.

SQLite должен быть скомпилирован с директивой SQLITE_DEBUG, чтобы эта прагма была включена.

Это недокументированная прагма.

См. также
schema_version, journal_mode, journal_size_limit, ATTACH DATABASE [SQL Cmd, Ap C], DETACH DATABASE [SQL Cmd, Ap C]
max_page_count
Ограничение размера базы данных
Стандартное использование
PRAGMA [database.]max_page_count; PRAGMA [database.]max_page_count = max_page;
Описание

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

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

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

Значение по умолчанию - 1 073 741 823 (230 - 1 или одна гига-страница). При использовании в сочетании с размером страницы 1 КБ по умолчанию это позволяет базам данных увеличиваться до одного терабайта. Невозможно восстановить значение по умолчанию, кроме ручной установки того же значения (или закрытия и повторного открытия базы данных). Значение max_page ограничено 32-битным значением со знаком.

См. также
page_count, page_size
omit_readlock
Отключить блокировку чтения для файлов, доступных только для чтения
Стандартное использование
PRAGMA omit_readlock; PRAGMA omit_readlock = switch;
Описание

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

Этот флаг особенно полезен при доступе к файлам базы данных на носителе только для чтения, таком как оптический диск. Его также можно безопасно использовать для «справочных» файлов базы данных, таких как файлы словарей, которые распространяются, но никогда не изменяются клиентским программным обеспечением.

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

Это недокументированная прагма.

page_count
Возвращает общее количество страниц в базе данных
Стандартное использование
PRAGMA [database.]page_count;
Описание
Прагма page_count возвращает текущее количество страниц в базе данных. Счетчик страниц включает все страницы в файле, включая бесплатные страницы. Размер файла базы данных должен быть page_count * page_size.
См. также
max_page_count, page_size, cache_size, freelist_count
page_size
Задайте размер страницы базы данных
Стандартное использование
PRAGMA [database.]page_size; PRAGMA [database.]page_size = bytes;
Описание

Прагма page_size получает или устанавливает размер страниц базы данных. Размер байта должен быть степенью двойки. По умолчанию допустимые размеры: 512, 1024, 2048, 4096, 8192, 16384 и 32768 байт. Это значение становится фиксированным после инициализации базы данных. Единственный способ изменить размер страницы в существующей базе данных - это установить размер страницы, а затем немедленно очистить базу данных.

Размер страницы по умолчанию рассчитывается на основе ряда факторов. Размер страницы по умолчанию начинается с 1024 байтов. Если драйвер типа данных указывает, что собственный блок ввода-вывода файловой системы больше, это большее значение будет использоваться до максимального размера по умолчанию, который обычно равен 8192. Эти значения можно изменить с помощью SQLITE_DEFAULT_PAGE_SIZE, SQLITE_MAX_DEFAULT_PAGE_ SIZE, и директивы времени компиляции SQLITE_MAX_PAGE_SIZE.

Конечным результатом является то, что размер страницы для файловых баз данных обычно составляет от 1 КБ до 4 КБ в Microsoft Windows и 1 КБ в большинстве других систем, включая Mac OS X, Linux и другие системы Unix.

См. также
cache_size
parser_trace
Включить отладочную информацию анализатора
Стандартное использование
PRAGMA parser_trace = switch;
Описание
Прагма parser_trace включает отладку синтаксического анализатора SQL. Если этот параметр включен, синтаксический анализатор SQL будет печатать свое состояние при анализе команд SQL. SQLite должен быть скомпилирован с директивой SQLITE_DEBUG для включения функции трассировки парсера.
См. также
sql_trace, vdbe_trace, vdbe_listing
quick_check
Инициировать частичную проверку целостности файла базы данных
Стандартное использование
PRAGMA [database.]quick_check; PRAGMA [database.]quick_check( max_errors );
Описание
Прагма quick_check запускает сокращенную проверку целостности файла базы данных. Прагма quick_check идентична прагме Integration_check, за исключением того, что она не проверяет, синхронизируется ли содержимое каждого индекса с данными исходной таблицы. Пропустив этот тест, время, необходимое для проведения проверки целостности, значительно сокращается.
См. также
integrity_check
read_uncommitted
Разрешить чтение незафиксированных данных из кэша базы данных
Стандартное использование
PRAGMA read_uncommitted; PRAGMA read_uncommitted = switch;
Описание

Прагма read_uncommitted получает или устанавливает метод изоляции общего кэша.

Если один и тот же процесс открывает одну и ту же базу данных несколько раз, SQLite можно настроить так, чтобы эти соединения могли совместно использовать один экземпляр кеша. Это полезно в ситуациях с очень низким объемом памяти, например в недорогих встроенных системах.

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

Прагма read_uncommitted применима только к системам, использующим режим общего кэша, который обычно не используется в настольных системах. Если вы используете режим общего кеша и вам может потребоваться эта прагма, пожалуйста, обратитесь к исходному коду и онлайн-документации по адресу http://www.sqlite.org/sharedcache.html для получения дополнительной информации.

recursive_triggers
Включить рекурсивные вызовы триггеров
Стандартное использование
PRAGMA recursive_triggers; PRAGMA recursive_triggers = switch;
Описание

Прагма recursive_triggers получает или устанавливает функциональность рекурсивного триггера. Если рекурсивные триггеры не включены, действие триггера не будет запускать другой триггер.

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

См. также
foreign_keys
reverse_unordered_selects
Изменение порядка следования неотсортированных результатов запроса
Стандартное использование
PRAGMA reverse_unordered_selects; PRAGMA reverse_unordered_selects = switch;
Описание

Прагма reverse_unordered_selects получает или устанавливает флаг обратного выбора. Если этот флаг установлен, SQLite изменит естественный порядок операторов SELECT, не имеющих явного предложения ORDER BY.

SQLite, как и стандарт SQL, не дает никаких обещаний относительно порядка результатов SELECT, которые не имеют явного предложения ORDER BY. Изменения в базе данных (например, добавление индекса), или изменения в оптимизаторе запросов могут привести к тому, что SQLite будет возвращать строки в другом порядке.

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

schema_version
Управление изменением схемы базы данных
Стандартное использование
PRAGMA [database.]schema_version; PRAGMA [database.]schema_version = number;
Описание

Прагма schema_version получает или задает значение версии схемы, которое хранится в заголовке базы данных. Это 32-битное целое число со знаком, которое отслеживает изменения схемы. Всякий раз, когда выполняется команда изменения схемы (например, CREATE ... или DROP ...), это значение увеличивается.

Это значение используется внутри SQLite для обеспечения согласованности нескольких различных кешей, а также для обеспечения согласованности подготовленных операторов с текущим форматом базы данных. Изменение этого значения вручную может привести к повреждению базы данных.

См. также
user_version
secure_delete
Заменять удаленное содержимое нулями
Стандартное использование
PRAGMA secure_delete; PRAGMA secure_delete = switch; PRAGMA database.secure_delete; PRAGMA database.secure_delete = switch;
Описание

Прагма secure_delete используется для управления удалением содержимого из базы данных. Обычно удаленный контент просто помечается как неиспользуемый. Если установлен флаг secure_delete, удаленное содержимое сначала перезаписывается серией значений из 0 байтов, удаляя удаленные значения из файла базы данных. Значение по умолчанию для флага безопасного удаления обычно выключено, но это можно изменить с помощью параметра сборки SQLITE_SECURE_DELETE.

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

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

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

См. также
SQLITE_SECURE_DELETE
short_column_names
Управляющий формат имени столбца, используемый в запросах
Стандартное использование
PRAGMA short_column_names; PRAGMA short_column_names = switch;
Описание

Прагма short_column_names в сочетании с прагмой full_column_names управляет тем, как соединение с базой данных определяет и форматирует имена столбцов в наборах результатов. Если включен short_col umn_name, выражения выходных столбцов, состоящие из одного именованного столбца таблицы, будут обрезаны, чтобы включать только имя столбца. Эта прагма включена по умолчанию.

Общие правила для выходных имен следующие:

  • Если выходной столбец имеет предложение имени AS, используется имя.
  • Если включено short_column_names и выходной столбец является неизмененным исходным столбцом, имя столбца набора результатов - имя_столбца.
  • Если включено full_column_names и выходной столбец является неизмененным исходным столбцом, имя столбца набора результатов будет table_name.column_name.
  • Имя столбца набора результатов - это текст выражения столбца, как указано.

Если включены и full_column_names, и short_column_names, short_column_names переопределит full_column_names.

Обратите внимание, что нет гарантии, что имена столбцов набора результатов будут соответствовать будущим версиям SQLite. Если ваше приложение зависит от определенных, узнаваемых имен столбцов, вам следует использовать предложение AS.

См. также
full_column_names
sql_trace
Дамп данных трассировки SQL
Стандартное использование
PRAGMA sql_trace; PRAGMA sql_trace = switch;
Описание

Прагма sql_trace выводит результаты трассировки SQL на экран. При включении Данные трассировки, обычно передаваемые в функцию обратного вызова sqlite3_trace(), будут напечатаны. SQLite должен быть скомпилирован с директивой SQLITE_DEBUG, чтобы эта прагма была включена.

Это недокументированная прагма.

См. также
vdbe_trace, parser_trace, vdbe_listing
synchronous
Управление синхронизацией диска базы данных
Стандартное использование
PRAGMA [database.]synchronous; PRAGMA [database.]synchronous = mode;
Описание

Прагма synchronous получает или устанавливает текущий режим синхронизации диска. Это контролирует, насколько агрессивно SQLite будет записывать данные полностью в физическое хранилище.

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

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

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

SQLite предлагает три уровня защиты, как показано в следующей таблице:

Режим Значение
0 или OFF Нет синхронизации вообще
1 или NORMAL Синхронизация после каждой последовательности критических дисковых операций
2 или FULL Синхронизация после каждой критической дисковой операции

Режим установки может быть именем или целочисленным эквивалентом. Возвращаемое значение всегда будет целым числом.

В режиме FULL полная синхронизация выполняется после каждой критической операции с диском. Этот режим разработан, чтобы избежать повреждения в случае сбоя любого приложения, сбоя системы или сбоя питания. Это самый безопасный, но и самый медленный. Режим по умолчанию - FULL (не NORMAL).

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

В режиме ВЫКЛ не предпринимаются попытки сбросить или синхронизировать записи, оставляя на усмотрение операционной системы записывать любой кеш файловой системы по своему усмотрению. Этот режим намного, намного быстрее, чем два других режима, но оставляет SQLite открытым для сбоев системы и сбоев питания даже после завершения транзакции.

Имейте в виду, что в некоторых системах сбой питания все еще может вызвать повреждение базы данных, даже если они работают в ПОЛНОМ режиме. Существует ряд контроллеров дисков, которые ложно сообщают об успешном завершении запроса на синхронизацию до того, как все данные будут фактически записаны в энергонезависимое хранилище. Если сбой питания происходит, когда данные все еще хранятся в кэше контроллера диска, повреждение файла все равно может произойти. К сожалению, ни SQLite, ни операционная система хоста ничего не могут с этим поделать, поскольку операционная система или SQLite не могут узнать, что информация неверна.

См. также
journal_mode, fullfsync
table_info
Список информации о столбцах для таблицы
Стандартное использование
PRAGMA [database.]table_info( table_name );
Описание

Прагма table_info используется для запроса информации о конкретной таблице. Набор результатов будет содержать по одной строке для каждого столбца в таблице.

Имя столбца Тип столбца Значение
cid Integer Индекс столбца
name Text Имя столбца
type Text Тип столбца, как указано
notnull Integer Имеет ограничение NOT NULL
dflt_value Text Значение DEFAULT
pk Integer Является частью PRIMARY KEY

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

См. также
index_list
temp_store
Управление режимом временного хранения
Стандартное использование
PRAGMA temp_store = mode;
Описание

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

SQLite поддерживает следующие режимы хранения:

Режим Значение
0 или DEFAULT Использовать значение времени компиляции по умолчанию. Обычно FILE.
1 или FILE Использовать хранилище на основе файлов
2 или MEMORY Использовать хранилище на основе памяти

Установленным режимом может быть имя или целочисленный эквивалент. Возвращаемое значение всегда будет целым числом.

Хранилище на основе памяти сделает временные базы данных эквивалентными базам данных в памяти. Файловые базы данных изначально будут базами данных в памяти, пока они не перерастут кеш страницы. Это означает, что многие «файловые» временные базы данных никогда не превращаются в файл.

Изменение режима приведет к удалению всех временных баз данных (и содержащихся в них данных).

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

Значение Смысл
0 Всегда использовать файлы, игнорировать прагму
1 Разрешить прагму, по умолчанию для файлов
2 Разрешить прагму, по умолчанию использовать память
3 Всегда использовать память, игнорировать прагму

По умолчанию временное хранилище использует файлы, но позволяет прагме temp_store переопределить этот выбор.

См. также
temp_store_directory, journal_mode
temp_store_directory
Управление местом хранения временных файлов
Стандартное использование
PRAGMA temp_store_directory; PRAGMA temp_store_directory = 'directory_path';
Описание

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

Чтобы вернуть каталог к его значению по умолчанию, установите directory_path в строку нулевой длины. Значение по умолчанию (а также интерпретация этого значения) зависит от ОС.

Изменение этой прагмы приведет к удалению всех временных баз данных (и данных, которые они содержат). Установка этой прагмы не является потокобезопасной. В идеале эту директиву следует устанавливать только при открытии основной базы данных.

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

См. также
temp_store
user_version
Управление пользовательским управлением версиями
Стандартное использование
PRAGMA [database.]user_version; PRAGMA [database.]user_version = number;
Описание
Прагма user_version получает или устанавливает определяемое пользователем значение версии, которое хранится в заголовке базы данных. Это 32-битное целое число со знаком, которое может использоваться для любых целей, которые разработчик сочтет нужным. Это значение никоим образом не используется SQLite.
См. также
schema_version
vdbe_trace
Включение отладочной информации VDBE
Стандартное использование
PRAGMA vdbe_trace; PRAGMA vdbe_trace = switch;
Описание

Прагма vdbe_trace включает отладку механизма виртуальной базы данных (VDBE). Если этот параметр включен, каждая инструкция VDBE печатается непосредственно перед выполнением. SQLite должен быть скомпилирован с директивой SQLITE_DEBUG для включения функции трассировки VDBE.

Дополнительную информацию см. в интерактивной документации VDBE (http://www.sqlite.org/vdbe.html).

См. также
vdbe_listing, sql_trace, parser_trace
vdbe_listing
Заставляет VDBE создавать дамп каждой программы перед выполнением
Стандартное использование
PRAGMA vdbe_listing; PRAGMA vdbe_listing = switch;
Описание

Прагма vdbe_listing включает отладку VDBE. Если этот параметр включен, каждая программа VDBE печатается непосредственно перед выполнением. SQLite должен быть скомпилирован с директивой SQLITE_DEBUG, чтобы функция списка VDBE была включена.

Дополнительную информацию см. в интерактивной документации VDBE (http://www.sqlite.org/vdbe.html).

См. также
vdbe_trace, sql_trace, parser_trace
writable_schema
Разрешить изменение системных таблиц
Стандартное использование
PRAGMA writable_schema; PRAGMA writable_schema = switch;
Описание

Прагма Writable_schema получает или устанавливает возможность изменения системных таблиц. Если установлена схема Writable_, можно создавать и изменять таблицы, начинающиеся с sqlite_, включая таблицу sqlite_master.

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

Это недокументированная прагма.

См. также
CREATE

Приложение G
Справочник по API SQLite C

В этом приложении рассматриваются структуры данных и функции, составляющие API программирования на языке C. Приложение предназначено для использования в качестве справочника, предоставляя конкретные сведения о каждой функции и структуре данных. Однако он не дает подробного обзора того, как все части сочетаются друг с другом в полезном приложении. Чтобы понять идеи и шаблоны, лежащие в основе API, Настоятельно рекомендуется просмотреть материал в главе 7.

Пункты, отмеченные [EXP], следует считать экспериментальными. Это не означает, что эти функции и возможности являются рискованными или непроверенными, просто они новее, и есть некоторая вероятность того, что интерфейс может немного измениться в будущей версии SQLite. Это довольно редко, но бывает. Те функции, которые не отмечены как экспериментальные, более или менее высечены из камня. Поведение функции может измениться, если будет обнаружена незначительная ошибка, но определение интерфейса не изменится. Команда SQLite очень твердо верит в обратную совместимость и не изменит поведение существующего интерфейса, если есть вероятность, что это может нарушить работу существующих приложений. Новые функции, требующие изменений, приводят к появлению новых интерфейсов, таких как версия _v2 некоторых функций (например, sqlite3_open_v2()). Как правило, это делает обновление библиотеки SQLite, используемой с существующей библиотекой, процессом с низким уровнем риска.

Официальную документацию для полного интерфейса программирования C можно найти по адресу http://www.sqlite.org/c3ref/intro.html.

Типы данных API

Это неполный список типов данных C, используемых API SQLite. Перечислены все распространенные типы данных. Те, которые были опущены, являются чрезвычайно специализированными и используются только при реализации низкоуровневых расширений. Полный список и дополнительную информацию см. на http://www.sqlite.org/c3ref/objlist.html или в файлах заголовков SQLite.

sqlite3
Соединение с базой данных
Описание

Структура sqlite3 представляет соединение с одним или несколькими файлами базы данных. Структура создается с помощью вызова sqlite3_open() или sqlite3_open_v2() и уничтожается с помощью sqlite3_close(). Почти все операции по управлению данными должны выполняться в контексте экземпляра sqlite3.

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

См. также
sqlite3_open(), sqlite3_open_v2(), sqlite3_close()
sqlite3_backup
Контекст оперативного резервного копирования [EXP]
Описание

Структура sqlite3_backup выполняет оперативное резервное копирование. Это делается путем создания низкоуровневой постраничной копии образа базы данных. Структура создается с помощью вызова sqlite3_backup_init() и уничтожается с помощью sqlite3_backup_finish().

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

См. также
sqlite3_backup_init(), sqlite3_backup_finish()
sqlite3_blob
Инкрементный ввод-вывод BLOB
Описание

Структура sqlite3_blob выполняет добавочный ввод-вывод для значения BLOB. Это позволяет приложению читать и записывать значения BLOB, которые слишком велики для размещения в памяти. Структура создается с помощью sqlite3_blob_open() и уничтожается с помощью sqlite3_blob_close().

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

См. также
sqlite3_blob_open(), sqlite3_blob_close()
sqlite3_context
SQL функция контекста
Описание

Структура sqlite3_context действует как контейнер данных для передачи информации между библиотекой SQLite и реализацией настраиваемой функции SQL. Структура создается библиотекой SQLite и передается в обратные вызовы функции, зарегистрированные с помощью sqlite3_create_function(). Функции обратного вызова могут извлекать соединение с базой данных или указатель данных пользователя из контекста. Контекст также используется для передачи значения результата SQL или условия ошибки.

Приложение никогда не должно создавать или уничтожать структуру sqlite3_context.

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

См. также
sqlite3_create_function()
sqlite3_int64, sqlite3_uint64, sqlite_int64, sqlite_uint64
64-разрядные целочисленные значения
Описание
64-разрядные целые типы, не зависящие от платформы, процессора и компилятора.
sqlite3_module
Реализация виртуальной таблицы [EXP]
Описание
Структура sqlite3_module определяет виртуальную таблицу. Структура содержит ряд указателей на функции, которые вместе обеспечивают реализацию виртуальной таблицы. Код для расширения виртуальной таблицы обычно выделяет статическую структуру sqlite3_module. Эта структура инициализируется всеми указателями на соответствующие функции, содержащимися в модуле, а затем передается в sqlite3_create_module().
См. также
sqlite3_create_module()
sqlite3_mutex
Блокировка взаимного исключения
Описание

Структура sqlite3_mutex обеспечивает абстрактную блокировку взаимного исключения. Приложение может создавать свои собственные блокировки с помощью вызова sqlite3_mutex_alloc(), но гораздо чаще используется ссылка на блокировку, используемую соединением с базой данных. Эту блокировку можно получить с помощью вызова sqlite3_db_mutex(). Блокировки мьютексов блокируются и разблокируются с помощью вызовов sqlite3_mutex_enter() и sqlite3_mutex_leave().

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

См. также
sqlite3_db_mutex(), sqlite3_mutex_alloc()
sqlite3_stmt
Подготовленный оператор
Описание

Структура sqlite3_stmt содержит подготовленный оператор. Это вся информация о состоянии и выполнении, необходимая для построения и выполнения оператора SQL. Операторы используются для установки любых связанных значений параметров и получения любых значений результатов. Операторы создаются с помощью sqlite3_pre pare_xxx() и уничтожаются с помощью sqlite3_finalize(). Заявления всегда связаны с конкретным подключением к базе данных.

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

См. также
sqlite3_prepare_xxx(), sqlite3_finalize()
sqlite3_value
Значение базы данных
Описание

Структура sqlite3_value содержит значение базы данных. Структура содержит как значение, так и информацию о типе. Значение может содержать целое число или число с плавающей запятой, BLOB, текстовое значение в одной из множества различных кодировок UTF или NULL. Значения используются в качестве параметров для реализаций функций SQL. Их также можно извлечь из результатов выписки.

Структуры ценностей бывают двух типов: защищенные и незащищенные. Защищенные значения могут безопасно преобразовываться в автономный тип, а незащищенные - нет. Параметры функции SQL защищены и могут быть переданы в любую форму sqlite3_value_xxx(). Значения, извлеченные из sqlite3_column_value(), не являются незащищенными. Их можно безопасно передавать в sqlite3_bind_value() или sqlite3_result_value(), но нельзя передавать в sqlite3_value_xxx(). Чтобы извлечь собственный тип данных C из оператора, используйте одну из других функций sqlite3_column_xxx().

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

См. также
sqlite3_column_xxx(), sqlite3_bind_xxx(), sqlite3_value_xxx(), sqlite3_result_xxx()
sqlite3_vfs
Реализация виртуальной файловой системы
Описание
Структура sqlite3_vfs состоит из серии указателей модулей функций, которые составляют виртуальную файловую систему (VFS). Реализация VFS обычно выделяет структуру sqlite3_vfs, инициализирует различные поля и передает структуру в sqlite3_vfs_register().
См. также
sqlite3_vfs_register()

Функции API

Это список почти всех вызовов поддерживаемых функций в API SQLite. Единственные функции, которые были опущены, - это те, которые связаны с внутренним тестированием разработчика, и те функции, которые считаются устаревшими.

Многие функции возвращают код результата SQLite. Тема кодов результатов довольно сложна и имеет долгую историю. Полное обсуждение см. В разделе «Коды результатов и коды ошибок». Однако основы довольно просты. Почти каждая функция возвращает SQLITE_OK, если функция завершилась успешно. Большинство других кодов результатов указывают на какой-то тип ошибки, но не на все. Например, коды SQLITE_ROW и SQLITE_DONE используются для обозначения определенного состояния программы. Результат SQLITE_MISUSE означает, что приложение неправильно использует API. Обычно это вызвано коды SQLITE_ROW и SQLITE_DONE используются для обозначения определенного состояния программы. Результат SQLITE_MISUSE означает, что приложение неправильно использует API. Обычно это вызвано коды SQLITE_ROW и SQLITE_DONE используются для обозначения определенного состояния программы. Результат SQLITE_MISUSE означает, что приложение неправильно использует API. Обычно это вызванонепонимание разработчиками того, как работает API, или из-за ошибки выполнения программы или логической ошибки, которая вызывает непреднамеренную последовательность вызовов.

sqlite3_aggregate_context()
Выделить или получить память агрегированного состояния
Определение
void* sqlite3_aggregate_context( sqlite3_context* ctx, int size );
ctx
Контекст функции SQL, предоставляемый библиотекой SQLite.
size
Размер выделяемой памяти в байтах.
Возвращает
Указатель на совокупное выделение памяти.
Описание

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

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

См. также
sqlite3_create_function()
sqlite3_auto_extension()
Регистрация автоматического расширения
Определение
int sqlite3_auto_extension( entry_point );

void entry_point( );
entry_point
Указатель функции на точку входа расширения.
Возвращает
Код результата SQLite.
Описание

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

Хотя точка входа задана как void entry_point(), фактический формат функции точки входа следующий:

int entry_point( sqlite3* db, char** error, const sqlite3_api_routines* api );

Несоответствие типов, вероятно, потребует преобразования или переопределения функции точки входа.

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

Дополнительную информацию о расширениях и точках входа см. В разделе «Расширения SQLite».

См. также
sqlite3_reset_auto_extension()
sqlite3_backup_finish()
Завершить онлайн-резервное копирование [EXP]
Определение
int sqlite3_backup_finish( sqlite3_backup* backup );
backup
Дескриптор онлайн-резервного копирования.
Возвращает
Код результата SQLite. Значение SQLITE_OK будет возвращено, даже если резервное копирование не было завершено.
Описание
Эта функция освобождает дескриптор оперативного резервного копирования. Дескриптор резервного копирования будет выпущен без ошибок, даже если sqlite3_backup_step() никогда не вернул SQLITE_DONE.
См. также
sqlite3_backup_init()
sqlite3_backup_init()
Запустить оперативное резервное копирование [EXP]
Определение
sqlite3_backup* sqlite3_backup_init(
          sqlite3* db_dst, const char* db_name_dst,
          sqlite3* db_src, const char* db_name_src );
db_dst
Целевое соединение с базой данных.
db_name_dst
Имя целевой логической базы данных в UTF-8. Это может быть main, temp или имя, присвоенное ATTACH DATABASE.
db_src
Соединение с исходной базой данных. Должен отличаться от db_dst.
db_name_src
Имя исходной логической базы данных в UTF-8.
Возвращает
Дескриптор онлайн-резервного копирования.
Описание

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

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

Для выполнения резервного копирования в оперативном режиме дескриптор резервного копирования создается с помощью sqlite3_backup_init(). Приложение продолжает вызывать sqlite3_backup_step() для передачи данных, обычно делая паузу между вызовами на короткое время. Наконец, вызывается sqlite3_backup_finish(), чтобы освободить дескриптор резервного копирования.

Для получения дополнительной информации об использовании API резервного копирования в Интернете см. http://www.sqlite.org/backup.html.

См. также
sqlite3_backup_finish(), sqlite3_backup_step()
sqlite3_backup_pagecount()
Получить количество страниц в исходной базе данных [EXP]
Определение
int sqlite3_backup_pagecount( sqlite3_backup* backup );
backup
Дескриптор онлайн-резервного копирования.
Возвращает
Количество страниц в исходной базе данных.
Описание
Эта функция получает общее количество страниц в исходной базе данных. Это значение обновляется при вызове sqlite3_backup_step() и может не отражать недавнюю активность в исходной базе данных.
См. также
sqlite3_backup_remaining(), sqlite3_backup_step()
sqlite3_backup_remaining()
Получить количество страниц, оставшихся в резервной копии [EXP]
Определение
int sqlite3_backup_remaining( sqlite3_backup* backup );
backup
Дескриптор онлайн-резервного копирования.
Возвращает
Количество страниц, оставшихся в процессе резервного копирования.
Описание
Эта функция используется для получения количества страниц, для которых еще требуется резервное копирование. Это значение обновляется при вызове sqlite3_backup_step() и может не отражать недавнюю активность в исходной базе данных.
См. также
sqlite3_backup_pagecount(), sqlite3_backup_step()
sqlite3_backup_step()
Продолжить оперативное резервное копирование [EXP]
Определение
int sqlite3_backup_step( sqlite3_backup* backup, int pages );
backup
Дескриптор онлайн-резервного копирования.
pages
Количество копируемых страниц. Если это значение отрицательное, копируются все оставшиеся страницы.
Возвращает
Код результата SQLite. SQLITE_OK указывает, что страницы были успешно скопированы, но осталось больше страниц. SQLITE_DONE указывает, что была сделана полная резервная копия. Если sqlite3_backup_step() возвращает SQLITE_BUSY или SQLITE_LOCKED, функцию можно безопасно повторить позже.
Описание

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

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

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

См. также
sqlite3_backup_init()
sqlite3_bind_xxx()
Привязать значения к параметрам оператора
Определение
int sqlite3_bind_blob(     sqlite3_stmt* stmt, int pidx, const void* val, int bytes, mem_callback );
int sqlite3_bind_double(   sqlite3_stmt* stmt, int pidx, double val );
int sqlite3_bind_int(      sqlite3_stmt* stmt, int pidx, int val );
int sqlite3_bind_int64(    sqlite3_stmt* stmt, int pidx, sqlite3_int64 val );
int sqlite3_bind_null(     sqlite3_stmt* stmt, int pidx );
int sqlite3_bind_text(     sqlite3_stmt* stmt, int pidx, const char* val, int bytes, mem_callback );
int sqlite3_bind_text16(   sqlite3_stmt* stmt, int pidx, const void* val, int bytes, mem_callback );
int sqlite3_bind_value(    sqlite3_stm t* stmt, int pidx, const sqlite3_value* val );
int sqlite3_bind_zeroblob( sqlite3_stmt* stmt, int pidx, int bytes );

void mem_callback( void* ptr );
stmt
Подготовленный оператор, содержащий значения параметров.
pidx
Параметр index. Первый параметр имеет индекс, равный единице (1).
val
Значение данных для связывания байтов Размер значения данных в байтах (не в символах). Обычно длина не включает нулевой терминатор. Если val является строкой с завершающим нулем и это значение отрицательное, длина будет вычисляться автоматически.
bytes
Дескриптор онлайн-резервного копирования.
mem_callback

Указатель функции на функцию освобождения памяти. Эта функция освобождает буфер памяти, используемый для хранения значения. Если буфер был выделен с помощью sqlite3_malloc(), ссылку на sqlite3_free() можно передать напрямую.

Также можно использовать специальные флаги SQLITE_STATIC и SQLITE_TRANSIENT. SQLITE_STATIC указывает, что приложение будет сохранять память значений действительной до тех пор, пока оператор не будет завершен (или не будет привязано новое значение). SQLITE_TRANSIENT заставит SQLite создать внутреннюю копию буфера значений, которая будет автоматически освобождена, когда она больше не понадобится.

Возвращает (sqlite3_bind_xxx())
Код ответа SQLite. Код SQLITE_RANGE будет возвращен, если индекс параметра недействителен.
Описание

Это семейство функций используется для привязки значения данных к параметру оператора. Связанные значения остаются на месте до тех пор, пока оператор не будет завершен (с помощью sqlite3_finalize()) или пока новое значение не будет привязано к тому же индексу параметра. Сброс оператора с помощью sqlite3_reset() не очищает привязки.

Функция sqlite3_bind_zeroblob() связывает объект BLOB заданной длины. Все байты BLOB устанавливаются в ноль. BLOB фактически не создается в памяти, что позволяет системе связывать очень большие значения BLOB. Эти большие двоичные объекты можно изменить с помощью функций sqlite3_blob_xxx().

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

См. также
sqlite3_column_xxx(), sqlite3_result_xxx(), sqlite3_value_xxx()
sqlite3_bind_parameter_count()
Получить количество параметров оператора
Определение
int sqlite3_bind_parameter_count( sqlite3_stmt* stmt );
stmt
Подготовленный оператор, содержащий значения параметров.
Возвращает
Количество допустимых параметров в этом операторе.
Описание
Эта функция возвращает самый большой допустимый индекс параметра оператора в данном операторе. Если эта функция возвращает 6, допустимые индексы параметров включают от 1 до 6 включительно.
См. также
sqlite3_bind_xxx()
sqlite3_bind_parameter_index()
Получить индекс именованного параметра оператора
Определение
int sqlite3_bind_parameter_index( sqlite3_stmt* stmt, const char *name );
stmt
Подготовленный оператор, содержащий значения параметров.
name
Имя параметра, включая символ префикса. Имя должно быть указано в кодировке UTF-8.
Возвращает
Индекс названного параметра. Если имя не найдено, возвращается ноль.
Описание
Эта функция находит значение индекса указанного параметра оператора. Возвращенное значение можно безопасно передать в функцию sqlite3_bind_xxx() (хотя эти функции могут возвращать SQLITE_RANGE, если имя не найдено).
См. также
sqlite3_bind_xxx(), sqlite3_bind_parameter_name()
sqlite3_bind_parameter_name()
Получить имя параметра инструкции
Определение
const char* sqlite3_bind_parameter_name( sqlite3_stmt* stmt, int pidx );
stmt
Подготовленный оператор, содержащий значения параметров.
pidx
Параметр index. Первый параметр имеет индекс, равный единице (1).
Возвращает
Текстовое представление параметра оператора с заданным индексом.
Описание
Эта функция ищет исходное строковое представление параметра оператора. Это наиболее часто используется с именованными параметрами, но правильно работает для любого явного типа параметра. Строка будет закодирована в UTF-8 и будет включать префиксный символ (например, параметр: val вернет: val, а не val). Возвращаемое значение будет NULL, если индекс параметра недействителен или если параметр является автоматическим параметром (просто ?).
См. также
sqlite3_bind_xxx(), sqlite3_bind_parameter_index()
sqlite3_blob_bytes()
Получение размера значения дескриптора BLOB
Определение
int sqlite3_blob_bytes( sqlite3_blob* blob );
blob
Дескриптор BLOB, полученный от sqlite3_blob_open().
Возвращает
Размер значения BLOB в байтах.
Описание
Эта функция возвращает размер указанного значения BLOB в байтах. Размер значения BLOB фиксирован и не может быть изменен без вызова INSERT или UPDATE.
См. также
sqlite3_blob_open(), sqlite3_bind_xxx() (refer to sqlite3_bind_zeroblob(), specifically), zeroblob() [Ap E]
sqlite3_blob_close()
Закрытие дескриптора BLOB
Определение
int sqlite3_blob_close( sqlite3_blob* blob );
blob
Дескриптор большого двоичного объекта, полученный от sqlite3_blob_open(). Передавать значение NULL безопасно.
Возвращает
Код ответа SQLite.
Описание
Эта функция закрывает и освобождает дескриптор большого двоичного объекта, полученный от sqlite3_blob_open(). Дескриптор BLOB всегда закрывается (и становится недействительным), даже если возвращается код ошибки.
См. также
sqlite3_blob_open()
sqlite3_blob_open()
Создание и открытие дескриптора BLOB
Определение
int sqlite3_blob_open( sqlite3* db,
        const char* db_name, const char* tbl_name, const char* col_name,
        sqlite3_int64 row_id, int flags, sqlite3_blob** blob );
db
Подключение к базе данных.
db_name
Логическое имя базы данных в UTF-8. Это может быть main, temp, или имя, присвоенное ATTACH DATABASE.
tbl_name
Имя таблицы в UTF-8..
col_name
Имя столбца в UTF-8.
row_id
Значение ROWID.
flags
Ненулевое значение откроет чтение/запись большого двоичного объекта. Нулевое значение откроет большой двоичный объект только для чтения.
blob
Ссылка на дескриптор большого двоичного объекта. По этой ссылке будет возвращен новый дескриптор большого двоичного объекта. Дескриптор BLOB может быть установлен в NULL, если возвращается ошибка.
Возвращает
Код ответа SQLite.
Описание

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

SELECT col_name FROM db_name.tbl_name WHERE ROWID = row_id;

Дескриптор BLOB остается действительным, пока он не будет закрыт или пока не истечет срок его действия. Дескриптор BLOB истекает, когда любой столбец строки, содержащей BLOB, изменяется каким-либо образом (обычно с помощью UPDATE или DELETE). Дескриптор большого двоичного объекта с истекшим сроком действия должен быть закрыт.

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

См. также
sqlite3_blob_read(), sqlite3_blob_write(), sqlite3_blob_close()
sqlite3_blob_read()
Чтение данных из BLOB
Определение
int sqlite3_blob_read( sqlite3_blob* blob, void* buff, int bytes, int offset );
blob
Дескриптор большого двоичного объекта, полученный от sqlite3_blob_open().
buff
Буфер данных. Данные считываются из большого двоичного объекта в буфер.
bytes
Число байтов, считываемых из большого двоичного объекта в буфер.
offset
Смещение от начала BLOB, где должно начинаться чтение.
Возвращает
Код возврата SQLite. Попытка чтения из истекшего дескриптора BLOB вернет SQLITE_ABORT.
Описание
Эта функция считывает указанное количество байтов из значения BLOB в заданный буфер. Чтение начнется с указанного смещения. Любая попытка чтения после конца большого двоичного объекта приводит к ошибке, что означает, что большой двоичный объект должен иметь байты + смещение или более байтов.
См. также
sqlite3_blob_bytes(), sqlite3_blob_open()
sqlite3_blob_write()
Запись данных в BLOB
Определение
int sqlite3_blob_write( sqlite3_blob* blob, void* buff, int bytes, int offset );
blob
Дескриптор большого двоичного объекта, полученный от sqlite3_blob_open().
buff
Буфер данных. Данные записываются из буфера в большой двоичный объект.
bytes
Количество байтов для записи из буфера в большой двоичный объект.
offset
Смещение от начала BLOB, где должна начинаться запись.
Возвращает
Код возврата SQLite. Попытка записи в дескриптор BLOB с истекшим сроком действия вернет SQLITE_ABORT.
Описание
Эта функция записывает указанное количество байтов из заданного буфера в значение BLOB. Запись начинается с указанного смещения. Любая попытка записи после конца большого двоичного объекта приводит к ошибке, что означает, что большой двоичный объект должен иметь байты + смещение или более байтов.
См. также
sqlite3_blob_bytes(), sqlite3_blob_open()
sqlite3_busy_handler()
Регистрация обработчика занятости
Определение
int sqlite3_busy_handler( sqlite3* db, busy_handler, void* udp );

int busy_handler( void* udp, int count );
db
Подключение к базе данных.
busy_handler
Указатель функции на функцию обработчика занятости приложения.
udp
Указатель пользовательских данных, определяемый приложением. Это значение предоставляется обработчику занятости.
count
Сколько раз обработчик был вызван для этой блокировки.
Возвращает (sqlite3_busy_handler())
Код результата SQLite.
Возвращает (busy_handler())
Ненулевой код возврата указывает, что соединение должно продолжать ожидать желаемой блокировки. Нулевой код возврата указывает, что соединение с базой данных должно разорваться и вернуть приложению SQLITE_BUSY или SQLITE_IOERR_BLOCKED.
Описание

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

Полное обсуждение состояний блокировки и обработчиков занятости см. В разделе «Блокировка базы данных».

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

См. также
sqlite3_busy_timeout()
sqlite3_busy_timeout()
Установка тайм-аута занятости
Определение
int sqlite3_busy_timeout( sqlite3* db, int ms );
db
Подключение к базе данных.
ms
Общая продолжительность тайм-аута в миллисекундах (тысячных долях секунды).
Возвращает
Код результата SQLite.
Описание
Эта функция регистрирует внутренний обработчик занятости, который продолжает попытки получить блокировку занятости до тех пор, пока не пройдет общее указанное время. Поскольку эта функция регистрирует внутренний обработчик занятости, любой текущий обработчик занятости удаляется. Значение тайм-аута можно явно удалить, установив нулевое значение тайм-аута.
См. также
sqlite3_busy_handler()
sqlite3_changes()
Получить количество изменений, сделанных оператором SQL
Определение
int sqlite3_changes( sqlite3* db );
db
Подключение к базе данных.
Возвращает
Число строк, измененных последним выполненным оператором SQL.
Описание

Эта функция возвращает количество строк, измененных последним оператором INSERT, UPDATE или DELETE. Учитываются только строки, непосредственно затронутые таблицей, указанной в команде SQL. Изменения, внесенные триггерами, действиями внешнего ключа или разрешениями конфликтов, не учитываются.

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

Эта функция предоставляется среде SQL по мере изменения функции SQL().

См. также
sqlite3_total_changes(), count_changes [PRAGMA, Ap F], changes() [SQL Func, Ap E]
sqlite3_clear_bindings()
Сбросить все параметры оператора в NULL
Определение
int sqlite3_clear_bindings( sqlite3_stmt* stmt );
stmt
Подготовленный оператор.
Возвращает
Код результата SQLite.
Описание
Эта функция сбрасывает все параметры оператора в NULL. Это эквивалентно вызову sqlite3_bind_null() для каждого допустимого индекса параметра. Многие приложения вызывают эту функцию каждый раз при вызове sqlite3_reset(), чтобы предотвратить утечку значений параметров от одного выполнения к другому.
См. также
sqlite3_bind_xxx(), sqlite3_reset()
sqlite3_close()
Закрыть соединение с базой данных
Определение
int sqlite3_close( sqlite3* db );
db
Подключение к базе данных. В эту функцию можно безопасно передать NULL (что приведет к бездействию).
Возвращает
Код результата SQLite.
Описание

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

Если приложению не удается завершить подготовленный оператор, закрытие завершится ошибкой и будет возвращен SQLITE_BUSY. Оператор необходимо завершить и снова вызвать sqlite3_close().

См. также
sqlite3_open(), sqlite3_finalize(), sqlite3_close(), sqlite3_next_stmt()
sqlite3_collation_needed()
Зарегистрируйте загрузчик сопоставления
Определение
int sqlite3_collation_needed(   sqlite3* db, void* udp, col_callback );
int sqlite3_collation_needed16( sqlite3* db, void* udp, col_callback16 );
void col_callback(   void* udp, sqlite3* db, int text_rep, const char* name );
void col_callback16( void* udp, sqlite3* db, int text_rep, const void* name );
db
Подключение к базе данных.
upd
Указатель пользовательских данных, определяемый приложением. Это значение становится доступным для обратного вызова загрузчика сопоставления.
col_callback, col_callback16
Указатель функции на определяемую приложением функцию загрузчика сопоставления.
text_rep
Желаемое текстовое представление для запрошенного сопоставления. Это значение может быть одним из SQLITE_UTF8, SQLITE_UTF16BE или SQLITE_UTF16LE.
name
Имя сопоставления в UTF-8 или UTF-16 (собственный порядок).
Возвращает (sqlite3_collation_needed [16]())
Код результата SQLite.
Описание

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

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

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

См. также
sqlite3_create_collation()
sqlite3_column_xxx()
Получение значения столбца результатов
Определение
const void*          sqlite3_column_blob(   sqlite3_stmt* stmt, int cidx );
double               sqlite3_column_double( sqlite3_stmt* stmt, int cidx );
int                  sqlite3_column_int(    sqlite3_stmt* stmt, int cidx );
sqlite3_int64        sqlite3_column_int64(  sqlite3_stmt* stmt, int cidx );
const unsigned char* sqlite3_column_text(   sqlite3_stmt* stmt, int cidx );
const void*          sqlite3_column_text16( sqlite3_stmt* stmt, int cidx );
sqlite3_value*       sqlite3_column_value(  sqlite3_stmt* stmt, int cidx );
stmt
Подготовленный и выполненный оператор.
cidx
Индекс столбца. Первый столбец имеет нулевой индекс (0).
Возвращает
Запрошенное значение.
Описание

Эти функции извлекают значения из подготовленного оператора. Значения доступны в любое время, когда sqlite3_step() возвращает SQLITE_ROW. Если запрошенный тип отличается от фактического базового значения, значение будет преобразовано с использованием правил преобразования, определенных в таблице 7-1.

SQLite позаботится обо всем управлении памятью для буферов, возвращаемых этими функциями. Возвращенные указатели могут стать недействительными при следующем вызове sqlite3_step(), sqlite3_reset(), sqlite3_finalize() или любого вызова sqlite3_column_xxx() для того же индекса столбца. Указатели также могут стать недействительными из-за вызова одной из функций sqlite3_column_bytes().

Имейте в виду, что sqlite3_column_int() будет обрезать любые целочисленные значения до 32 бит. Если база данных содержит значения, которые не могут быть представлены 32-разрядным целым числом со знаком, безопаснее использовать sqlite3_column_int64(). Буфер, возвращаемый sqlite3_column_text() и sqlite3_col umn_text16(), всегда будет оканчиваться нулем.

Структура, возвращаемая функцией sqlite3_column_value(), является незащищенным объектом sqlite3_value. Этот объект можно использовать только вместе с sqlite3_bind_value() или sqlite3_result_value(). Вызов любой из функций sqlite3_value_xxx() приведет к неопределенному поведению.

См. также
sqlite3_column_count() [C API, Ap G], sqlite3_column_bytes() [C API, Ap G], sqlite3_column_type() [C API, Ap G], sqlite3_value_xxx() [C API, Ap G]
sqlite3_column_bytes()
Получить размер буфера столбца
Определение
int sqlite3_column_bytes(   sqlite3_stmt* stmt, int cidx );
int sqlite3_column_bytes16( sqlite3_stmt* stmt, int cidx );
stmt
Подготовленный и выполненный оператор.
cidx
Индекс столбца. Первый столбец имеет нулевой индекс (0).
Возвращает
Количество байтов в значении столбца.
Описание

Эти функции возвращают количество байтов в текстовом значении или значении BLOB. Вызов этих функций может вызвать преобразование типа (аннулирование буферов, возвращаемых sqlite3_column_xxx()), поэтому необходимо позаботиться о том, чтобы вызывать их вместе с соответствующей функцией sqlite3_column_xxx().

Чтобы избежать проблем, приложение должно сначала извлечь нужный тип с помощью функции sqlite3_col umn_xxx(), а затем вызвать соответствующую функцию sqlite3_column_bytes(). За функциями sqlite3_column_text() и sqlite3_column_blob() должен следовать вызов sqlite3_column_bytes(), а за любым вызовом sqlite3_column_text16() должен следовать вызов sqlite3_column_bytes16().

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

См. также
sqlite3_column_xxx()
sqlite3_column_count()
Получить количество столбцов результата в операторе
Определение
int sqlite3_column_count( sqlite3_stmt* stmt );
stmt
Подготовленный оператор.
Возвращает
Количество столбцов, возвращаемых подготовленным оператором.
Описание
Эта функция возвращает количество столбцов, доступных в строке результата. Индексы столбцов начинаются с нуля (0), поэтому, если эта функция возвращает 5, индексы столбцов значений от 0 до 4. Значение ноль (0) возвращается, если инструкция не возвращает никакого результата.
См. также
sqlite3_column_xxx(), sqlite3_step()
sqlite3_column_database_name()
Получить имя исходной базы данных столбца результата
Определение
const char* sqlite3_column_database_name(   sqlite3_stmt* stmt, int cidx );
const void* sqlite3_column_database_name16( sqlite3_stmt* stmt, int cidx );
stmt
Подготовленный оператор.
cidx
Индекс столбца. Первый столбец имеет нулевой индекс (0).
Возвращает
Логическое имя исходной базы данных для данного столбца результатов.
Описание

Эти функции возвращают несимметричное логическое имя исходной базы данных, которая связана со столбцом результата SELECT. Возвращенные указатели будут оставаться действительными до тех пор, пока sqlite3_finalize() не будет вызван для оператора, или до тех пор, пока одна из этих функций не будет вызвана с тем же индексом столбца. SQLite позаботится обо всем управлении памятью для буферов, возвращаемых этими функциями.

Данные доступны только для столбцов результатов, которые получены непосредственно из ссылки на столбец. Если столбец результата определен как выражение или подзапрос, будет возвращено NULL.

Эти функции доступны только в том случае, если библиотека SQLite была скомпилирована с параметром сборки SQLITE_ENA BLE_COLUMN_METADATA.

См. также
sqlite3_column_table_name(), sqlite3_column_origin_name(), SQLITE_ENABLE_COLUMN_METADATA [Build Opt, Ap A]
sqlite3_column_decltype()
Получить имя исходной базы данных столбца результата
Определение
const char* sqlite3_column_decltype(   sqlite3_stmt* stmt, int cidx );
const void* sqlite3_column_decltype16( sqlite3_stmt* stmt, int cidx );
stmt
Подготовленный оператор.
cidx
Индекс столбца. Первый столбец имеет нулевой индекс (0).
Возвращает
Определенное имя типа для данного столбца результатов.
Описание

Эти функции возвращают объявленный тип, связанный с исходным столбцом. Это тип, который был указан в команде CREATE TABLE, используемой для определения столбца исходной таблицы. Возвращенные указатели будут оставаться действительными до тех пор, пока для оператора не будет вызвана sqlite3_finalize() или пока одна из этих функций не будет вызвана с тем же индексом столбца. SQLite позаботится обо всем управлении памятью для буферов, возвращаемых этими функциями.

Данные доступны только для столбцов результатов, которые получены непосредственно из ссылки на столбец. Если столбец результата определен как выражение или подзапрос, будет возвращено NULL.

См. также
sqlite3_column_type(), sqlite3_column_table_name()
sqlite3_column_name()
Получить имя исходной базы данных столбца результата
Определение
const char* sqlite3_column_name(   sqlite3_stmt* stmt, int cidx );
const void* sqlite3_column_name16( sqlite3_stmt* stmt, int cidx );
stmt
Подготовленный оператор.
cidx
Индекс столбца. Первый столбец имеет нулевой индекс (0).
Возвращает
Имя столбца результатов.
Описание

Эти функции возвращают имя столбца результатов. Это имя, указанное в предложении AS заголовка выбора. Если предложение AS не было предоставлено, имя будет производным от выражения, используемого для определения заголовка SELECT. Формат производных имен не определен стандартом SQL и может изменяться от одного выпуска SQLite к другому. Если приложение зависит от явных имен столбцов, оно всегда должно использовать предложения AS в заголовке select.

Возвращенные указатели будут оставаться действительными до тех пор, пока для оператора не будет вызвана sqlite3_finalize() или пока одна из этих функций не будет вызвана с тем же индексом столбца. SQLite позаботится обо всем управлении памятью для буферов, возвращаемых этими функциями.

См. также
SELECT [SQL Cmd, Ap C], full_column_names [PRAGMA, Ap F], short_column_names [PRAGMA, Ap F]
sqlite3_column_origin_name()
Получить имя исходного столбца столбца результата
Определение
const char* sqlite3_column_origin_name(   sqlite3_stmt* stmt, int cidx );
const void* sqlite3_column_origin_name16( sqlite3_stmt* stmt, int cidx );
stmt
Подготовленный оператор.
cidx
Индекс столбца. Первый столбец имеет нулевой индекс (0).
Возвращает
Имя исходного столбца для данного столбца результатов.
Описание

Эти функции возвращают имя исходного столбца, связанного со столбцом результата SELECT. Возвращенные указатели будут оставаться действительными до тех пор, пока для оператора не будет вызвана sqlite3_finalize() или пока одна из этих функций не будет вызвана с тем же индексом столбца. SQLite позаботится обо всем управлении памятью для буферов, возвращаемых этими функциями.

Данные доступны только для столбцов результатов, которые получены непосредственно из ссылки на столбец. Если столбец результата определен как выражение или подзапрос, будет возвращено NULL.

Эти функции доступны только в том случае, если библиотека SQLite была скомпилирована с параметром сборки SQLITE_ENA BLE_COLUMN_METADATA.

См. также
sqlite3_column_name() [C API, Ap G], sqlite3_column_table_name() [C API, Ap G], sqlite3_column_database_name() [C API, Ap G], SQLITE_ENABLE_COLUMN_METADATA [Build Opt, Ap A]
sqlite3_column_table_name()
Получить имя исходной таблицы для столбца результата
Определение
const char* sqlite3_column_table_name(   sqlite3_stmt* stmt, int cidx );
const void* sqlite3_column_table_name16( sqlite3_stmt* stmt, int cidx );
stmt
Подготовленный оператор.
cidx
Индекс столбца. Первый столбец имеет нулевой индекс (0).
Возвращает
Имя исходной таблицы для данного столбца результатов.
Описание

Эти функции возвращают имя исходной таблицы, связанной со столбцом результата SELECT. Возвращенные указатели будут оставаться действительными до тех пор, пока sqlite3_finalize() не будет вызван для sqlite3_column_table_name() или до тех пор, пока одна из этих функций не будет вызвана с тем же индексом столбца. SQLite позаботится обо всем управлении памятью для буферов, возвращаемых этими функциями.

Данные доступны только для столбцов результатов, которые получены непосредственно из ссылки на столбец. Если столбец результата определен как выражение или подзапрос, будет возвращено NULL.

Эти функции доступны только в том случае, если библиотека SQLite была скомпилирована с параметром сборки SQLITE_ENA BLE_COLUMN_METADATA.

См. также
sqlite3_column_database_name() [C API, Ap G], sqlite3_column_origin_name() [C API, Ap G], SQLITE_ENABLE_COLUMN_METADATA [Build Opt, Ap A]
sqlite3_column_type()
Получить тип данных для столбца результата
Определение
int sqlite3_column_type( sqlite3_stmt* stmt, int cidx );
stmt
Подготовленный и выполненный оператор.
cidx
Индекс столбца. Первый столбец имеет нулевой индекс (0).
Возвращает
Собственный код типа данных значения результата.
Описание

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

Возвращаемое значение будет SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB или SQLITE_NULL.

См. также
sqlite3_column_xxx(), sqlite3_step()
sqlite3_commit_hook()
Регистрация обратного вызова фиксации
Определение
void* sqlite3_commit_hook( sqlite3* db, commit_callback, void* udp );

int commit_callback( void* udp );
db
Подключение к базе данных.
commit_callback
Указатель функции на определяемую приложением функцию обратного вызова фиксации.
udp
Указатель пользовательских данных, определяемый приложением. Это значение становится доступным для обратного вызова фиксации.
Возвращает (sqlite3_commit_hook())
Предыдущий указатель пользовательских данных, если применимо.
Возвращает (commit_callback())
Если не равно нулю, фиксация преобразуется в обратный вызов.
Описание

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

Обратный вызов фиксации не должен использовать связанное соединение с базой данных для изменения каких-либо баз данных, а также не может вызывать sqlite3_prepare_v2() или sqlite3_step(). Если обратный вызов фиксации возвращает ненулевое значение, фиксация будет отменена, а транзакция будет отменена.

См. также
sqlite3_rollback_hook(), sqlite3_update_hook()
sqlite3_compileoption_get()
Итерация по определенным параметрам компиляции
Определение
const char* sqlite3_compileoption_get( int iter );
iter
Значение индекса итератора.
Возвращает
Строку в кодировке UTF-8 с именем параметра и необязательным значением.
Описание

Эта функция выполняет итерацию по всем параметрам сборки, используемым для компиляции этого экземпляра библиотеки SQLite. Начиная с нулевого значения итератора (0) и вызывая эту функцию снова и снова с постоянно увеличивающимся значением итератора, будут возвращены все определенные значения. Если значение итератора выходит за пределы допустимого диапазона, возвращается NULL-указатель.

Будут включены только те параметры сборки, которые были фактически предоставлены. Те, которые приняли значения по умолчанию, не будут предоставлены. Как правило, в список включены только директивы ENABLE, DISABLE, OMIT и несколько других. Директивы DEFAULT и MAX, управляющие пределами или значениями по умолчанию, не включены. Большинство этих значений можно запросить с помощью sqlite3_limit().

Если параметр сборки является простым логическим значением, будет включено только имя параметра. Если для параметра требуется фактическое значение, параметр будет указан как имя = значение. В обоих случаях префикс SQLITE_ не будет включен.

Эта функция предоставляется среде SQL как SQL-функция sqlite_compile option_get(). Обратите внимание, что в названии функции SQL нет 3.

См. также
sqlite3_compileoption_used(), sqlite3_compileoption_get(), sqlite_compileoption_get() [SQL Func, Ap E]
sqlite3_compileoption_used()
Получить значение параметра компиляции
Определение
int sqlite3_compileoption_used( const char* name );
name
Строка в кодировке UTF-8 с именем параметра и, необязательно значение.
Возвращает
Логическое значение, указывающее, использовался ли указанный параметр компиляции и, при необходимости, значение.
Описание

Эта функция запрашивает, использовалась ли конкретная опция компиляции (и, возможно, значение) для компиляции этого экземпляра библиотеки SQLite. Эта функция знает те же параметры сборки, которые возвращает sqlite3_compileoption_get(). Функция будет работать с префиксом SQLITE_ или без него.

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

Эта функция представлена в среде SQL как функция SQL sqlite_compile option_used(). Обратите внимание, что в названии функции SQL нет 3.

См. также
sqlite3_compileoption_get(), sqlite_compileoption_used() [SQL Func, Ap E]
sqlite3_complete()
Определяет, завершен ли оператор SQL
Определение
int sqlite3_complete(   const char* sql );
int sqlite3_complete16( const void* sql );
sql
Оператор SQL.
Возвращает
Ноль (0), если оператор является неполным, SQLITE_NOMEM, если не удалось выделить память, или ненулевое значение, указывающее, что оператор завершен.
Описание

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

Перед использованием этой функции необходимо вызвать функцию sqlite3_initialize(). Если библиотека SQLite не была инициализирована, эта функция сделает это автоматически. Это может привести к появлению дополнительных ненулевых кодов результата, если инициализация не удалась.

См. также
sqlite3_prepare_xxx()
sqlite3_config()
Определяет, завершен ли оператор SQL
Определение
int sqlite3_config( int option, ... );
option
Параметр конфигурации, который необходимо изменить.
Дополнительные параметры
Дополнительные параметры определяются данной опцией конфигурации.
Возвращает
Код результата SQLite.
Описание

Эта функция настраивает библиотеку SQLite. Он должен быть вызван до того, как библиотека SQLite завершит процесс инициализации (либо до возврата sqlite3_initialize(), либо после вызова sqlite3_shut down()). Количество дополнительных параметров и их типы определяется конкретным вариантом конфигурации.

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

Для получения дополнительной информации о поддерживаемых в настоящее время параметрах конфигурации см. http://www.sqlite.org/c3ref/c_config_getmalloc.html.

См. также
sqlite3_initialize(), sqlite3_db_config(), sqlite3_limit()
sqlite3_context_db_handle()
Получить соединение с базой данных из контекста функции
Определение
sqlite3* sqlite3_context_db_handle( sqlite3_context* ctx );
ctx
Контекст функции SQL, предоставляемый библиотекой SQLite.
Возвращает
Соединение с базой данных, связанное с этим контекстом.
Описание
Эта функция возвращает соединение с базой данных, связанное с контекстом функции SQL. Это вызывается изнутри функции C, которая реализует пользовательскую функцию SQL или агрегат SQL.
См. также
sqlite3_create_function()
sqlite3_create_collation()
Регистрация пользовательской реализации сопоставления
Определение
int sqlite3_create_collation(    sqlite3* db, const char* name, int text_rep,
                                 void* udp, comp_func );
int sqlite3_create_collation16(  sqlite3* db, const void* name, int text_rep,
                                 void* udp, comp_func );
int sqlite3_create_collation_v2( sqlite3* db, const char* name, int text_rep,
                                 void* udp, comp_func, dest_func );
int comp_func( void* udp, int sizeA, const void* textA,
                          int sizeB, const void* textB );
void dest_func( void* udp );
                
db
Подключение к базе данных.
name
Имя сопоставления в UTF-8 или UTF-16, в зависимости от используемой функции.
text_rep
Текстовое представление, ожидаемое функцией сравнения. Это значение может быть одним из SQLITE_UTF8, SQLITE_UTF16 (собственный порядок), SQLITE_UTF16BE, SQLITE_UTF16LE или SQLITE_UTF16_ALIGNED (собственный порядок, с выравниванием по 16 битам).
udp
Указатель пользовательских данных, определяемый приложением. Это значение доступно как для функции сравнения сопоставления, так и для функции уничтожения сопоставления.
comp_func
Указатель функции на определяемую приложением пользовательскую функцию сравнения параметров сортировки.
dest_func
Необязательный указатель функции на определяемую приложением функцию уничтожения сопоставления. Это может быть NULL.
sizeA, sizeB
Длина параметров textA и textB, соответственно, в байтах. textA, textB Буферы данных, содержащие текстовые значения в запрошенном представлении. Они не могут оканчиваться нулем.
Возвращает (sqlite3_create_collation[16][_v2]())
Код результата SQLite.
Возвращает (comp_func())
Результаты сравнения. Отрицательный для A < B, ноль для A = B, положительный для A > B.
Описание

Эти функции определяют настраиваемую реализацию сопоставления. Это делается путем регистрации функции сравнения под заданным именем сопоставления и ожидаемым текстовым представлением.

Единственное различие между sqlite3_create_collation() и sqlite3_create_collation16() - это кодировка текста, используемая для определения имени сортировки (второй параметр). Кодировка, используемая при вызове функции сравнения, определяется третьим параметром. Любая функция может использоваться для регистрации функции сравнения, которая понимает любую из заданных кодировок.

Единственная разница между sqlite3_create_collation() и sqlite3_create_collation_v2() - это добавление функции обратного вызова destroy.

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

Функция сравнения должна возвращать отрицательное значение, если testA меньше textB, нулевое значение, если testA и textB равны, или положительное значение, если testA больше, чем textB. Концептуально возвращаемое значение представляет собой A минус B.

Функция уничтожения вызывается каждый раз, когда параметры сортировки перезаписываются или когда соединение с базой данных закрывается. В функцию уничтожения будет передан указатель пользовательских данных, и ей будет предоставлена возможность освободить любую соответствующую память. Если возможно, ссылку на sqlite3_free() можно передать непосредственно в sqlite3_create_collation_v2().

Для получения дополнительной информации о написании пользовательских параметров сортировки см. «Функции сопоставления».

См. также
sqlite3_create_function(), sqlite3_collation_needed()
sqlite3_create_function()
Определение скалярной или агрегированной функции SQL
Определение
int sqlite3_create_function(   sqlite3* db, const char* name, int num_param,
        int text_rep, void* udp, func_func, step_func, final_func );
int sqlite3_create_function16( sqlite3* db, const void* name, int num_param,
        int text_rep, void* udp, func_func, step_func, final_func );
void func_func(  sqlite3_context* ctx, int argc, sqlite3_value** argv );
void step_func(  sqlite3_context* ctx, int argc, sqlite3_value** argv );
void final_func( sqlite3_context* ctx );
db
Подключение к базе данных.
name
Имя сопоставления в UTF-8 или UTF-16, в зависимости от используемой функции.
num_param
Количество ожидаемых параметров в функции SQL. Если значение -1, будет принято любое количество параметров.
text_rep
Текстовое представление, наиболее подходящее для функции (ей). Это значение может быть одним из SQLITE_UTF8, SQLITE_UTF16 (собственный порядок), SQLITE_UTF16BE, SQLITE_UTF16LE или SQLITE_ANY.
upd
Указатель пользовательских данных, определяемый приложением. Это значение можно извлечь из параметра ctx с помощью функции sqlite3_user_data().
func_func
Указатель функции на реализацию определяемой приложением скалярной функции SQL. Если это не NULL, параметры step_func и final_func должны быть NULL.
step_func
Указатель функции на реализацию функции агрегированного шага, определяемой приложением. Если это не NULL, параметр func_func должен иметь значение NULL, а параметр final_func не должен быть NULL.
final_func
Указатель функции на определяемую приложением реализацию функции завершения агрегата. Если это не NULL, параметр func_func должен иметь значение NULL, а параметр step_func не должен быть NULL.
ctx
Контекст функции SQL, предоставляемый библиотекой SQLite.
argc
Число параметров, переданных функции SQL.
argv
Значения параметров, переданные в функцию SQL.
Возвращает (sqlite3_create_function[16]())
Код результата SQLite.
Описание

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

Единственная разница между sqlite3_create_function() и sqlite3_create_function16() - это кодировка текста, используемая для определения имени функции (второй параметр). Кодировка, используемая при вызове зарегистрированных функций, определяется четвертым параметром. Любая функция может использоваться для регистрации функций, которые понимают любую из заданных кодировок.

Один вызов одной из этих функций может использоваться для определения либо скалярной функции SQL, либо агрегатной функции SQL, но не обоих одновременно. Скалярная функция определяется путем предоставления допустимого параметра func_func и установки для step_func и final_func значения NULL. И наоборот, агрегатная функция определяется путем предоставления действительных step_func и final_func при установке для func_func значения NULL.

Предоставляя разные значения для параметров num_param или text_rep (или обоих), разные функции могут быть зарегистрированы под одним и тем же именем функции SQL. SQLite выберет наиболее подходящий вариант, сначала по номеру параметра, а затем по текстовому представлению. Явный номер параметра считается более подходящим, чем функция переменной длины. Текстовые представления оцениваются по тем представлениям, которые требуют наименьшего количества преобразований.

И скалярные, и агрегатные функции могут быть определены под одним и тем же именем, если они принимают разное количество параметров. SQLite использует эту функциональность внутри для определения как скалярных, так и агрегатных функций max() и min().

Функцию можно переопределить, зарегистрировав новый набор указателей на функции под тем же именем, номером параметра и текстовым представлением. Эта функция может использоваться для переопределения существующих функций (включая встроенные функции). Чтобы отменить регистрацию функции, просто передайте все указатели на функции NULL.

Наконец, есть одно существенное ограничение для sqlite3_create_function(). Хотя новые функции могут быть определены в любое время, переопределение или удаление функции при наличии активных операторов является незаконным. Поскольку существующие операторы могут содержать ссылки на существующие функции SQL, все подготовленные операторы должны быть признаны недействительными при переопределении или удалении функции. Если в процессе выполнения есть активные операторы, это невозможно и приведет к сбою sqlite3_create_function().

Для получения дополнительной информации о том, как писать собственные скалярные и агрегатные функции SQL, см. главу 9.

См. также
sqlite3_create_collation() [C API, Ap G], sqlite3_user_data() [C API, Ap G], sqlite3_context_db_handle() [C API, Ap G], sqlite3_value_xxx() [C API, Ap G], sqlite3_result_xxx() [C API, Ap G]
sqlite3_create_module()
Регистрация реализации виртуальной таблицы [EXP]
Определение
int sqlite3_create_module(    sqlite3* db, const char* name,
            const sqlite3_module* mod_struct, void* udp );
int sqlite3_create_module_v2( sqlite3* db, const char* name,
            const sqlite3_module* mod_struct, void* udp, dest_func );

void dest_func( void* udp );
db
Подключение к базе данных.
name
Имя модуля (тип виртуальной таблицы) в кодировке UTF-8.
mod_struct
Структура модуля, которая включает указатели на все определяемые приложением функции, необходимые для реализации модуля виртуальной таблицы.
upd
Указатель пользовательских данных, определяемый приложением. Это значение доступно для некоторых функций виртуальных таблиц, а также для функции деструктора.
dest_func
Необязательный указатель функции на функцию уничтожения модуля. Это может быть NULL.
Возвращает (sqlite3_create_module[_v2]())
Код результата SQLite.
Описание

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

Дополнительные сведения о написании модуля виртуальной таблицы см. в главе 10.

См. также
sqlite3_declare_vtab()
sqlite3_data_count()
Получить количество допустимых столбцов результата в операторе
Определение
int sqlite3_data_count( sqlite3_stmt* stmt );
stmt
Подготовленный оператор.
Возвращает
Количество столбцов, доступных в подготовленном операторе.
Описание

Эта функция возвращает количество столбцов, доступных в строке результата. Индексы столбцов начинаются с нуля (0), поэтому, если эта функция возвращает 5, индексы столбцов значений составляют от 0 до 4. Значение ноль (0) будет возвращено, если текущая строка результата отсутствует.

Эта функция почти идентична sqlite3_column_count(). Основное отличие состоит в том, что эта функция вернет ноль (0), если в данный момент нет действительной строки результата (например, до вызова sqlite3_step()), в то время как sqlite3_column_count() всегда будет возвращать количество ожидаемых столбцов, даже если в настоящее время нет доступных строк.

См. также
sqlite3_column_count()
sqlite3_db_config()
Расширенная конфигурация подключения к базе данных [EXP]
Определение
int sqlite3_db_config( sqlite3* db, int option, ... );
db
Подключение к базе данных.
option
Параметр конфигурации, который необходимо изменить.
Дополнительные параметры
Дополнительные параметры определяются данной опцией конфигурации.
Возвращает
Код результата SQLite.
Описание

Эта функция настраивает соединения с базой данных. Он должен вызываться сразу после вызова одной из функций sqlite3_open_xxx().

Для получения дополнительной информации о поддерживаемых в настоящее время параметрах конфигурации см. http://www.sqlite.org/c3ref/c_dbconfig_lookaside.html.

См. также
sqlite3_config(), sqlite3_limit()
sqlite3_db_handle()
Получить соединение с базой данных из оператора
Определение
sqlite3* sqlite3_db_handle( sqlite3_stmt* stmt );
stmt
Подготовленный оператор.
Возвращает
Соединение с базой данных, связанное с предоставленным оператором.
Описание
Эта функция извлекает соединение с базой данных, связанное с подготовленным оператором.
См. также
sqlite3_prepare_xxx()
sqlite3_db_mutex()
Извлечь блокировку мьютекса соединения с базой данных
Определение
sqlite3_mutex* sqlite3_db_mutex( sqlite3* db );
db
Подключение к базе данных.
Возвращает
Блокировку мьютекса соединения с базой данных. Если библиотека не находится в «сериализованном» режиме потоковой передачи, будет возвращен нулевой указатель.
Описание

Эта функция извлекает и возвращает ссылку на блокировку взаимного исключения, связанную с данным соединением с базой данных. Библиотека SQLite должна быть в «сериализованном» режиме потоковой передачи. Если он находится в любом другом режиме, эта функция вернет NULL.

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

См. также
sqlite3_mutex_enter(), sqlite3_mutex_leave()
sqlite3_db_status()
Получение статуса ресурса подключения к базе данных [EXP]
Определение
int sqlite3_db_status( sqlite3* db, int option, int* current, int* highest, int reset);
db
Подключение к базе данных.
option
Параметр ресурса для извлечения.
current
Текущее значение данной опции будет передано обратно через эту ссылку.
highest
Наивысшее из наблюдаемых значений данной опции будет передано обратно через эту ссылку.
reset
Если это значение не равно нулю, максимальное видимое значение будет сброшено на текущее значение.
Возвращает
Код результата SQLite.
Описание

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

Для получения дополнительной информации о поддерживаемых в настоящее время параметрах состояния см. http://www.sqlite.org/c3ref/db_status.html.

См. также
sqlite3_status(), sqlite3_stmt_status()
sqlite3_declare_vtab()
Определение схемы виртуальной таблицы [EXP]
Определение
int sqlite3_declare_vtab( sqlite3* db, const char *sql );
db
Подключение к базе данных.
sql
Строка в кодировке UTF-8, содержащая правильно отформатированный оператор CREATE TABLE.
Возвращает
Код результата SQLite.
Описание

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

Дополнительные сведения о написании модуля виртуальной таблицы см. в главе 10.

См. также
sqlite3_create_module()
sqlite3_enable_load_extension()
Включение или отключение загрузки динамического расширения
Определение
int sqlite3_enable_load_extension( sqlite3* db, int enabled );
db
Подключение к базе данных.
enabled
Флаг включения/выключения. Ненулевое значение включает динамические модули, а нулевое значение отключает их.
Возвращает
Код результата SQLite.
Описание
Эта функция включает или отключает возможность загрузки динамических модулей. По соображениям безопасности динамические модули по умолчанию отключены и должны быть включены явно.
См. также
sqlite3_load_extension()
sqlite3_enable_shared_cache()
Включение или отключение режима общего кэша
Определение
int sqlite3_enable_shared_cache( int enabled );
enabled
Флаг включения/выключения. Ненулевой флаг включает режим общего кэша, а нулевое значение отключает его.
Возвращает
Код результата SQLite.
Описание

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

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

Для получения дополнительной информации о режиме общего кэша см. http://www.sqlite.org/sharedcache.html.

См. также
sqlite3_open(), sqlite3_open_v2()
sqlite3_errcode()
Получить код ошибки для последнего неудачного вызова API
Определение
int sqlite3_errcode( sqlite3* db );
db
Подключение к базе данных.
Возвращает
Код ошибки или расширенный код ошибки.
Описание

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

Если библиотека SQLite находится в «сериализованном» режиме потоковой передачи, существует риск состояния гонки между потоками. Чтобы избежать проблем, текущий поток должен использовать sqlite3_mutex_enter() для получения монопольного доступа к соединению с базой данных до выполнения первоначального вызова API. Поток может освободить мьютекс после вызова sqlite3_errcode(). В «многопоточном» режиме ответственность за управление доступом к соединению с базой данных лежит на приложении.

Если вызов API возвращает SQLITE_MISUSE, это указывает на ошибку приложения. В этом случае код результата может быть доступен или недоступен для sqlite3_errcode().

См. также
sqlite3_errmsg() [C API, Ap G], sqlite3_extended_result_codes() [C API, Ap G], sqlite3_extended_errcode() [C API, Ap G], sqlite3_mutex_enter() [C API, Ap G]
sqlite3_errmsg()
Получить строку ошибки из последнего неудачного вызова API
Определение
const char* sqlite3_errmsg(   sqlite3* db );
const void* sqlite3_errmsg16( sqlite3* db );
db
Подключение к базе данных.
Возвращает
Удобочитаемое сообщение об ошибке на английском языке.
Описание

Эта функция возвращает сообщение об ошибке из последнего неудачного вызова API sqlite3_*, связанного с этим соединением с базой данных. Если за неудачным вызовом последовал успешный вызов, результаты не определены.

Большинство встроенных сообщений об ошибках SQLite чрезвычайно лаконичны и несколько загадочны. Хотя сообщения об ошибках полезны для ведения журнала и отладки, большинство приложений должны предоставлять свои собственные сообщения об ошибках для конечных пользователей.

Если SQLite используется в многопоточной среде, эта функция подвержена тем же проблемам, что и sqlite3_errcode().

Если вызов API возвращает SQLITE_MISUSE, это указывает на ошибку приложения. В этом случае, код результата может быть доступен или недоступен для sqlite3_errmsg().

См. также
sqlite3_errcode()
sqlite3_exec()
Выполнение операторов SQL
Определение

int sqlite3_exec( sqlite3* db, const char* sql, exec_callback, void* udp, char** errmsg );

int exec_callback( void* udp, int c_num, char** c_vals, char** c_names );
db
Подключение к базе данных.
sql
Закодированная в UTF-8 строка с завершающим нулем, содержащая один или несколько операторов SQL. Если предоставлено более одного оператора SQL, они должны быть разделены точкой с запятой.
exec_callback
Необязательная функция обратного вызова. Этот определяемый приложением обратный вызов вызывается один раз для каждой строки в наборе результатов. Если установлено значение NULL, данные результатов не будут доступны.
udp
Указатель пользовательских данных, определяемый приложением. Это значение становится доступным для функции обратного вызова exec.
errmsg
Необязательная ссылка на указатель строки. Если функция возвращает что-либо, кроме SQLITE_OK, сообщение об ошибке будет передано обратно. Если ошибок не обнаружено, указатель будет установлен в NULL. Ссылка может иметь значение NULL, чтобы игнорировать сообщения об ошибках. Сообщения об ошибках должны быть освобождены с помощью sqlite3_free().
c_num
Количество столбцов в строке текущего результата.
c_vals
Массив строк UTF-8, содержащих значения текущей строки результата. Эти значения генерируются с помощью sqlite3_column_text() и используют те же правила преобразования. Невозможно определить исходный тип данных значения.
c_names
Массив строк UTF-8, содержащих имена столбцов текущей строки результата. Они создаются с помощью sqlite3_column_name().
Возвращает (sqlite3_exec())
Код результата SQLite.
Возвращает (exec_callback())
Если обратный вызов возвращает ненулевое значение, выполнение текущего оператора останавливается, и sqlite3_exec() немедленно возвращает SQLITE_ABORT.
Описание

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

Хотя sqlite3_exec() - это простой способ выполнения автономных операторов SQL, он не позволяет использовать параметры операторов. Это может привести к проблемам с производительностью и безопасностью операторов, требующих значений, определяемых приложением. Подготовка и выполнение оператора вручную позволяет использовать параметры оператора и функции sqlite3_bind_xxx().

Эта функция не имеет преимущества в производительности по сравнению с явными функциями sqlite3_prepare() и sqlite3_step(). Фактически, sqlite3_exec() использует эти функции внутри себя.

См. также
sqlite3_column_xxx() (в частности, sqlite3_column_text()), sqlite3_column_name(), sqlite3_prepare_xxx(), sqlite3_step()
sqlite3_extended_errcode()
Получить расширенный код ошибки для последнего неудачного вызова API
Определение
int sqlite3_extended_errcode( sqlite3* db );
db
Подключение к базе данных.
Возвращает
Расширенный код ошибки.
Описание

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

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

Если вызов API возвращает SQLITE_MISUSE, это указывает на ошибку приложения. В этом случае код результата может быть доступен или недоступен для sqlite3_extended_errcode().

См. также
sqlite3_errcode(), sqlite3_extended_result_codes()
sqlite3_extended_result_codes()
Включение или отключение расширенных кодов результатов
Определение
int sqlite3_extended_result_codes( sqlite3* db, int enabled );
db
Подключение к базе данных.
enabled
Ненулевое значение указывает, что расширенные коды результатов должны быть включены, в то время как нулевое значение указывает, что расширенные коды результатов должны быть выключены.
Возвращает
Код результата SQLite.
Описание
Эта функция используется для включения расширенных кодов результатов. Расширенные коды по умолчанию отключены. При включении любая функция API, возвращающая код результата SQLite, может возвращать расширенный код. Расширенный код всегда доступен с помощью sqlite3_extended_errcode().
См. также
sqlite3_errcode(), sqlite3_extended_errcode()
sqlite3_file_control()
Низкоуровневый контроль файлов базы данных
Определение
int sqlite3_file_control( sqlite3* db, const char *name, int option, void* data );
db
Подключение к базе данных.
name
Имя целевой логической базы данных в UTF-8. Это может быть основное, временное или логическое имя, присвоенное ATTACH DATABASE.
option
Значение параметра конфигурации.
data
Значение данных конфигурации. Точное значение обычно определяется значением параметра option.
Возвращает
Код результата SQLite или другое значение.
Описание
Эта функция позволяет приложению взаимодействовать с низкоуровневым уровнем файлового ввода-вывода в SQLite. Параметры опции и данных будут переданы в функцию xFileControl() соответствующего драйвера VFS (виртуальной файловой системы).
См. также
sqlite3_vfs_register()
sqlite3_finalize()
Завершить и выпустить подготовленный оператор
Определение
int sqlite3_finalize( sqlite3_stmt* stmt );
stmt
Подготовленный оператор.
Возвращает
Код результата SQLite.
Описание
Эта функция завершает подготовленные операторы. Завершение оператора освобождает все внутренние ресурсы и освобождает всю память. После того, как оператор был завершен, он больше не действителен и не может быть использован повторно.
См. также
sqlite3_prepare_xxx(), sqlite3_step()
sqlite3_free()
Освобождение выделения памяти
Определение
void sqlite3_free( void* ptr );
ptr
Указатель на выделение памяти.
Описание
Эта функция освобождает блок памяти, который ранее был выделен с помощью sqlite3_malloc() или sqlite3_realloc(). Это не должно использоваться для освобождения памяти, полученной с помощью собственного вызова malloc() или нового вызова.
См. также
sqlite3_malloc(), sqlite3_realloc()
sqlite3_free_table()
Структура таблицы выпуска
Определение
void sqlite3_free_table( char** result );
result
Массив значений таблицы, возвращаемый функцией sqlite3_get_table().
Описание
Эта функция правильно освобождает массив значений, возвращаемый sqlite3_get_table().
См. также
sqlite3_get_table()
sqlite3_get_autocommit()
Получить текущий режим транзакции
Определение
int sqlite3_get_autocommit( sqlite3* db );
db
Подключение к базе данных.
Возвращает
Ненулевое значение, если данное соединение с базой данных находится в режиме автоматической фиксации, и нулевое значение, если это не так.
Описание

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

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

См. также
BEGIN TRANSACTION [SQL Cmd, Ap C]
sqlite3_get_auxdata()
Получить вспомогательные данные из параметра функции
Определение
void* sqlite3_get_auxdata( sqlite3_context* ctx, int pidx );
ctx
Контекст функции SQL, предоставляемый библиотекой SQLite.
pidx
Индекс параметра функции. Первый параметр имеет нулевой индекс (0).
Возвращает
Определяемый пользователем указатель данных, установленный с помощью функции sqlite3_set_auxdata().
Описание
Эта функция используется реализацией функции SQL для извлечения любых определяемых приложением вспомогательных данных, которые могли быть присоединены к параметру функции с помощью sqlite3_set_aux data(). Если данные недоступны или данные были очищены, эта функция вернет NULL.
См. также
sqlite3_set_auxdata(), sqlite3_value_xxx()
sqlite3_get_table()
Получить таблицу результатов
Определение
int sqlite3_get_table( sqlite3* db, const char sql, char*** result, int* num_rows, int* num_cols, char** errmsg );
db
Подключение к базе данных.
sql
Закодированная в UTF-8 строка с завершающим нулем, содержащая один или несколько операторов SQL. Если предоставлено более одного оператора SQL, они должны быть разделены точкой с запятой.
result
Ссылка на массив строк. Результаты запроса передаются обратно с использованием этой ссылки. Переданное значение необходимо освободить с помощью вызова sqlite3_free_table().
num_rows
Количество строк в результате, не включая имена столбцов.
num_cols
Количество столбцов в результате.
errmsg
Необязательная ссылка на строку. В случае возникновения ошибки ссылка будет установлена на сообщение об ошибке. Приложение отвечает за освобождение сообщения с помощью sqlite3_free(). Если ошибки не возникает, ссылка будет установлена в NULL. Ссылка может быть NULL.
Возвращает
Код результата SQLite.
Описание

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

Результатом является массив строк со значениями (num_rows + 1) * num_cols. Первые значения num_cols - это имена столбцов, за которыми следуют отдельные строки. Чтобы получить доступ к значению определенного столбца и строки, используйте формулу (row + 1) * num_cols + col, предполагая, что индексы row и col начинаются с нуля (0).

См. также
sqlite3_free_table(), sqlite3_free()
sqlite3_initialize()
Инициализировать библиотеку SQLite
Определение
int sqlite3_initialize( );
Возвращает
Код результата SQLite.
Описание
Эта функция инициализирует библиотеку SQLite. Его можно смело вызывать несколько раз. Многие стандартные функции SQLite, такие как sqlite3_open(), автоматически вызывают sqlite3_initialize(), если он не был явно вызван приложением. Библиотеку SQLite можно безопасно инициализировать и многократно выключать.
См. также
sqlite3_open(), sqlite3_shutdown()
sqlite3_interrupt()
Отменить все выполняемые операции с базой данных
Определение
void sqlite3_interrupt( sqlite3* db );
db
Подключение к базе данных.
Описание

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

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

См. также
sqlite3_close()
sqlite3_last_insert_rowid()
Получить последнее вставленное определение ROWID
Определение
sqlite3_int64 sqlite3_last_insert_rowid( sqlite3* db );
db
Подключение к базе данных.
Возвращает
Значение последнего вставленного ROWID.
Описание

Эта функция возвращает ROWID последней успешно вставленной строки. Если с момента открытия соединения с базой данных не было вставлено ни одной строки, эта функция вернет ноль (0). Обратите внимание, что ноль является допустимым ROWID, но он никогда не будет автоматически назначен SQLite. Эта функция обычно используется для получения автоматически сгенерированного значения для вновь вставленной записи. Значение часто используется для заполнения внешних ключей.

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

Если библиотека SQLite находится в «сериализованном» режиме потоковой передачи, существует риск состояния гонки между потоками. Чтобы избежать проблем, текущий поток должен использовать sqlite3_mutex_enter() для получения монопольного доступа к соединению с базой данных до выполнения первоначального вызова API. Поток может освободить мьютекс после вызова sqlite3_last_insert_rowid(). В «многопоточном» режиме ответственность за управление доступом к соединению с базой данных лежит на приложении.

Эта функция представлена в среде SQL как функция SQL last_insert_rowid().

См. также
last_insert_rowid() [SQL Func, Ap E]
sqlite3_libversion()
Получение версии библиотеки SQLite
Определение
const char* sqlite3_libversion( );
Возвращает
Версию библиотеки SQLite в виде строки в кодировке UTF-8.
Описание

Эта функция возвращает версию библиотеки SQLite в виде строки. Строка версии для SQLite v3.6.23 - «3.6.23». Строка версии для v3.6.23.1 - «3.6.23.1».

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

Эта функция представлена в среде SQL как функция SQL sqlite_version().

См. также
sqlite3_libversion_number(), sqlite3_sourceid(), sqlite_version() [SQL Func, Ap E]
sqlite3_libversion_number()
Получить версию библиотеки SQLite как номер
Определение
int sqlite3_libversion_number( );
Возвращает
Версию библиотеки SQLite в виде числа.
Описание

Эта функция возвращает версию библиотеки SQLite в виде целого числа. Номер имеет вид Mmmmppp, где M - основная версия, m - второстепенная, а p - точечный выпуск. Меньшие ступеньки не включены. Номер версии SQLite v3.6.23.1 (и v3.6.23) - 3006023.

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

См. также
sqlite3_libversion(), sqlite3_sourceid()
sqlite3_limit()
Получение или установка ограничений и максимумов библиотеки SQLite
Определение
int sqlite3_limit( sqlite3* db, int limit, int value );
db
Подключение к базе данных.
limit
Определенный лимит, который нужно установить.
value
Новое значение. Если значение отрицательное, значение не будет установлено (и будет возвращено текущее значение).
Возвращает
Предыдущее значение.
Описание

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

Полный список поддерживаемых в настоящее время ограничений см. по адресу http://www.sqlite.org/c3ref/c_limit_attached.html.

См. также
sqlite3_config(), sqlite3_db_config()
sqlite3_load_extension()
Загрузить динамическое расширение
Определение
int sqlite3_load_extension( sqlite3* db, const char* file, const char* entry_point, char** errmsg );
db
Подключение к базе данных.
file
Путь и имя файла расширения.
entry_point
Имя функции точки входа. Если это NULL, будет использоваться имя sqlite3_exten sion_init.
errmsg
Необязательная ссылка на указатель строки. Если функция возвращает что-либо, кроме SQLITE_OK, возвращается сообщение об ошибке. Если ошибок не обнаружено, указатель будет установлен в NULL. Ссылка может иметь значение NULL, чтобы игнорировать сообщения об ошибках. Сообщения об ошибках должны быть освобождены с помощью sqlite3_free().
Возвращает
Код результата SQLite.
Описание

Эта функция пытается загрузить динамическое расширение SQLite. По умолчанию использование динамических расширений отключено, и их необходимо включить с помощью вызова sqlite3_enable_load_extension().

Эта функция также представлена как функция SQL load_extension().

Хотя нет никаких ограничений на то, когда может быть загружено расширение, многие расширения регистрируют новые функции и, таким образом, подчиняются ограничениям sqlite3_create_function().

Дополнительную информацию о динамических расширениях см. в разделе «Использование загружаемых расширений».

См. также
sqlite3_enable_load_extension() [C API, Ap G], sqlite3_create_function() [C API, Ap G], load_extension() [SQL Func , Ap E]
sqlite3_log()
Записать сообщение в файл журнала SQLite [EXP]
Определение
void sqlite3_log( int errcode, const char* format, ... );
errcode
Код ошибки, связанный с этим сообщением журнала.
format
Строка форматирования сообщения в стиле sqlite3_snprintf().
Дополнительные параметры
Параметры форматирования сообщения.
Описание

Эта функция записывает сообщение в файл журнала SQLite. Формат и дополнительные параметры будут переданы функции, подобной sqlite3_snprintf(), для форматирования. Сообщения ограничены несколькими сотнями символов. Более длинные сообщения будут обрезаны.

Функция sqlite3_log() была разработана для использования расширениями, у которых нет обычного пути отладки или сообщения об ошибках. Обычно SQLite не имеет файла журнала, и сообщение просто игнорируется. Файл журнала можно настроить с помощью sqlite3_config().

См. также
sqlite3_config()
sqlite3_malloc()
Получить динамическое выделение памяти
Определение
void* sqlite3_malloc( int bytes );
bytes
Размер запрошенного выделения в байтах.
Возвращает
Вновь выделенный буфер. Если память недоступна, возвращается NULL.
Описание

Эта функция получает динамическое выделение памяти из библиотеки SQLite. Память, выделенная с помощью sqlite3_malloc(), должна быть освобождена с помощью sqlite3_free(). Выделение всегда начинается с 8-байтовой (или более) границы.

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

См. также
sqlite3_free(), sqlite3_realloc()
sqlite3_memory_highwater()
Получение или сброс максимальной отметки использования памяти
Определение
sqlite3_int64 sqlite3_memory_highwater( int reset );
reset
Если это значение не равно нулю, отметка максимального уровня будет сброшена на текущее значение использования.
Возвращает
Максимальное количество байтов, выделенных системой памяти SQLite.
Описание
Эта функция возвращает максимальную отметку использования памяти или максимальное наблюдаемое значение. Отметку высокого уровня воды можно при желании сбросить до текущего значения.
См. также
sqlite3_memory_used()
sqlite3_memory_used()
Получить текущее использование памяти
Определение
sqlite3_int64 sqlite3_memory_used( );
Возвращает
Количество байтов, выделенных в данный момент системой памяти SQLite.
Описание
Эта функция возвращает текущее количество байтов, выделенных функциями памяти SQLite. Этот рисунок включает все накладные расходы, вносимые распределителем SQLite, но не включает какие-либо накладные расходы, необходимые для обработчиков системной памяти.
См. также
sqlite3_memory_highwater(), sqlite3_malloc()
sqlite3_mprintf()
Форматирование и выделение строки
Определение
char* sqlite3_mprintf( const char* format, ... );
format
Строка форматирования сообщения в стиле sqlite3_snprintf().
Дополнительные параметры
Параметры форматирования сообщения.
Возвращает
Вновь выделенную строку, построенную на основе заданных параметров.
Описание

Эта функция строит отформатированную строку и возвращает ее во вновь выделенном буфере памяти. Если требуемое выделение памяти не удается, может быть возвращено NULL. Приложение отвечает за освобождение возвращенного буфера с помощью sqlite3_free().

Эта функция поддерживает те же расширенные параметры форматирования, что и sqlite3_snprintf().

См. также
sqlite3_snprintf()
sqlite3_mutex_alloc()
Выделить новую блокировку мьютекса
Определение
sqlite3_mutex* sqlite3_mutex_alloc( int type );
type
Желаемый тип блокировки взаимного исключения.
Возвращает
Вновь выделенную и инициализированную блокировку взаимного исключения.
Описание

Эта функция выделяет и инициализирует новую блокировку взаимного исключения запрошенного типа. Приложения могут запрашивать блокировку SQLITE_MUTEX_RECURSIVE или SQLITE_MUTEX_FAST. Рекурсивная блокировка должна поддерживать и правильно подсчитывать количество ссылок на несколько вызовов входа / выхода из одного и того же потока. Быстрый мьютекс не должен поддерживать ничего, кроме простой семантики входа / выхода. В зависимости от библиотеки потоков, быстрая блокировка может быть не быстрее, чем рекурсивная блокировка.

Любой мьютекс приложения, полученный с помощью sqlite3_mutex_alloc(), в конечном итоге должен быть освобожден с помощью sqlite3_mutex_free().

См. также
sqlite3_mutex_free(), sqlite3_db_mutex()
sqlite3_mutex_enter()
Получение блокировки мьютекса
Определение
void sqlite3_mutex_enter( sqlite3_mutex* mutex );
mutex
Блокировка взаимного исключения. Если передано значение NULL, функция немедленно вернется.
Описание

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

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

См. также
sqlite3_mutex_leave(), sqlite3_mutex_try(), sqlite3_db_mutex()
sqlite3_mutex_free()
Освобождение блокировки мьютекса
Определение
void sqlite3_mutex_free( sqlite3_mutex* mutex );
mutex
Блокировка взаимного исключения. Передача указателя NULL не допускается.
Описание
Эта функция разрушает и снимает блокировку взаимного исключения. Блокировка не должна удерживаться каким-либо потоком, когда она освобождена. Приложения должны освобождать только те блокировки, которые приложение создало с помощью sqlite3_mutex_alloc().
См. также
sqlite3_mutex_alloc()
sqlite3_mutex_held()
Проверить, удерживается ли блокировка мьютекса
Определение
int sqlite3_mutex_held( sqlite3_mutex* mutex );
mutex
Блокировка взаимного исключения. Если передано значение NULL, функция должна вернуть истину (ненулевое значение).
Возвращает
Ненулевое значение, если данный мьютекс в настоящее время удерживается, или нулевое значение, если мьютекс не удерживается.
Описание

Эта функция проверяет, удерживает ли текущий поток заданную блокировку взаимного исключения. Библиотека SQLite использует эту функцию только в проверках assert(). Как правило, эта функция доступна только в том случае, если SQLite скомпилирован, а SQLITE_DEBUG определен. Не все библиотеки потоковой передачи поддерживают эту функцию. Неподдерживаемые платформы всегда должны возвращать значение true (ненулевое значение).

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

См. также
sqlite3_mutex_notheld(), sqlite3_mutex_enter(), SQLITE_DEBUG [Build Opt, Ap A]
sqlite3_mutex_leave()
Снять блокировку мьютекса
Определение
void sqlite3_mutex_leave( sqlite3_mutex* mutex );
mutex
Блокировка взаимного исключения. Если передано значение NULL, функция просто вернется.
Описание
Эта функция позволяет потоку освободить блокировку взаимного исключения. Это делает блокировку доступной для других потоков. Обычно это делается при выходе из критического участка кода.
См. также
sqlite3_mutex_enter(), sqlite3_mutex_alloc(), sqlite3_db_mutex(), sqlite3_mutex_free()
sqlite3_mutex_notheld()
Проверить, не удерживается ли блокировка мьютекса
Определение
int sqlite3_mutex_notheld( sqlite3_mutex* mutex );
mutex
Блокировка взаимного исключения. Если передано значение NULL, функция должна вернуть истину (ненулевое значение).
Возвращает
Ненулевое значение, если данный мьютекс в настоящее время не удерживается, или нулевое значение, если мьютекс удерживается.
Описание
Эта функция по сути противоположна sqlite3_mutex_held() и подчиняется тем же условиям и ограничениям.
См. также
sqlite3_mutex_held()
sqlite3_mutex_try()
Попытка получить блокировку мьютекса
Определение
int sqlite3_mutex_try( sqlite3_mutex* mutex );
mutex
Блокировка взаимного исключения.
Возвращает
Значение SQLITE_OK возвращается, если блокировка была получена. В противном случае будет возвращено SQLITE_BUSY.
Описание

Эта функция пытается получить блокировку взаимного исключения для текущего потока. Если блокировка получена, будет возвращен SQLITE_OK. Если блокировка удерживается другим потоком, будет возвращен SQLITE_BUSY. Если передан указатель мьютекса NULL, функция вернет SQLITE_OK.

Эта функция поддерживается не всеми библиотеками потоков. Если SQLite поддерживает потоки, но эта функция не поддерживается, допустимый мьютекс всегда будет приводить к коду возврата SQLITE_BUSY.

См. также
sqlite3_mutex_enter(), sqlite3_mutex_leave()
sqlite3_next_stmt()
Переход по списку подготовленных операторов
Определение
sqlite3_stmt* sqlite3_next_stmt( sqlite3* db, sqlite3_stmt* stmt );
db
Подключение к базе данных.
stmt
Предыдущий оператор. Чтобы получить первый оператор, передайте NULL.
Возвращает
Подготовленный указатель оператора. NULL указывает на конец списка.
Описание

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

Если приложение выполняет итерацию по подготовленным операторам и завершает их, помните, что функция sqlite3_finalize() удалит оператор из списка операторов. В таких случаях приложение должно продолжать передавать значение оператора NULL до тех пор, пока не останутся все операторы. Однако этот метод не рекомендуется, так как он может оставить висячие указатели на инструкции.

См. также
sqlite3_prepare_xxx(), sqlite3_finalize()
sqlite3_open()
Открытие файла базы данных
Определение
int sqlite3_open(   const char* filename, sqlite3** db_ref );
int sqlite3_open16( const void* filename, sqlite3** db_ref );
filename
Путь и имя файла базы данных в виде строки в кодировке UTF-8 или UTF-16.
db_ref
Ссылка на соединение с базой данных. Если база данных успешно открыта, соединение с базой данных будет передано обратно.
Возвращает
Код результата SQLite.
Описание

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

Если имя файла :memory: будет создана временная база данных в памяти. База данных будет автоматически удалена при закрытии соединения с базой данных. Каждый вызов open создаст новую базу данных в памяти. Каждая база данных в памяти доступна только через одно соединение с базой данных.

Если имя файла NULL или пустая строка (""), будет создана временная база данных с файловой поддержкой. Это очень похоже на базу данных в памяти, только база данных может выгружать страницы на диск. Это позволяет базе данных увеличиваться до гораздо больших размеров, не беспокоясь о потреблении памяти.

Эти функции считаются устаревшими API. Рекомендуется, чтобы все новые разработки использовали sqlite3_open_v2().

См. также
sqlite3_open_v2(), sqlite3_close()
sqlite3_open_v2()
Открытие файла базы данных
Определение
int sqlite3_open_v2( const char* filename, sqlite3** db_ref, int flags, const char* vfs );
filename
Путь и имя файла базы данных в виде строки в кодировке UTF-8.
db_ref
Ссылка на соединение с базой данных. Если база данных успешно открыта, соединение с базой данных будет передано обратно.
flags
Ряд флагов, которые можно использовать для управления открытием файла базы данных.
vfs
Имя используемого модуля VFS (виртуальной файловой системы). NULL приведет к модулю по умолчанию.
Возвращает
Код результата SQLite.
Описание

Эта функция очень похожа на sqlite3_open(), но обеспечивает лучший контроль над тем, как открывается файл базы данных. Параметр flags управляет состоянием открытого файла, а параметр vfs позволяет приложению указать драйвер VFS (виртуальной файловой системы).

Параметр flag состоит из нескольких бит-флагов, которые могут быть соединены вместе. Приложение должно указать одну из следующих комбинаций флагов:

SQLITE_OPEN_READONLY
Открыть файл только для чтения. Файл должен уже существовать.
SQLITE_OPEN_READWRITE
Попытка открыть файл для чтения/записи. Если это невозможно, откройте файл только для чтения. Открытие файла только для чтения не приведет к ошибке. Файл должен уже существовать.
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE
Попытка открыть файл для чтения/записи. Если его нет, создайте файл. Если файл существует, но разрешения не разрешают доступ для чтения / записи, откройте файл только для чтения. Открытие файла только для чтения не приведет к ошибке. Это поведение sqlite3_open().

Кроме того, эти необязательные флаги могут быть добавлены к одному из вышеперечисленных наборов флагов:

SQLITE_OPEN_NOMUTEX
Если библиотека SQLite была скомпилирована с поддержкой потоковой передачи, откройте соединение с базой данных в «многопоточном» режиме. Этот флаг нельзя использовать вместе с флагом SQLITE_OPEN_FULLMUTEX.
SQLITE_OPEN_FULLMUTEX
Если библиотека SQLite была скомпилирована с поддержкой потоковой передачи, откройте соединение с базой данных в «сериализованном» режиме. Этот флаг нельзя использовать вместе с флагом SQLITE_OPEN_NO MUTEX.
SQLITE_OPEN_SHAREDCACHE
Включает режим общего кэша для этого соединения с базой данных. Этот флаг нельзя использовать вместе с флагом SQLITE_OPEN_PRIVATECACHE.
SQLITE_OPEN_PRIVATECACHE
Отключает режим общего кэша для этого соединения с базой данных. Этот флаг нельзя использовать вместе с флагом SQLITE_OPEN_SHAREDCACHE.

Дополнительные флаги могут быть добавлены в будущие версии SQLite.

См. также
sqlite3_open() [Ap G], sqlite3_close() [Ap G], sqlite3_enable_shared_cache() [Ap G], sqlite3_config() [Ap G], sqlite3_vfs_find() [Ap G]
sqlite3_overload_function()
Создание функции-заглушки [EXP]
Определение
int sqlite3_overload_function( sqlite3* db, const char* name, int num_param );
db
Подключение к базе данных.
name
Имя функции в кодировке UTF-8.
num_param
Количество параметров в функции-заглушке.
Возвращает
Код результата SQLite.
Описание

Эта функция проверяет, существует ли названная функция SQL (с указанным числом параметров) или нет. Если функция существует, ничего не делается. Если функция еще не существует, регистрируется функция-заглушка. Эта функция-заглушка всегда будет возвращать SQLITE_ERROR.

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

См. также
sqlite3_create_function(), sqlite3_create_module()
sqlite3_prepare_xxx()
Преобразование строки SQL в подготовленный оператор
Определение
int sqlite3_prepare( sqlite3* db, const char* sql, int sql_len, sqlite3_stmt** stmt_ref, const char** tail );
int sqlite3_prepare16( sqlite3* db, const void* sql, int sql_len, sqlite3_stmt** stmt_ref, const char** tail );
int sqlite3_prepare_v2( sqlite3* db, const char* sql, int sql_len, sqlite3_stmt** stmt_ref, const char** tail );
int sqlite3_prepare16_v2( sqlite3* db, const void* sql, int sql_len, sqlite3_stmt** stmt_ref, const char** tail );
db
Подключение к базе данных.
sql
Один или несколько операторов SQL в строке в кодировке UTF-8 или UTF-16. Если строка содержит более одного оператора SQL, они должны быть разделены точкой с запятой. Если строка содержит только один оператор SQL, точка с запятой в конце не требуется.
sql_len
Длина буфера sql в байтах. Если строка sql оканчивается нулем, длина должна включать символ завершения. Если строка sql оканчивается нулем, но длина неизвестна, отрицательное значение заставит SQLite вычислить длину буфера.
stmt_ref
Ссылка на подготовленный оператор. SQLite выделит и вернет подготовленный оператор.
tail
Если буфер sql содержит более одного оператора SQL, используется только первый полный оператор. Если существуют дополнительные утверждения, эта ссылка будет использоваться для передачи указателя на следующий оператор SQL в буфере sql. Эта ссылка может иметь значение NULL.
Возвращает
Код результата SQLite.
Описание

Эти функции берут строку оператора SQL и создают подготовленный оператор. Подготовленный оператор можно выполнить с помощью sqlite3_step(). Все подготовленные операторы в конечном итоге должны быть завершены с помощью sqlite3_finalize().

И исходная версия, и версия _v2 используют одни и те же параметры. Однако версии _v2 дают несколько иное утверждение. Новые операторы могут автоматически восстанавливаться после некоторых ошибок и обеспечивать лучшую обработку ошибок. Более подробное обсуждение различий см. В разделе «Подготовка v2».

Исходные версии считаются устаревшими API-интерфейсами, и их использование в новых разработках не рекомендуется.

См. также
sqlite3_finalize(), sqlite3_step()
sqlite3_profile()
Регистрация обратного вызова профиля оператора SQL
Определение
void* sqlite3_profile( sqlite3* db, profile_func, void* udp );

void profile_func( void* udp, const char* sql, sqlite3_uint64 time );
db
Подключение к базе данных.
profile_func
Определяемая приложением функция обратного вызова профиля.
udp
Указатель пользовательских данных, определяемый приложением. Это значение доступно для функции профиля.
sql
Текст выполненного оператора SQL в кодировке UTF-8.
time
Время на настенных часах, необходимое для выполнения оператора в наносекундах. Большинство системных часов не могут обеспечить точность наносекунд.
Возвращает (sqlite3_profile())
Параметр udp любой предыдущей функции профиля. Если предыдущая функция профиля не была зарегистрирована, будет возвращено NULL.
Описание
Эта функция позволяет приложению зарегистрировать функцию профилирования. Функция обратного вызова вызывается каждый раз, когда завершается вызов sqlite3_step(). Разрешена только одна функция профиля. Регистрация новой функции отменяет старую функцию. Чтобы отменить регистрацию функции профилирования, передайте указатель функции NULL.
См. также
sqlite3_trace(), sqlite3_prepare_xxx(), sqlite3_step()
sqlite3_progress_handler()
Регистрация обработчика прогресса
Определение

void sqlite3_progress_handler( sqlite3* db, int num_ops, progress_handler, void* udp );

int progress_handler( void* udp );
db
Подключение к базе данных.
num_ops
Число операций с байтовым кодом, которые ядро базы данных должно выполнять между вызовами обработчика хода выполнения. Это приблизительное значение.
progress_handler
Определяемая приложением функция обратного вызова обработчика хода выполнения.
udp
Указатель пользовательских данных, определяемый приложением. Это значение доступно обработчику прогресса.
Возвращает (progress_handler())
Если обработчик возвращает ненулевое значение, текущие операции с базой данных прерываются.
Описание

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

Эта функция обеспечивает только периодический обратный вызов. Его нельзя использовать для оценки процента выполнения.

См. также
sqlite3_interrupt()
sqlite3_randomness()
Запрос данных случайного значения
Определение
void sqlite3_randomness( int bytes, void* buffer );
bytes
Желаемое количество байтов случайных данных.
buffer
Буфер приложения, достаточно большой для хранения запрошенных данных.
Описание
Эта функция позволяет приложению запрашивать случайные значения данных из внутреннего генератора псевдослучайных чисел SQLite.
См. также
random() [SQL Func, Ap E], randomblob() [SQL Func, Ap E]
sqlite3_realloc()
Изменение размера распределения динамической памяти
Определение
void* sqlite3_realloc( void* ptr, int bytes );
ptr
Указатель на существующий буфер динамической памяти. Может быть NULL.
bytes
Новый размер запрошенного буфера в байтах.
Возвращает
Вновь настроенный буфер. Может иметь значение NULL, если не удается выделить память. Если NULL, старый буфер все еще будет действителен.
Описание

Эта функция изменяет размер распределения динамической памяти. Его можно использовать для увеличения или уменьшения размера выделения. Это может потребовать перемещения выделения. В этом случае содержимое текущего буфера будет скопировано в начало настроенного буфера. Если новый буфер меньше, некоторые данные будут отброшены. Выделение всегда начинается с 8-байтовой (или более) границы.

Если передан NULL-указатель, эта функция будет действовать как sqlite3_malloc() и выделять новый буфер. Если параметр bytes равен нулю или отрицателен, эта функция будет действовать как sqlite3_free(), освобождая существующий буфер и возвращая NULL.

См. также
sqlite3_malloc(), sqlite3_free()
sqlite3_release_memory()
Уменьшение использования памяти
Определение
int sqlite3_release_memory( int bytes );
bytes
Запрошенное количество освобождаемых байтов.
Возвращает
Фактическое количество свободных байтов. Это может быть больше или меньше запрошенного числа.
Описание
Эта функция запрашивает, чтобы библиотека SQLite освободила определенный объем памяти. SQLite сделает это, освободив некритическую память, например пространство кеша (включая кеш страницы). Освобожденная память может не быть непрерывной.
См. также
sqlite3_malloc(), sqlite3_free()
sqlite3_reset()
Сброс подготовленного оператора
Определение
int sqlite3_reset( sqlite3_stmt* stmt );
stmt
Подготовленный оператор.
Возвращает
Код результата SQLite.
Описание

Эта функция сбрасывает подготовленный оператор, делая его доступным для выполнения. Обычно это делается после одного или нескольких вызовов sqlite3_step(). Вызов сброса снимает все блокировки и другие ресурсы, связанные с последним выполнением оператора. Эта функция может быть вызвана в любое время между вызовами sqlite3_prepare_xxx() и sqlite3_finalize().

Эта функция не очищает привязки параметров оператора. Приложение должно вызвать sqlite3_clear_bindings(), чтобы сбросить параметры оператора до NULL.

См. также
sqlite3_prepare_xxx(), sqlite3_step(), sqlite3_finalize(), sqlite3_clear_bindings()
sqlite3_reset_auto_extension()
Удалить все автоматические расширения
Определение
void sqlite3_reset_auto_extension( );
stmt
Подготовленный оператор.
Возвращает
Код результата SQLite.
Описание

Эта функция удаляет все автоматические расширения, которые ранее были зарегистрированы с помощью sqlite3_auto_extension().

Невозможно удалить определенные расширения или получить список текущих расширений.

См. также
sqlite3_auto_extension()
sqlite3_result_xxx()
Возвращает результат из функции SQL
Определение
void sqlite3_result_blob( sqlite3_context* ctx, const void* val, int bytes, mem_callback
void sqlite3_result_double( sqlite3_context* ctx, double val );
void sqlite3_result_int( sqlite3_context* ctx, int val );
void sqlite3_result_int64( sqlite3_context* ctx, sqlite3_int64 val );
void sqlite3_result_null( sqlite3_context* ctx );
void sqlite3_result_text( sqlite3_context* ctx, const char* val, int bytes, mem_callback )
void sqlite3_result_text16( sqlite3_context* ctx, const void* val, int bytes, mem_callback )
void sqlite3_result_text16le( sqlite3_context* ctx, const void* val, int bytes, mem_callback )
void sqlite3_result_text16be( sqlite3_context* ctx, const void* val, int bytes, mem_callback
void sqlite3_result_value( sqlite3_context* ctx, sqlite3_value* val );
void sqlite3_result_zeroblob( sqlite3_context* ctx, int bytes );

void mem_callback( void* ptr );
                
ctx
Контекст функции SQL, предоставляемый библиотекой SQLite.
val
Возвращаемое значение данных.
bytes
Размер значения данных в байтах. В частности, текстовые значения выражаются в байтах, а не в символах.
mem_callback

Указатель функции на функцию освобождения памяти. Эта функция используется для освобождения буфера памяти, используемого для хранения значения. Если буфер был выделен с помощью sqlite3_mal loc(), ссылку на sqlite3_free() можно передать напрямую.

Также можно использовать специальные флаги SQLITE_STATIC и SQLITE_TRANSIENT. SQLITE_STATIC указывает, что приложение будет сохранять память значений действительной. SQLITE_TRANSIENT заставит SQLite создать внутреннюю копию буфера значений, которая будет автоматически освобождена, когда она больше не понадобится.

Описание

Эти функции возвращают результат реализации функции SQL. Скалярная функция и агрегатная функция финализации могут возвращать результат. Функция агрегированного шага может возвращать ошибку только с sqlite3_result_error_xxx(). По умолчанию возвращается значение SQL NULL, если не используется одна из этих функций. Функция может вызывать одну из этих функций (и / или вызывать sqlite3_result_error_xxx()) столько раз, сколько необходимо для обновления или сброса значения результата.

В остальном эти функции почти идентичны функциям sqlite3_bind_xxx().

См. также
sqlite3_result_error_xxx(), sqlite3_create_function()
sqlite3_result_error_xxx()
Возвращает условие ошибки из функции SQL
Определение
void sqlite3_result_error(        sqlite3_context* ctx, const char* msg, int bytes );
void sqlite3_result_error16(      sqlite3_context* ctx, const void* msg, int bytes );
void sqlite3_result_error_code(   sqlite3_context* ctx, int errcode );
void sqlite3_result_error_nomem(  sqlite3_context* ctx );
void sqlite3_result_error_toobig( sqlite3_context* ctx );
ctx
Контекст функции SQL, предоставляемый библиотекой SQLite.
msg
Возвращаемое сообщение об ошибке в кодировке UTF-8 или UTF-16.
bytes
Размер сообщения об ошибке в байтах (не в символах).
errcode
Код результата SQLite.
Описание

Эти функции возвращают ошибку из реализации функции SQL. Это вызовет выдачу исключения SQL и sqlite3_step() для возврата данной ошибки. Функция может вызывать одну из этих функций (и вызывать sqlite3_result_xxx()) столько раз, сколько необходимо для обновления или сброса ошибки результата.

Функции sqlite3_result_error() и sqlite3_result_error16() установят для ошибки результата значение SQLITE_ERROR и вернут предоставленное сообщение об ошибке. Создается копия сообщения об ошибке, поэтому нет необходимости поддерживать буфер сообщений в действующем состоянии после возврата из функции.

Функция sqlite3_result_error_code() устанавливает код результата SQLite, отличный от SQLITE_ERROR. Поскольку sqlite3_result_error [16]() устанавливает код результата равным SQLITE_ERROR, для обратной передачи настраиваемого сообщения и кода ошибки требуется установить сообщение, за которым следует код результата.

Функция sqlite3_result_error_nomem() указывает на состояние нехватки памяти. Эта специализированная функция не будет пытаться выделить память. Функция sqlite3_result_error_toobig() указывает, что входной параметр (обычно текст или значение BLOB) слишком велик для обработки приложением.

См. также
sqlite3_result_xxx(), sqlite3_create_function()
sqlite3_rollback_hook()
Регистрация обратного вызова отката
Определение
void* sqlite3_rollback_hook( sqlite3* db, rollback_callback, void* udp );

void commit_callback( void* udp );
db
Подключение к базе данных.
rollback_callback
Указатель на функцию обратного вызова фиксации, определяемую приложением.
upd
Указатель пользовательских данных, определяемый приложением. Это значение становится доступным для обратного вызова отката.
Возвращает (sqlite3_rollback_hook())
Предыдущий указатель пользовательских данных, если применимо.
Описание

Эта функция регистрирует обратный вызов отката. Эта функция обратного вызова вызывается, когда база данных выполняет откат от явного ROLLBACK или из-за состояния ошибки (включая обратный вызов фиксации, возвращающий ненулевое значение). Обратный вызов не вызывается, когда выполняемая транзакция откатывается из-за закрытия соединения с базой данных.

Каждое соединение с базой данных может иметь только один обратный вызов. Регистрация нового обратного вызова перезапишет любой ранее зарегистрированный обратный вызов. Чтобы удалить обратный вызов, установите указатель функции NULL.

Обратный вызов не должен использовать связанное соединение с базой данных для изменения каких-либо баз данных, а также не может вызывать sqlite3_prepare_v2() или sqlite3_step().

См. также
sqlite3_commit_hook(), sqlite3_update_hook()
sqlite3_set_authorizer()
Регистрация обратного вызова авторизации
Определение
int sqlite3_set_authorizer( sqlite3* db, auth_callback, void* udp );
int auth_callback( void* udp, int action_code, const char* param1, const char* param2, const char* db_name, const char* trigger_name );
db
Подключение к базе данных.
auth_callback
Определяемая приложением функция обратного вызова авторизации.
upd
Указатель пользовательских данных, определяемый приложением. Это значение становится доступным для обратного вызова авторизации.
action_code
Код, указывающий, какая операция с базой данных требует авторизации.
param1, param2
Два значения данных, связанных с действием авторизации. Конкретное значение этих параметров зависит от значения параметра action_code.
db_name
Логическое имя базы данных, на которую влияет рассматриваемое действие. Это значение действительно для многих, но не для всех значений action_code.
trigger_name
Если рассматриваемое действие исходит от триггера, имя триггера самого низкого уровня. Если действие исходит от простого оператора SQL, этот параметр будет NULL.
Возвращает (sqlite3_set_authorizer())
Код результата SQLite.
Возвращает (auth_func())
Код результата SQLite. Код SQLITE_OK указывает, что действие разрешено. Код SQLITE_IGNORE запрещает конкретное действие, но позволяет продолжить выполнение инструкции SQL. Код SQLITE_DENY вызывает отклонение всего оператора SQL.
Описание

Эта функция регистрирует обратный вызов авторизации. Обратный вызов вызывается при подготовке операторов SQL, позволяя приложению разрешать или запрещать определенные действия. Это полезно при обработке операторов SQL из внешних источников (включая пользователя приложения). Функция авторизации не должна использовать соединение с базой данных.

Полное описание поддерживаемых в настоящее время кодов действий см. на http://www.sqlite.org/c3ref/c_alter_table.html.

См. также
sqlite3_prepare_xxx(), sqlite3_limit()
sqlite3_set_auxdata()
Установка вспомогательных данных для параметра функции
Определение

void sqlite3_set_auxdata( sqlite3_context* ctx, int pidx, void* data, mem_callback );

void mem_callback( void* ptr );
ctx
Контекст функции SQL, предоставляемый библиотекой SQLite.
pidx
Параметр index.
data
Значение вспомогательных данных.
mem_callback

Указатель функции на функцию освобождения памяти. Эта функция освобождает буфер памяти, используемый для хранения значения. Если буфер был выделен с помощью sqlite3_malloc(), ссылку на sqlite3_free() можно передать напрямую.

Флаги SQLITE_STATIC и SQLITE_TRANSIENT (которые используются sqlite3_bind_xxx() и sqlite3_result_xxx()) в этом контексте недоступны.

Описание

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

Для получения дополнительных сведений о том, как использовать вспомогательные данные, см. http://www.sqlite.org/c3ref/get_auxdata.html.

См. также
sqlite3_get_auxdata(), sqlite3_create_function()
sqlite3_shutdown()
Завершение работы библиотеки SQLite
Определение
int sqlite3_shutdown( );
Возвращает
Код результата SQLite.
Описание
Эта функция завершает работу библиотеки SQLite и освобождает все связанные ресурсы. Библиотеку необходимо повторно инициализировать, прежде чем ее можно будет использовать снова. Эту функцию безопасно вызывать несколько раз.
См. также
sqlite3_initialize()
sqlite3_sleep()
Спящий режим текущего потока
Определение
int sqlite3_sleep( int milliseconds );
milliseconds
Количество миллисекунд, в течение которых настенные часы переходят в спящий режим. Не все платформы поддерживают это временное разрешение. Число можно округлить в большую сторону.
Возвращает
Фактическое количество сна в миллисекундах.
Описание
Эта функция переводит текущий поток в спящий режим, по крайней мере, на указанное количество миллисекунд (тысячные доли секунды). Фактическое время ожидания может быть больше, особенно в тех системах, которые не поддерживают тактовую частоту процесса в миллисекундах.
sqlite3_snprintf()
Форматирование строки
Определение
char* sqlite3_snprintf( int bytes, char* buf, const char* format, ... );
bytes
Количество байтов, доступных в выходном буфере.
buf
Предварительно выделенный буфер для приема форматированной строки.
format
Строка формата, используемая для построения выходной строки. Это похоже на стандартную форматированную строку в стиле printf(), но поддерживает несколько дополнительных флагов форматирования.
Дополнительные параметры
Параметры форматирования сообщения.
Возвращает
Указатель на буфер форматированной строки.
Описание

Эта функция форматирует и создает строку UTF-8 в предоставленном буфере. Он разработан для имитации стандартной функции snprintf(). Предполагая, что размер предоставленного буфера составляет один байт или больше, строка всегда будет завершаться нулевым символом в конце.

Обратите внимание, что первые два параметра sqlite3_snprintf() отличаются от стандартного snprintf(). Кроме того, snprintf() возвращает количество символов в выходной строке, а sqlite3_snprintf() возвращает указатель на буфер, переданный приложением.

В дополнение к стандартным флагам форматирования% s,% c,% d,% i,% o,% u,% x,% X,% f,% e,% E,% g,% G и %%, все функции стиля SQLite printf() также поддерживают флаги% q,% Q,% z,% w и% p.

Флаг% q похож на% s, только он очищает строку для использования в качестве строкового литерала SQL. В основном это состоит из удвоения всех символов одинарной кавычки ('), чтобы сформировать правильный escape-код SQL (' '). Таким образом,% q возьмет входную строку O'Reilly и выведет O''Reilly. Отформатированная строка должна содержать заключительные одинарные кавычки (например, «... '% q' ...»).

Флаг% Q похож на% q, только он также заключит строку ввода в одинарные кавычки. Флаг% Q примет входную строку O'Reilly и выведет 'O''Reilly' (включая заключительные одинарные кавычки). Флаг% Q также выводит константу NULL (без одинарных кавычек), если строковое значение является указателем NULL. Это позволяет флагу% Q принимать более разнообразный набор символьных указателей без дополнительной логики приложения. Поскольку% Q включает в себя свой собственный, форматированная строка должна содержать заключительные одинарные кавычки (например, «...% Q ...»).

Флаг% w похож на флаг% q, только он предназначен для работы с идентификаторами SQL, а не с строковыми константами SQL. Идентификаторы включают имена баз данных, имена таблиц и имена столбцов. Флаг% w очищает входные значения, удваивая все символы двойных кавычек ("), чтобы сформировать правильный escape-код SQL (" "). Подобно% q, форматированная строка, в которой используется% w, должна включать заключительные кавычки. В случае идентификаторы, они должны быть заключены в двойные кавычки:

"... \"%w\" ..."

Наконец, флаг %p предназначен для форматирования указателей. Это приведет к получению шестнадцатеричного значения и должно корректно работать как в 32-, так и в 64-разрядных системах.

Как правило, построение SQL-запросов с использованием манипуляций со строками несколько рискованно. Для литеральных значений лучше использовать интерфейс prepare/bind/step, даже если оператор будет использоваться только один раз. Если вам необходимо создать строку запроса, всегда проверяйте правильность очистки входных значений с помощью %q , %Q или %w и всегда проверяйте, правильно ли указаны значения. Это включает в себя размещение двойных кавычек вокруг всех идентификаторов и имен.

См. также
sqlite3_mprintf(), sqlite3_vmprintf()
sqlite3_soft_heap_limit()
Ограничение объема памяти, используемой SQLite
Определение
void sqlite3_soft_heap_limit( int bytes );
bytes
Запрошенный предел в байтах.
Описание

Описание Эта функция устанавливает мягкое ограничение на объем динамической памяти кучи, используемой библиотекой SQLite. Если выделение памяти приведет к превышению лимита, вызывается sqlite3_release_mem ory() в попытке восстановить память. Если это не удается, память все равно выделяется без предупреждения или ошибки.

Чтобы снять ограничение, просто введите нулевое или отрицательное значение.

См. также
sqlite3_release_memory(), sqlite3_limit(), sqlite3_config(), sqlite3_db_config()
sqlite3_sourceid()
Получение идентификатора источника библиотеки SQLite
Определение
const char* sqlite3_sourceid( );
Возвращает
Значение идентификатора источника для возврата, используемого для создания библиотеки SQLite.
Описание

Эта функция возвращает идентификатор источника библиотеки SQLite. Идентификатор источника состоит из даты, времени, и хэш-код из последней проверки системы контроля версий, использованный для создания библиотеки SQLite. Идентификатор источника для SQLite 3.6.23.1:

2010-03-26 22:28:06 b078b588d617e07886ad156e9f54ade6d823568e

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

Эта функция представлена в среде SQL как функция SQL sqlite_source_id().

См. также
sqlite3_libversion(), sqlite3_libversion_number(), sqlite_source_id() [SQL Func, Ap E]
sqlite3_sql()
Получить SQL подготовленного оператора
Определение
const char* sqlite3_sql( sqlite3_stmt stmt );
stmt
Подготовленный оператор.
Возвращает
Строка SQL, используемая для подготовки оператора.
Описание

Эта функция возвращает исходную строку SQL, используемую для подготовки данного оператора. Оператор должен быть подготовлен с использованием _v2 версии prepare. Если оператор был подготовлен с помощью исходных функций sqlite3_prepare() или sqlite3_prepare16(), эта функция всегда будет возвращать NULL.

Возвращенный оператор всегда будет закодирован в UTF-8, даже если sqlite3_prepare16_v2() использовался для подготовки оператора.

См. также
sqlite3_libversion(), sqlite3_libversion_number(), sqlite_source_id() [SQL Func, Ap E]
sqlite3_status()
Получение статуса ресурса библиотеки SQLite [EXP]
Определение
int sqlite3_status( int option, int *current, int *highest, int reset );
option
Опция статуса для извлечения.
current
Ссылка на целое число. Текущее значение будет передано обратно с использованием этой ссылки.
highest
Ссылка на целое число. По этой ссылке будет передано наивысшее увиденное значение.
reset
Если этот флаг не равен нулю, отметка максимального уровня будет сброшена на текущее значение после того, как будет возвращено текущее значение отметки максимального уровня.
Возвращает
Код результата SQLite.
Описание

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

Доступные параметры включают несколько различных мониторов памяти, а также информацию о кэше страницы. Полный список поддерживаемых в настоящее время параметров см. на http://www.sqlite.org/c3ref/c_status_malloc_size.html.

См. также
sqlite3_db_status(), sqlite3_memory_highwater()
sqlite3_step()
Выполнение подготовленного оператора
Определение
int sqlite3_step( sqlite3_stmt* stmt );
stmt
Подготовленный оператор.
Возвращает
Код состояния SQLite. Если доступна строка результата, будет возвращено SQLITE_ROW. Когда выполнение инструкции завершено, возвращается SQLITE_DONE. Любое другое значение следует рассматривать как ошибку.
Описание

Эта функция выполняет подготовленный оператор. Функция вернет SQLITE_DONE, когда оператор завершит выполнение. В этот момент оператор должен быть сброшен перед повторным вызовом sqlite3_step(). Если подготовленный оператор возвращает значение любого типа, будет возвращен SQLITE_ROW. Это указывает на доступность ряда значений данных. Приложение может извлечь эти значения с помощью функций sqlite3_column_xxx(). Приложение может продолжать вызывать sqlite3_step() до тех пор, пока не вернет SQLITE_DONE. Эта функция никогда не должна возвращать SQLITE_OK.

Многие команды SQL, такие как CREATE TABLE, никогда не возвращают никаких значений SQL. Большинство других команд SQL, таких как INSERT, UPDATE и DELETE, по умолчанию не возвращают никаких значений SQL. Все эти команды SQL обычно выполняют свое действие и возвращают SQLITE_DONE при первом вызове sqlite3_step().

Другие команды, как и многие команды PRAGMA, возвращают одно значение. В этом интерфейсе они возвращаются как таблицы с одним столбцом и одной строкой. Первый вызов sqlite3_step() должен возвращать SQLITE_ROW, а второй вызов должен возвращать SQLITE_DONE. Второй вызов на самом деле не требуется, и приложение может просто сбросить или завершить оператор после получения возвращаемого значения SQL.

Команда SELECT, и некоторые команды PRAGMA вернут полные наборы результатов. Они возвращаются по одной строке за раз с использованием повторных вызовов sqlite3_step().

Приложению не нужно ждать, пока sqlite3_step() вернет SQLITE_DONE, прежде чем оно вызовет sqlite3_reset() или sqlite3_finalize(). Текущее исполнение можно отменить в любой момент.

Если возникает ошибка, результаты зависят от того, как был подготовлен оператор. Рекомендуется, чтобы во всех новых разработках использовались версии sqlite3_prepare() _v2. Это заставляет sqlite3_step() возвращать более конкретные коды ошибок. Для получения дополнительной информации о том, как sqlite3_step() обрабатывает коды ошибок, см. «Подготовка v2».

Если при подключении к базе данных возникают проблемы с параллелизмом, вызовы sqlite3_step() обычно вызывают ошибки. Если требуемая блокировка базы данных в настоящее время недоступна, будет возвращен код SQLITE_BUSY. Правильная работа с этим кодом ошибки - сложная тема. См. «Блокировка базы данных» для получения подробной информации.

См. также
sqlite3_prepare_xxx(), sqlite3_reset(), sqlite3_finalize()
sqlite3_stmt_status()
Получение статуса подготовленного ресурса оператора [EXP]
Определение
int sqlite3_stmt_status( sqlite3_stmt* stmt, int option, int reset );
stmt
Подготовленный оператор.
option
Опция статуса для извлечения.
reset
Если это значение не равно нулю, запрошенное значение состояния сбрасывается после его возврата.
Возвращает
Код результата SQLite.
Описание

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

Доступные параметры включают количество операций сканирования, шага и сортировки. Полный список поддерживаемых в настоящее время параметров см. на http://www.sqlite.org/c3ref/c_stmtstatus_fullscan_step.html.

См. также
sqlite3_status(), sqlite3_db_status()
sqlite3_strnicmp()
Сравнить две строки, игнорируя регистр [EXP]
Определение
int sqlite3_strnicmp( const char* textA, const char* textB, int lenth );
textA, textB
Две строки в кодировке UTF-8.
length
Максимальное количество символов для сравнения.
Возвращает
Отрицательное значение будет возвращено, если textA < textB, нулевое значение будет возвращено, если textA = textB, и положительное значение будет возвращено, если textA> textB.
Описание

Эта функция позволяет приложению сравнивать две строки, используя ту же независимую от регистра логику, которую SQLite использует для внутренних целей. Помните, что это функция сравнения заказов, не функция «равно». Результат, равный нулю (который часто интерпретируется как ложь), указывает на то, что две строки эквивалентны.

Возможность игнорировать регистр букв применяется только к символам с кодом символа 127 или меньше. Поведение этой функции при использовании кодов символов больше 255 в некоторой степени не определено.

См. также
sqlite3_snprintf()
sqlite3_table_column_metadata()
Поиск метаданных расширенного столбца
Определение

int sqlite3_table_column_metadata( sqlite3* db, const char* db_name, const char* tbl_name, const char* col_name, const char** datatype, const char** collation, int* not_null, int* primary_key, int* autoincrement );
db
Подключение к базе данных.
db_name
Логическое имя базы данных в кодировке UTF-8. Имя может быть основным, временным или именем, присвоенным ATTACH DATABASE.
tbl_name
Имя таблицы.
col_name
Имя столбца.
datatype
Ссылка на строку. Объявленный тип данных будет передан обратно. Это тип данных, который появляется в операторе CREATE TABLE.
collation
Ссылка на строку. Объявленное сопоставление будет передано обратно.
not_null
Ссылка на целое число. Если обратно передается ненулевое значение, столбец имеет ограничение NOT NULL.
primary_key
Ссылка на целое число. Если обратно передается ненулевое значение, столбец является частью первичного ключа таблицы.
autoincrement
Ссылка на целое число. Если обратно передается ненулевое значение, для столбца устанавливается значение АВТО УВЕЛИЧЕНИЕ. Это означает, что столбец является псевдонимом ROWID и обозначен как INTEGER PRIMARY KEY.
Возвращает
Код результата SQLite.
Описание

Эта функция используется для получения информации об определенном столбце. Учитывая соединение с базой данных, логическое имя базы данных, имя таблицы и имя столбца, эта функция вернет исходный тип данных (как указано в операторе CREATE TABLE) и имя сопоставления по умолчанию. Также будет возвращен набор флагов, указывающих, имеет ли столбец ограничение NOT NULL, является ли столбец частью ПЕРВИЧНОГО КЛЮЧА таблицы и является ли столбец частью последовательности AUTOINCREMENT.

Если запрашивается информация о столбце ROWID, OID или _OID_, возвращаемые значения зависят от того, имеет ли таблица определенный пользователем столбец псевдонима ROWID, обозначенный как INTEGER PRIMARY KEY. Если в таблице есть такой столбец, информация о столбце, определяемом пользователем, будет передана обратно, как если бы это был обычный столбец. Если в таблице нет столбца псевдонима ROWID, значения («INTEGER», «BINARY», 0, 1, 0) будут переданы обратно в параметрах с пятого по девятый соответственно.

Имена типов данных и параметров сортировки передаются обратно с помощью статических буферов. Приложение не должно освобождать эти буферы. Буферы остаются действительными до тех пор, пока не будет выполнен еще один вызов sqlite3_table_column_meta data().

Эта функция не работает с представлениями. Если приложение попытается запросить информацию о столбце представления, будет возвращена ошибка.

Эта функция доступна только в том случае, если библиотека SQLite была скомпилирована с параметром сборки SQLITE_ENABLE_COL UMN_METADATA.

См. также
table_info [PRAGMA, Ap F], SQLITE_ENABLE_COLUMN_METADATA [Build Opt, Ap A]
sqlite3_threadsafe()
Проверить, является ли SQLite потокобезопасным
Определение
int sqlite3_threadsafe( );
Возвращает
Значение времени компиляции SQLITE_THREADSAFE.
Описание
Эта функция возвращает значение времени компиляции SQLITE_THREADSAFE. Нулевое значение указывает на отсутствие поддержки потоков. Любое другое значение указывает, что доступен некоторый уровень поддержки потоков. Если результат отличен от нуля, конкретный режим потоковой передачи может быть установлен для всей библиотеки с помощью sqlite3_config() или для конкретного соединения с базой данных с помощью sqlite3_open_v2().
См. также
sqlite3_config(), sqlite3_open_v2(), SQLITE_THREADSAFE [Build Opt, Ap A]
sqlite3_total_changes()
Получить общее количество изменений, сделанных подключением к базе данных
Определение
int sqlite3_total_changes( sqlite3* db );
db
Подключение к базе данных.
Возвращает
Количество строк, измененных этим подключением к базе данных с момента его открытия.
Описание

Эта функция возвращает количество строк таблицы, измененных любыми операторами INSERT, UPDATE или DELETE, выполненными с момента открытия соединения с базой данных. Счетчик включает изменения, сделанные триггерами таблиц и действиями внешнего ключа, но не включает удаления, сделанные ограничениями REPLACE, откатами или усеченными таблицами. Подсчитываются строки, которые были успешно изменены в рамках явной транзакции, а затем были выполнены откат.

Эта функция представлена в среде SQL как функция SQL total_changes().

См. также
sqlite3_changes(), count_changes [PRAGMA, Ap F], total_changes() [SQL Func, Ap E]
sqlite3_trace()
Регистрация обратного вызова трассировки оператора SQL [EXP]
Определение
void* sqlite3_trace( sqlite3* db, trace_callback, void* udp);

void trace_callback( void* udp, const char* sql );
db
Подключение к базе данных.
trace_callback
Определяемая приложением функция обратного вызова трассировки.
udp
Указатель пользовательских данных, определяемый приложением. Это значение доступно функции трассировки.
sql
Текст выполненного оператора SQL в кодировке UTF-8.
Возвращает (sqlite3_trace())
Предыдущий указатель пользовательских данных, если применимо.
Описание

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

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

См. также
sqlite3_profile()
sqlite3_unlock_notify()
Установить обратный вызов уведомления о разблокировке [EXP]
Определение
int sqlite3_unlock_notify( sqlite* db, notify_callback, void* arg );

void notify_callback( void** argv, int argc );
db
Подключение к базе данных.
notify_callback
Функция обратного вызова уведомления о разблокировке.
arg
Запись уведомления для конкретного приложения.
argv
Массив записей уведомлений.
argc
Количество записей уведомлений.
Возвращает (sqlite3_unlock_notify())
Код результата SQLite.
Описание

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

Это расширенный вызов API, требующий глубокого понимания режимов потоковой передачи и блокировки, используемых общим кешем. Для получения дополнительной информации см. http://www.sqlite.org/unlock_notify.html.

Эта функция доступна только в том случае, если библиотека SQLite была скомпилирована с параметром сборки SQLITE_ENA BLE_UNLOCK_NOTIFY.

См. также
SQLITE_ENABLE_UNLOCK_NOTIFY [Build Opt, Ap A]
sqlite3_update_hook()
Зарегистрировать обратный вызов обновления
Определение
void* sqlite3_update_hook( sqlite3* db, update_callback, void* udp );

void update_callback( void* udp, int type, const char* db_name, const char* tbl_name, sqlite3_int64 rowid );
db
Подключение к базе данных.
update_callback
Определяемая приложением функция обратного вызова, которая вызывается при изменении строки базы данных.
udp
Указатель пользовательских данных, определяемый приложением. Это значение становится доступным для обратного вызова обновления.
type
Тип обновления базы данных. Возможные значения: SQLITE_INSERT, SQLITE_UPDATE и SQLITE_DELETE.
db_name
Логическое имя изменяемой базы данных. Имена включают main, temp или любое имя, переданное в ATTACH DATABASE.
tbl_name
Имя изменяемой таблицы.
rowid
ROWID изменяемой строки. В случае ОБНОВЛЕНИЯ это значение ROWID после внесения изменений.
Возвращает (sqlite3_update_hook())
Предыдущий указатель пользовательских данных, если применимо.
Описание

Эта функция позволяет приложению регистрировать обратный вызов обновления. Этот обратный вызов вызывается при изменении строки базы данных. Обратный вызов не должен использовать связанное соединение с базой данных для изменения каких-либо баз данных, а также не может вызывать sqlite3_prepare_v2() или sqlite3_step().

Обратный вызов будет вызываться при изменении строк триггером, но не при их удалении из-за разрешения конфликта (например, INSERT OR REPLACE). Обратный вызов не вызывается при изменении системных таблиц (таких как sqlite_master).

См. также
sqlite3_commit_hook(), sqlite3_rollback_hook()
sqlite3_user_data()
Получение указателя данных пользователя из контекста функции SQL
Определение
void* sqlite3_user_data( sqlite3_context* ctx );
ctx
Контекст функции SQL, предоставляемый библиотекой SQLite.
Возвращает
Указатель пользовательских данных, переданный в sqlite3_create_function().
Описание
Эта функция используется реализацией функции SQL для извлечения указателя данных пользователя из контекста функции. Это позволяет функции получить доступ к указателю пользовательских данных, переданному в sqlite3_create_function().
См. также
sqlite3_create_function()
sqlite3_value_xxx()
Получение параметра функции SQL
Определение
const void*          sqlite3_value_blob(     sqlite3_value* value );
double               sqlite3_value_double(   sqlite3_value* value );
int                  sqlite3_value_int(      sqlite3_value* value );
sqlite3_int64        sqlite3_value_int64(    sqlite3_value* value );
const unsigned char* sqlite3_value_text(     sqlite3_value* value );
const void*          sqlite3_value_text16(   sqlite3_value* value );
const void*          sqlite3_value_text16le( sqlite3_value* value );
const void*          sqlite3_value_text16be( sqlite3_value* value );
value
Параметр функции SQL, предоставляемый библиотекой SQLite.
Возвращает
Извлеченное значение.
Описание

Эти функции используются реализацией функции SQL для извлечения значений из параметров функции SQL. Если запрошенный тип отличается от фактического базового значения, значение будет преобразовано с использованием правил преобразования, определенных в таблице 7-1.

SQLite берет на себя все управление памятью для буферов, возвращаемых этими функциями. Возвращенные указатели станут недействительными при возврате из реализации функции или при выполнении другого вызова sqlite3_value_xxx() с тем же значением параметра.

Имейте в виду, что sqlite3_value_int() будет обрезать любые целочисленные значения до 32 бит. Если в функцию SQL передаются значения, которые не могут быть представлены 32-разрядным целым числом со знаком, безопаснее использовать sqlite3_column_int64(). Буфер, возвращаемый sqlite3_value_text() и sqlite3_value_text16(), всегда будет оканчиваться нулем.

В остальном эти функции почти идентичны функциям sqlite3_column_xxx().

См. также
sqlite3_value_bytes(), sqlite3_value_numeric_type(), sqlite3_column_xxx()
sqlite3_value_bytes()
Получить размер параметра функции SQL
Определение
int sqlite3_value_bytes(   sqlite3_value* value );
int sqlite3_value_bytes16( sqlite3_value* value );
value
Значение параметра функции SQL.
Возвращает
Количество байтов в значении параметра функции SQL.
Описание

Эти функции возвращают количество байтов в текстовом значении или значении BLOB. Вызов этих функций может вызвать преобразование типа (недействительность буферов, возвращаемых sqlite3_value_xxx()), поэтому следует позаботиться о том, чтобы вызывать их вместе с соответствующей функцией sqlite3_value_xxx().

Чтобы избежать проблем, приложение должно сначала извлечь нужный тип с помощью функции sqlite3_value_xxx(), а затем вызвать соответствующую функцию sqlite3_value_bytes(). За функциями sqlite3_value_text() и sqlite3_value_blob() должен следовать вызов sqlite3_value_bytes(), а за любым вызовом sqlite3_value_text16xxx() должен следовать вызов sqlite3_value_bytes16().

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

В остальном эти функции почти идентичны функциям sqlite3_column_bytes().

См. также
sqlite3_value_xxx(), sqlite3_column_bytes()
sqlite3_value_numeric_type()
Попытка получить параметр функции SQL в виде числа
Определение
int sqlite3_value_numeric_type( sqlite3_value* value );
value
Значение параметра функции SQL.
Возвращает
Код типа данных.
Описание

Эта функция пытается преобразовать заданное значение параметра в числовой тип. Если значение можно преобразовать в целое число или число с плавающей запятой без потери данных, преобразование будет выполнено и будет возвращен тип SQLITE_INTEGER или SQLITE_FLOAT.

Если преобразование приведет к потере данных, преобразование не выполняется и возвращается исходный тип данных. В этом случае типом может быть SQLITE_TEXT, SQLITE_BLOB или SQLITE_NULL.

Разница между этой функцией и простым вызовом такой функции, как sqlite3_value_int(), заключается в функции преобразования. Эта функция выполнит преобразование только в том случае, если исходное значение полностью использовано и имеет смысл в числовом контексте. Например, sqlite3_value_int() преобразует NULL в значение 0, а эта функция - нет. Точно так же sqlite3_value_int() преобразует строку 123xyz в целочисленное значение 123 и проигнорирует завершающий xyz. Однако эта функция не допускает такого преобразования, потому что конечный xyz не может быть понят в числовом контексте.

См. также
sqlite3_value_xxx(), sqlite3_value_type()
sqlite3_value_type()
Получить тип данных параметра функции SQL
Определение
int sqlite3_value_type( sqlite3_value* value );
value
Значение параметра функции SQL.
Возвращает
Собственный код типа данных значения параметра.
Описание

Эта функция возвращает начальный тип данных параметра функции SQL. Если эта функция используется, онаследует вызывать перед любой функцией sqlite3_value_xxx(). После преобразования типа результат этой функции не определен.

Возвращаемое значение будет SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB или SQLITE_NULL.

В остальном эта функция почти идентична функции sqlite3_column_type().

См. также
sqlite3_value_xxx(), sqlite3_value_numeric_type(), sqlite3_column_type()
sqlite3_version[]
Строка статической версии
Определение
extern const char sqlite3_version[];
Описание

Это не функция, а статический массив символов, содержащий строку версии библиотеки SQLite. Это то же значение, которое возвращает sqlite3_libversion(). Строка версии для SQLite v3.6.23 - «3.6.23». Строка версии для v3.6.23.1 - «3.6.23.1».

Это значение также доступно во время компиляции как макрос SQLITE_VERSION. Сравнивая макрос, используемый во время компиляции, со значением sqlite3_version, найденным в текущей библиотеке, приложение может проверить, что оно связывается с правильной версией библиотеки.

См. также
sqlite3_libversion()
sqlite3_vfs_find()
Получить модуль VFS по имени
Определение
sqlite3_vfs* sqlite3_vfs_find( const char* name );
name
Имя модуля VFS. Если NULL, будет возвращен модуль VFS по умолчанию.
Возвращает
Ссылку на модуль VFS. Если названный модуль не существует, будет возвращено NULL.
Описание

Эта функция используется для поиска определенного модуля VFS по имени. Это позволяет модулю-заглушке или сквозному модулю VFS найти базовую реализацию VFS.

Чтобы использовать настраиваемый модуль VFS с подключением к базе данных, просто передайте имя VFS в sqlite3_open_v2(). Вызов этой функции не требуется.

См. также
sqlite3_vfs_register(), sqlite3_open_v2()
sqlite3_vfs_register()
Регистрация настраиваемого модуля VFS
Определение
int sqlite3_vfs_register( sqlite3_vfs* vfs, int make_default );
vfs
Модуль VFS.
make_default
Если это значение не равно нулю, данный модуль VFS станет модулем по умолчанию.
Возвращает
Код результата SQLite.
Описание

Эта функция регистрирует определяемый приложением модуль VFS в SQLite. Новый модуль также можно сделать по умолчанию для новых подключений к базе данных. Один и тот же модуль (под одним именем) можно безопасно зарегистрировать несколько раз. Чтобы сделать существующий модуль модулем по умолчанию, просто повторно зарегистрируйте его с установленным флагом по умолчанию.

Модули VFS - это сложная тема SQLite. Для получения дополнительной информации см. http://www.sqlite.org/c3ref/vfs.html.

См. также
sqlite3_vfs_find(), sqlite3_vfs_unregister()
sqlite3_vfs_unregister()
Отмена регистрации модуля VFS
Определение
int sqlite3_vfs_unregister( sqlite3_vfs* vfs );
vfs
Модуль VFS.
Возвращает
Код результата SQLite.
Описание
Эта функция используется для отмены регистрации модуля VFS. Модуль не должен использовать соединения с базой данных. Если модуль по умолчанию не зарегистрирован, произвольно будет выбрано новое значение по умолчанию.
См. также
sqlite3_vfs_register()
sqlite3_vmprintf()
Форматирование и выделение строки
Определение
char* sqlite3_vmprintf( const char* format, va_list addl_args );
format
Строка форматирования сообщения в стиле sqlite3_snprintf().
addl_args
Си var-arg список аргументов.
Возвращает
Вновь выделенную строку, построенную на основе заданных параметров.
Описание
Эта функция почти идентична sqlite3_mprintf(), но для этого требуется список аргументов C var-arg, а не переменное количество параметров функции. Это можно использовать для создания специфичных для приложения функций стиля printf().
См. также
sqlite3_mprintf(), sqlite3_snprintf()

Об авторе

Джей Крейбич - профессиональный инженер-программист, которого всегда интересовало, как люди обрабатывают и понимают информацию. В настоящее время он работает в Volition, Inc., студии программного обеспечения, которая специализируется на видеоиграх с открытым миром. Он живет на небольшой ферме в центре Иллинойса с женой и двумя сыновьями, где любит читать, фотографировать и мастерить.

Выходные данные

Животное на обложке Использование SQLite - большая белая цапля (Ardea herodias occidentalis), подвид большой голубой цапли. Долгое время считавшийся отдельным видом (этот вопрос все еще обсуждается некоторыми птичьими экспертами), она отличается более длинным клювом, более короткими перьями у основания головы и, конечно же, полностью белыми перьями. Что еще больше усугубляет путаницу, большую белую цаплю также прозвали большой белой цаплей, однако ноги у цапли черные, а не желто-коричневые, на голове отсутствуют оперения. Большие белые цапли обычно живут около соленой воды, в водно-болотных угодьях Флорида-Кис и Карибского моря. Основной вид обитает также в Мексике, Центральной Америке и даже в Канаде.

Рыба составляет большую часть рациона большой белой цапли, хотя она носит условный характер и также ест лягушек, ящериц, птиц, мелких млекопитающих, ракообразных и водных насекомых. Цапля в основном питается утром и в сумерках, когда рыба наиболее активна. Обладая длинными ногами, птица пробирается сквозь воду и стоит неподвижно, пока не увидит, что что-то приближается к ней. Затем он хватает добычу своим острым клювом и проглатывает ее целиком. Иногда цапли задыхаются от еды, если она слишком велика.

Хотя большие белые цапли - одиночные охотники, на период размножения они собираются в большие колонии, насчитывающие от 5 до 500 гнезд. Самцы выбирают и защищают место для гнездования и устраивают шумные демонстрации, чтобы привлечь самок. У птиц бывает один партнер в год, и самец и самка по очереди насиживают свою кладку. После вылупления яиц ответственность по-прежнему разделяется; оба родителя съедают в четыре раза больше еды, чем обычно, и срыгивают ее своим цыплятам.

Большие синие и большие белые цапли варьируются от 36 до 55 дюймов в высоту с размахом крыльев от 66 до 79 дюймов. Соответственно, во взрослом возрасте у них мало естественных хищников из-за своего размера. Они препятствовали вторжению человеческой цивилизации своей привычкой помогать себе тщательно зарыбленными рыбными прудами.

Изображение на обложке взято из Естественной истории Касселла. Шрифт обложки - Adobe ITC Garamond. Шрифт текста - Linotype Birka; шрифт заголовка - Adobe Myriad Condensed; а шрифт кода - TheSansMonoCondensed от LucasFont.