Spec-Zone.ru › SQLite

Встроенная функция printf() в SQLite

Содержание
1. Обзор
1.1. Преимущества
1.2. Недостатки
2. Подробности форматирования
2.1. Типы подстановок
2.2. Необязательное поле длины
2.3. Необязательное поле ширины
2.4. Необязательное поле точности
2.5. Поле флагов параметров
3. Реализация и история

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()? Почему бы не использовать реализацию printf() из стандартной библиотеки C? На это есть несколько причин:

  1. Используя собственную встроенную реализацию, SQLite гарантирует одинаковый вывод на всех платформах и во всех локалях. Это важно для согласованности и тестирования. Было бы проблематично, если бы на одной машине результатом было "5.25e+08", а на другой — "5.250e+008". Оба результата корректны, но лучше, когда SQLite всегда возвращает один и тот же результат.

  2. Нам неизвестен способ использовать интерфейс printf() стандартной библиотеки C для реализации функции format() в SQL. Однако встроенную реализацию printf() можно легко адаптировать для этой задачи.

  3. Функция printf() в SQLite поддерживает новые нестандартные типы подстановок (%q, %Q, %w и %z), а также расширенное поведение подстановок (%s и %z), полезное как внутри SQLite, так и для приложений, использующих SQLite. Реализации printf() из стандартных библиотек обычно нельзя расширить таким образом.

  4. Благодаря интерфейсам sqlite3_mprintf() и sqlite3_vmprintf() встроенная реализация printf() позволяет записать строку произвольной длины в буфер памяти, выделенный с помощью sqlite3_malloc64(). Это безопаснее и позволяет избежать ошибок по сравнению с попыткой заранее вычислить верхнюю границу размера результирующей строки, выделить буфер подходящего размера, а затем вызвать snprintf().

  5. Специфичная для SQLite функция printf() поддерживает новый флаг (!) под названием «alternate-form-2». Флаг alternate-form-2 слегка меняет обработку преобразований чисел с плавающей точкой, благодаря чему вывод всегда представляет собой текстовое представление числа с плавающей точкой, совместимое с SQL, — чего невозможно добиться с помощью printf() из стандартной библиотеки. Для строковых подстановок флаг alternate-form-2 задаёт измерение ширины и точности в символах, а не в байтах, что упрощает обработку строк, содержащих многобайтовые символы UTF-8.

  6. Встроенная реализация SQLite поддерживает параметры времени компиляции, такие как SQLITE_PRINTF_PRECISION_LIMIT, которые защищают от атак типа «отказ в обслуживании» приложения, предоставляющие функциональность printf() недоверенным пользователям.

  7. Использование встроенной реализации printf() означает, что у SQLite на одну зависимость от среды хоста меньше, что повышает переносимость.

1.2. Недостатки

Справедливости ради следует сказать, что у встроенной реализации printf() есть и некоторые недостатки. А именно:

  1. Встроенная реализация printf() занимает дополнительное место в коде (около 7800 байт при использовании GCC 5.4 с -Os).

  2. Подфункция преобразования чисел с плавающей точкой в текст во встроенной реализации printf() ограничена точностью в 16 значащих цифр или в 26 значащих цифр, если используется флаг alternate-form-2 "!". Любое число типа IEEE-754 double можно точно представить в виде десятичного значения, однако для многих значений double точное десятичное представление требует более 16 или 26 значащих цифр. Функция printf() в SQLite выводит только первые 16 или 26 значащих цифр, поскольку это можно сделать эффективно, а 16 десятичных цифр достаточно, чтобы различить любые значения double. В редких случаях, когда требуется точный десятичный эквивалент значения double, используйте десятичное расширение.

  3. Встроенная реализация snprintf() принимает указатель на буфер и размер буфера в порядке, обратном порядку, используемому реализациями стандартных библиотек.

  4. Встроенная реализация printf() не обрабатывает модификаторы позиционной адресации posix, которые позволяют указывать аргументы printf() не в том порядке, в каком идут подстановки с символом %. Во встроенной реализации printf() порядок аргументов должен точно соответствовать порядку подстановок с символом %.

Несмотря на эти недостатки, разработчики считают, что наличие встроенной реализации printf() в SQLite в целом приносит больше пользы, чем вреда.

2. Подробности форматирования

Строка формата printf() — это шаблон для создаваемой строки. Подстановки выполняются при появлении символа "%" в строке формата. За символом "%" следуют один или несколько символов, описывающих подстановку. Каждая подстановка имеет следующий формат:

 %[flags][width][.precision][length]type 

Все подстановки начинаются с одного символа "%" и заканчиваются одним символом типа. Остальные элементы подстановки необязательны.

Чтобы включить в вывод один символ "%", укажите в шаблоне два символа "%" подряд.

2.1. Типы подстановок

В следующей таблице перечислены типы подстановок, поддерживаемые SQLite:

Тип подстановки Значение
% Два символа "%" подряд преобразуются в один символ "%" в выводе; никакие значения не подставляются.
d, i Аргумент — знаковое целое число, выводимое в десятичной системе счисления.
u Аргумент — беззнаковое целое число, выводимое в десятичной системе счисления.
f Аргумент — число double, выводимое в десятичной системе счисления.
e, E Аргумент — число double, выводимое в экспоненциальной форме. В зависимости от типа символом экспоненты будет 'e' или 'E'.
g, G Аргумент — число double, выводимое либо в обычной десятичной форме, либо в экспоненциальной форме, если показатель экспоненты достаточно далёк от нуля.
x, X Аргумент — целое число, выводимое в шестнадцатеричной системе счисления. Для %x используются строчные шестнадцатеричные цифры, а для %X — прописные.
o Аргумент — целое число, выводимое в восьмеричной системе счисления.
s, z Аргумент — это либо строка, завершающаяся нулём, которая выводится, либо нулевой указатель, который трактуется как пустая строка. Для типа %z в интерфейсах языка C функция sqlite3_free() вызывается для строки после её копирования в вывод. Подстановки %s и %z идентичны в SQL-функции printf(); аргумент NULL трактуется как пустая строка.

Подстановка %s используется во всех функциях printf(), но подстановка %z и безопасная обработка нулевых указателей — это расширения SQLite, отсутствующие в других реализациях printf(). Внимание: в последних версиях GNU printf для вывода целых значений size_t используется %z. Это несовместимо с использованием %z в SQLite.
c Для интерфейсов языка C аргумент — целое число, интерпретируемое как символ. Для SQL-функции format() аргумент — строка, из которой извлекается и выводится первый символ.
p Аргумент — указатель, выводимый как шестнадцатеричный адрес. Поскольку в языке SQL нет понятия указателя, подстановка %p для SQL-функции format() работает как %x.
n Аргумент — указатель на целое число. Для этого типа подстановки ничего не выводится. Вместо этого целое число, на которое указывает аргумент, перезаписывается количеством символов в созданной строке, полученных в результате обработки всех символов формата слева от %n.
q, Q Аргумент — строка, завершающаяся нулём. При выводе все одинарные кавычки (') в строке удваиваются, чтобы строку можно было безопасно использовать внутри строкового литерала SQL. При подстановке типа %Q одинарные кавычки также добавляются в начало и конец подставленной строки.

Если аргумент %Q является нулевым указателем, выводом будет не заключённое в кавычки значение "NULL". Иными словами, нулевой указатель создаёт значение SQL NULL, а ненулевой указатель — допустимый строковый литерал SQL. Если аргумент %q является нулевым указателем, вывод не создаётся. Таким образом, нулевой указатель для %q эквивалентен пустой строке.

Если к %Q добавить флаг alternate-form-1 ("#") (например, "%#Q"), управляющие коды в диапазоне от U+0001 до U+001f экранируются с помощью обратных косых черт в стиле JSON, а весь вывод заключается в "unistr('...')". Таким образом, полученный вывод безопасно отображать на устройствах, которые интерпретируют управляющие последовательности ANSI. Преобразование "%#q" аналогичным образом экранирует управляющие символы, но не добавляет обёртку функции unistr().

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

Подстановки %q и %Q — это расширения SQLite, отсутствующие в большинстве других реализаций printf().
w Эта подстановка работает как %q, но вместо одинарных кавычек удваивает все двойные кавычки ("), благодаря чему результат подходит для использования с именем идентификатора в двойных кавычках в инструкции SQL.

Подстановка %w — это расширение SQLite, отсутствующее в большинстве других реализаций printf().

2.2. Необязательное поле длины

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

Модификатор длины Значение
(по умолчанию) "int" или "unsigned int". 32 бита на всех современных системах.
l "long int" или "long unsigned int". Также 32 бита на всех современных системах.
ll "long long int", "long long unsigned", значение "sqlite3_int64" или "sqlite3_uint64". На всех современных системах это 64-битные целые числа.

В SQLite значение имеет только модификатор длины "ll". И только при использовании интерфейсов языка 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 точность задаёт общее количество значащих цифр; если указанная точность равна 0, она округляется до 1.

Для подстановки символа %c точность N, превышающая 1, приводит к повторению символа N раз. Это нестандартное расширение, встречающееся только в SQLite.

Если вместо числа в поле точности указан один символ "*", фактическое значение точности считывается как целое число из списка аргументов.

2.5. Поле флагов параметров

Флаги состоят из нуля или нескольких символов, непосредственно следующих за символом "%", который вводит подстановку. Ниже перечислены различные флаги и их значения:

Флаг Значение
- Выравнивает значение по левому краю в выводе. По умолчанию значение выравнивается по правому краю. Если ширина равна нулю или меньше длины подставляемого значения, дополнение не выполняется и флаг "-" ни на что не влияет.
+ Для подстановок знаковых чисел добавляет знак "+" перед положительными числами. Перед отрицательными числами знак "-" выводится всегда, независимо от настроек флагов.
(пробел) Для подстановок знаковых чисел добавляет один пробел перед положительными числами.
0 (Параметр дополнения нулями) Добавляет перед числовыми подстановками столько символов "0", сколько нужно, чтобы довести значение до заданной ширины. Если поле ширины не указано, флаг ни на что не влияет. Бесконечность и NaN (не число) обычно выводятся как "Inf" и "NaN" соответственно, но при включённом дополнении нулями выводятся как "9.0e+999" и "null". Иными словами, при дополнении нулями бесконечность и NaN с плавающей точкой выводятся как допустимые литералы SQL и JSON.
# Это флаг «alternate-form-1». Его значение зависит от конкретного преобразования.
  • Для подстановок %#g и %#G удаляет завершающие нули.
  • Для подстановки %#f знаки минуса подавляются, если флаг "+" не указан и все выводимые цифры равны нулю. Так, например, '%#.2f' выводит значение -0.004 как "0.00", а не "-0.00".
  • Этот флаг принудительно добавляет десятичную точку для всех подстановок чисел с плавающей точкой.
  • Для подстановок %#o, %#x и %#X флаг alternate-form-1 добавляет перед значением соответственно "0", "0x" или "0X".
  • Для %#Q флаг alternate-form-1 приводит к тому, что строка, содержащая управляющие коды C0 (символы ASCII от 0x01 до 0x1f), выводится в виде вызова SQL-функции unistr() со строковым литералом в качестве аргумента, содержащим экранированные обратной косой чертой последовательности в стиле JSON для кодирования этих управляющих кодов и самого символа "\".
  • Для %#q создаваемый текст дополняется экранированными обратной косой чертой последовательностями для любых управляющих кодов и символа '\', но обёртка unistr('...') не добавляется, поскольку %q предназначен для использования внутри более крупного строкового литерала и не задаёт граничные маркеры.
, Этот флаг добавляет в вывод числовых подстановок (%d, %f и подобных) разделители-запятые перед каждой третьей цифрой слева от десятичной точки. Справа от десятичной точки запятые не добавляются. Это может помочь людям легче оценивать величину больших целых чисел. Например, значение 2147483647 при использовании "%d" будет выведено как "2147483647", а при использовании "%,d" — как "2,147,483,647". Этот флаг является нестандартным расширением.
! Это флаг «alternate-form-2». Для строковых подстановок он задаёт измерение ширины и точности в символах, а не в байтах. Для подстановок чисел с плавающей точкой флаг alternate-form-2 увеличивает максимальное количество выводимых значащих цифр с 16 до 26, принудительно добавляет десятичную точку и обеспечивает вывод как минимум одной цифры после десятичной точки.

Насколько нам известно, флаг alternate-form-2 — нестандартное расширение, отсутствующее во всех остальных реализациях printf().

3. Реализация и история

Базовая процедура форматирования строк — функция sqlite3VXPrintf(), находящаяся в исходном файле printf.c. Все интерфейсы вызывают эту основную функцию (иногда косвенно). Функция sqlite3VXPrintf() была написана первым автором SQLite (Хиппом) в конце 1980-х годов, когда он был аспирантом Университета Дьюка. Хипп хранил эту реализацию printf() в своём личном наборе инструментов, пока в 2000 году не начал работать над SQLite. Код был включён в дерево исходных файлов SQLite 2000-10-08 для версии SQLite 1.0.9.

В системе управления версиями Fossil используется собственная реализация printf(), созданная на основе ранней версии реализации printf() из SQLite, однако с тех пор эти две реализации разошлись.

В функции sqlite3_snprintf() аргументы — указатель на буфер и размер буфера — идут в порядке, обратном порядку, используемому в процедуре snprintf() стандартной библиотеки C. Это объясняется тем, что в стандартной библиотеке C не было процедуры snprintf(), когда Хипп впервые создавал свою версию, и он выбрал порядок, отличный от порядка, выбранного разработчиками стандартной библиотеки C.

Эта страница в последний раз обновлялась 2025-11-13 07:12:58Z

SQLite is in the Public Domain.
https://sqlite.org/printf.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API