Встроенная функция printf() в SQLite
Содержание
1. Обзор
SQLite содержит собственную реализацию функции форматирования строк "printf()", доступную через следующие интерфейсы:
- format() → SQL-функция, возвращающая отформатированную строку
- sqlite3_mprintf() → Сохраняет отформатированную строку в памяти, полученной с помощью sqlite3_malloc64().
- sqlite3_snprintf() → Сохраняет отформатированную строку в статическом буфере
- sqlite3_str_appendf() → Добавляет отформатированный текст к динамической строке
- sqlite3_vmprintf() → Версия sqlite3_mprintf() с использованием переменного числа аргументов
- sqlite3_vsnprintf() → Версия sqlite3_snprintf() с использованием переменного числа аргументов
- sqlite3_str_vappendf() → Версия sqlite3_str_appendf() с использованием переменного числа аргументов
Та же основная функция форматирования строк используется внутри SQLite.
1.1. Преимущества
Почему в SQLite есть собственная реализация printf()? Почему не использовать реализацию из стандартной библиотеки C? Причин несколько:
Используя собственную реализацию, SQLite гарантирует, что вывод будет одинаковым на всех платформах и во всех локалях. Это важно для согласованности и тестирования. Было бы проблематично, если на одной машине результат был "5.25e+08", а на другой "5.250e+008". Оба результата верны, но лучше, когда SQLite всегда выдает один и тот же результат.
Нам неизвестно, как использовать стандартный интерфейс printf() библиотеки C для реализации функции форматирования SQL (format()) SQLite. Однако встроенная реализация printf() может быть легко адаптирована для этой задачи.
printf() в SQLite поддерживает новые нестандартные типы подстановок (%q, %Q, %w и %z), а также улучшенные параметры подстановки (%s и %z), которые полезны как внутри SQLite, так и для приложений, использующих SQLite. Стандартные реализации printf() обычно не могут быть расширены таким образом.
Через интерфейсы sqlite3_mprintf() и sqlite3_vmprintf() встроенная реализация printf() поддерживает возможность вывода строки произвольной длины в буфер памяти, полученный из sqlite3_malloc64(). Это безопаснее и менее подвержено ошибкам, чем попытка заранее рассчитать максимальный размер строки, выделить буфер соответствующего размера и вызвать snprintf().
Специфичная для SQLite функция printf() поддерживает новый флаг (!) "формат-2". Флаг формат-2 изменяет обработку преобразований чисел с плавающей запятой, что обеспечивает, что вывод всегда представляет собой SQL-совместимое текстовое представление числа с плавающей запятой — чего нельзя достичь с помощью стандартной функции printf(). Для подстановок строк флаг формат-2 приводит к тому, что ширина и точность измеряются в символах, а не байтах, что упрощает обработку строк, содержащих многобайтные UTF8-символы.
В SQLite существуют параметры компиляции, такие как SQLITE_PRINTF_PRECISION_LIMIT, которые обеспечивают защиту от атак типа "отказ в обслуживании" для приложений, которые предоставляют функциональность printf() недоверенным пользователям.
Использование собственной реализации printf() означает, что у SQLite на одну зависимость от среды разработки меньше, что повышает ее переносимость.
1.2. Недостатки
Честно говоря, наличие встроенной реализации printf() также имеет некоторые недостатки:
Встроенная реализация printf() использует дополнительное пространство кода (примерно 7800 байт в GCC 5.4 с оптимизацией -Os).
Подфункция преобразования чисел с плавающей запятой в текст для встроенной реализации printf() имеет ограниченную точность до 16 значащих цифр или 26 значащих цифр при использовании флага "формат-2". Любое число с плавающей запятой IEEE-754 может быть точно представлено в десятичном виде, но для многих чисел с плавающей запятой точное десятичное представление требует более 16 или 26 значащих цифр. Функция printf() SQLite отображает только первые 16 или 26 значащих цифр, так как это можно сделать эффективно, и 16 десятичных знаков достаточно для различения всех возможных значений double. Используйте расширение decimal, чтобы получить точное десятичное эквивалентное значение double в тех редких случаях, когда это требуется.
Порядок параметров указателя буфера и размера буфера в реализации snprintf() встроенной функции printf() обратный по сравнению со стандартными реализациями.
Встроенная реализация printf() не обрабатывает позиционные модификаторы POSIX, которые позволяют изменять порядок аргументов printf() по сравнению с порядком %-подстановок. Во встроенной реализации printf() порядок аргументов должен точно соответствовать порядку %-подстановок.
Несмотря на недостатки, разработчики считают, что наличие встроенной реализации printf() внутри SQLite является положительным моментом.
2. Подробности форматирования
Строка форматирования для printf() — это шаблон для генерируемой строки. Подстановки производятся всякий раз, когда в строке форматирования появляется символ "%". Символ "%" следует за одним или несколькими дополнительными символами, описывающими подстановку. Каждая подстановка имеет следующий формат:
%[flags][width][.precision][length]type
Все подстановки начинаются с одиночного "%" и заканчиваются одиночным символом типа. Другие элементы подстановки необязательны.
Чтобы включить одиночный символ "%" в вывод, в шаблоне нужно поместить два последовательных символа "%".
2.1. Типы подстановки
В следующей таблице показаны типы подстановок, поддерживаемые SQLite:
| Тип подстановки | Значение |
|---|---|
| % | Два символа "%" подряд преобразуются в один символ "%" в выводе без подстановки каких-либо значений. |
| d, i | Аргумент — целое число со знаком, отображаемое в десятичной системе счисления. |
| u | Аргумент — целое число без знака, отображаемое в десятичной системе счисления. |
| f | Аргумент — число с плавающей запятой, отображаемое в десятичной системе счисления. |
| e, E | Аргумент — число с плавающей запятой, отображаемое в экспоненциальной форме. Символ экспоненты — 'e' или 'E' в зависимости от типа. |
| g, G | Аргумент — число с плавающей запятой, отображаемое либо в обычном десятичном формате, либо, если показатель степени не близок к нулю, в экспоненциальной форме. |
| x, X | Аргумент — целое число, отображаемое в шестнадцатеричной системе счисления. Используется строчная шестнадцатеричная запись для %x и прописная для %X. |
| o | Аргумент — целое число, отображаемое в восьмеричной системе счисления. |
| s, z | Аргумент — либо строка с нулевым завершением, которая отображается, либо указатель null, который обрабатывается как пустая строка. Для типа %z в интерфейсах языка C вызывается sqlite3_free() для строки после того, как она была скопирована в вывод. Подстановки %s и %z идентичны для SQL-функции printf(), при этом аргумент null обрабатывается как пустая строка. Подстановка %s универсальна для функций printf(), но подстановка %z и безопасное обращение с указателями null — это расширения SQLite, отсутствующие в других реализациях printf(). |
| c | В интерфейсах языка C аргумент — целое число, интерпретируемое как символ. Для SQL-функции format() аргумент — строка, из которой извлекается и отображается первый символ. |
| p | Аргумент — указатель, отображаемый как шестнадцатеричный адрес. Поскольку язык SQL не имеет понятия об указателе, подстановка %p для функции format() SQL работает как %x. |
| n | Аргумент — указатель на целое число. Для этого типа подстановки ничего не отображается. Вместо этого целое число, на которое указывает аргумент, перезаписывается числом символов в сгенерированной строке, полученной от всех символов форматирования слева от %n. |
| q, Q | Аргумент — строка с нулевым завершением. Строка печатается с удвоением всех символов одинарной кавычки ('), чтобы строка могла безопасно отображаться внутри SQL-строкового литерала. Тип подстановки %Q также помещает одинарные кавычки в начало и конец подставляемой строки. Если аргумент для %Q — null-указатель, то выводом является нецитированное "NULL". Иными словами, нулевой указатель генерирует SQL NULL, а ненулевой — допустимый SQL-строковый литерал. Если аргумент для %q — нулевой указатель, то вывод не генерируется. Таким образом, нулевой указатель для %q эквивалентен пустой строке. Для этих подстановок точность — это количество байтов или символов, взятых из аргумента, а не количество байтов или символов, записанных в вывод. Подстановки %q и %Q — расширения SQLite, отсутствующие в большинстве других реализаций printf(). |
| w | Эта подстановка работает как %q, за исключением того, что она удваивает все символы двойной кавычки ("), делая результат подходящим для использования с двойной кавычкой для имени идентификатора в SQL-запросе. Подстановка %w — расширение SQLite, отсутствующее в большинстве других реализаций printf(). |
2.2. Необязательное поле длины
Длина значения аргумента может быть указана одной или несколькими буквами, которые появляются непосредственно перед символом типа подстановки. В SQLite длина важна только для целочисленных типов. Длина игнорируется для функции format() SQL, которая всегда использует 64-битные значения.
| Длина спецификатора | Значение |
|---|---|
| (по умолчанию) | "int" или "unsigned int". 32 бита на всех современных системах. |
| l | "long int" или "long unsigned int". Также 32 бита на всех современных системах. |
| ll | "long long int" или "long long unsigned" или значение "sqlite3_int64" или "sqlite3_uint64". Это 64-битные целые числа на всех современных системах. |
Только модификатор длины "ll" когда-либо вносит различия в SQLite. И он вносит различия только при использовании интерфейсов языка C.
2.3. Поле ширины (необязательное)
Поле ширины указывает минимальную ширину подставляемого значения в выводе. Если строка или число, которые записываются в вывод, короче ширины, то значение дополняется. Дополнение происходит слева (значение выравнивается по правому краю) по умолчанию. Если используется флаг "-", то дополнение происходит справа, и значение выравнивается по левому краю.
Ширина измеряется в байтах по умолчанию. Однако, если присутствует флаг "!", то ширина измеряется в символах. Это имеет значение только для многобайтовых utf-8 символов, и они встречаются только при подстановке строк.
Если ширина представляет собой единственный символ "*" вместо числа, то фактическое значение ширины считывается как целое число из списка аргументов. Если считанное значение отрицательно, то абсолютное значение используется для ширины, и значение выравнивается по левому краю, как если бы присутствовал флаг "-".
Если подставляемое значение больше ширины, то полное значение добавляется в вывод. Другими словами, ширина является минимальной шириной значения, так как оно отображается в выводе.
2.4. Поле точности (необязательное)
Поле точности, если оно присутствует, должно следовать за шириной, отделённое одиночной точкой (".") символом. Если ширина отсутствует, то "." для ввода точности следует непосредственно за флагами (если они есть) или начальным символом "%" .
Для подстановок строк %s, %z, %q, %Q или %w точность — это число байт или символов, используемых из аргумента. По умолчанию количество байт, но если присутствует флаг "!", то количество символов. Если точность отсутствует, то подставляется вся строка. Примеры: "%.3s" подставляет первые 3 байта строки аргумента. "%!.3s" подставляет первые три символа строки аргумента.
Для подстановок целых чисел %d, %i, %x, %X, %o и %p точность определяет минимальное количество отображаемых цифр. В случае необходимости добавляются ведущие нули, чтобы расширить вывод до минимального количества цифр.
Для подстановок чисел с плавающей запятой %e, %E и %f точность определяет количество цифр после десятичной точки. Для %g и %G точность — это общее количество значащих цифр, округлённых до 1, если указанная точность равна 0.
Для подстановки символа %c точность N, большая 1, приводит к повторению символа N раз. Это нестандартное расширение, встречающееся только в SQLite.
Если точность представлена одиночным символом "*" вместо числа, то фактическое значение точности считывается как целое число из списка аргументов.
2.5. Поле флагов опций
Флаги состоят из одного или нескольких символов, которые непосредственно следуют за символом "%", вводящим подстановку. Различные флаги и их значения приведены ниже:
| Флаг | Значение |
|---|---|
| - | Выравнивание значения по левому краю в выводе. По умолчанию выравнивание по правому краю. Если ширина равна нулю или меньше длины подставляемого значения, то дополнение не выполняется, и флаг "-" не оказывает никакого влияния. |
| + | Для подстановок чисел со знаком включается знак "+" перед положительными числами. Знак "-" всегда отображается перед отрицательными числами, независимо от настроек флагов. |
| (пробел) | Для подстановок чисел со знаком добавляется пробел перед положительными числами. |
| 0 | (опция нулевого заполнения) Добавляется столько символов "0" к числовым подстановкам, сколько необходимо, чтобы расширить значение до указанной ширины. Если поле ширины опущено, то этот флаг не имеет эффекта. Для бесконечности и NaN (Not-A-Number) числа с плавающей запятой обычно отображаются как "Inf" и "NaN" соответственно, но с включённой опцией нулевого заполнения они отображаются как "9.0e+999" и "null" соответственно. Другими словами, с опцией нулевого заполнения числа с плавающей запятой Infinity и NaN отображаются как корректные SQL и JSON литералы. |
| # | Этот флаг — "альтернативная форма-1". Для подстановок %g и %G это приводит к удалению последующих нулей. Этот флаг требует отображения десятичной точки для всех подстановок чисел с плавающей запятой. Для подстановок %o, %x и %X флаг альтернативной формы-1 заставляет предварять значение символами "0", "0x" или "0X" соответственно. |
| , | Опция запятой добавляет разделители запятыми к результату числовых подстановок (%d, %f и т.п.) перед каждой третьей цифрой слева от десятичной точки. Для цифр справа от десятичной точки запятые не добавляются. Это помогает лучше воспринимать масштаб больших целочисленных значений. Например, значение 2147483647 отображается как "2147483647" при использовании "%d", но с опцией запятой отображается как "2 147 483 647". Этот флаг является нестандартным расширением. |
| ! | Это флаг "альтернативная форма-2". Для подстановок строк этот флаг заставляет ширину и точность понимать в терминах символов, а не байтов. Для подстановок чисел с плавающей запятой флаг альтернативной формы-2 увеличивает максимальное число отображаемых значащих цифр с 16 до 26, требует отображения десятичной точки и заставляет отобразиться по крайней мере одну цифру после десятичной точки. Флаг альтернативной формы-2 является нестандартным расширением, которое не встречается в других реализациях printf(), насколько нам известно. |
3. Реализация и история
Основная процедура форматирования строк — функция sqlite3VXPrintf() в файле исходного кода printf.c. Все различные интерфейсы вызывают (иногда косвенно) эту основную функцию. Функция sqlite3VXPrintf() изначально была кодом, написанным первым автором SQLite (Хиппом) во время учёбы в аспирантуре в Университете Дьюка в конце 1980-х. Хипп сохранял эту реализацию printf() в своём личном инструментарии до начала работы над SQLite в 2000 году. Код был включён в дерево исходного кода SQLite 08 октября 2000 года для версии SQLite 1.0.9.
Система управления версиями Fossil использует собственную реализацию printf(), которая основана на ранней версии реализации printf() SQLite, но с тех пор эти две реализации разошлись.
Функция sqlite3_snprintf() имеет переставленные аргументы указателя буфера и размера буфера по сравнению со стандартной функцией snprintf() в стандартной библиотеке C. Это связано с тем, что функция snprintf() отсутствовала в стандартной библиотеке C, когда Хипп впервые разрабатывал свою версию, и он выбрал другой порядок, чем разработчики стандартной библиотеки C.
Эта страница была в последний раз изменена 15 июля 2024 года в 21:17:15 UTC
SQLite is in the Public Domain.
https://sqlite.org/printf.html