8.4. Двоичные типы данных
Для хранения двоичных данных предназначен тип bytea
; см. Таблицу 8.6.
Таблица 8.6. Двоичные типы данных
Имя | Размер | Описание |
---|---|---|
bytea | 1 или 4 байта плюс сама двоичная строка | двоичная строка переменной длины |
Двоичные строки представляют собой последовательность октетов (байт) и имеют два отличия от текстовых строк. Во-первых, в двоичных строках можно хранить байты с кодом 0 и другими «непечатаемыми» значениями (обычно это значения вне десятичного диапазона 32..126). В текстовых строках нельзя сохранять нулевые байты, а также значения и последовательности значений, не соответствующие выбранной кодировке базы данных. Во-вторых, в операциях с двоичными строками обрабатываются байты в чистом виде, тогда как текстовые строки обрабатываются в зависимости от языковых стандартов. То есть, двоичные строки больше подходят для данных, которые программист видит как «просто байты», а символьные строки — для хранения текста.
Тип bytea
поддерживает два формата ввода и вывода: «шестнадцатеричный» и традиционный для PostgreSQL формат «спецпоследовательностей». Входные данные принимаются в обоих форматах, а формат выходных данных зависит от параметра конфигурации bytea_output; по умолчанию выбран шестнадцатеричный. (Заметьте, что шестнадцатеричный формат был введён в PostgreSQL 9.0; в ранних версиях и некоторых программах он не будет работать.)
Стандарт SQL определяет другой тип двоичных данных, BLOB
(BINARY LARGE OBJECT
, большой двоичный объект). Его входной формат отличается от форматов bytea
, но функции и операторы в основном те же.
8.4.1. Шестнадцатеричный формат bytea
В «шестнадцатеричном» формате двоичные данные кодируются двумя шестнадцатеричными цифрами на байт, при этом первая цифра соответствует старшим 4 битам. К полученной строке добавляется префикс \x
(чтобы она отличалась от формата спецпоследовательности). В некоторых контекстах обратную косую черту нужно экранировать, продублировав её (см. Подраздел 4.1.2.1). Вводимые шестнадцатеричные цифры могут быть в любом регистре, а между парами цифр допускаются пробельные символы (но не внутри пары и не в начале последовательности \x
). Этот формат совместим со множеством внешних приложений и протоколов, к тому же обычно преобразуется быстрее, поэтому предпочтительнее использовать его.
Пример:
SET bytea_output = 'hex'; SELECT '\xDEADBEEF'::bytea; bytea ------------ \xdeadbeef
8.4.2. Формат спецпоследовательностей bytea
Формат «спецпоследовательностей» традиционно использовался в PostgreSQL для значений типа bytea
. В нём двоичная строка представляется в виде последовательности ASCII-символов, а байты, непредставимые в виде ASCII-символов, передаются в виде спецпоследовательностей. Этот формат может быть удобен, если с точки зрения приложения представление байт в виде символов имеет смысл. Но на практике это обычно создаёт путаницу, так как двоичные и символьные строки могут выглядеть одинаково, а кроме того выбранный механизм спецпоследовательностей довольно неуклюж. Поэтому в новых приложениях этот формат обычно не стоит использовать.
Передавая значения bytea
в формате спецпоследовательности, байты с определёнными значениями необходимо записывать специальным образом, хотя так можно записывать и все значения. В общем виде для этого значение байта нужно преобразовать в трёхзначное восьмеричное число и добавить перед ним обратную косую черту. Саму обратную косую черту (символ с десятичным кодом 92) можно записать в виде двух таких символов. В Таблице 8.7 перечислены символы, которые нужно записывать спецпоследовательностями, и приведены альтернативные варианты записи, если они возможны.
Таблица 8.7. Спецпоследовательности записи значений bytea
Десятичное значение байта | Описание | Спецпоследовательность ввода | Пример | Шестнадцатеричное представление |
---|---|---|---|---|
0 | нулевой байт | '\000' | '\000'::bytea | \x00 |
39 | апостроф | '''' или '\047' | ''''::bytea | \x27 |
92 | обратная косая черта | '\\' или '\134' | '\\'::bytea | \x5c |
от 0 до 31 и от 127 до 255 | «непечатаемые» байты | E'\\ (восьмеричное значение) | '\001'::bytea | \x01 |
Требования экранирования непечатаемых символов определяются языковыми стандартами. Иногда такие символы могут восприниматься и без спецпоследовательностей.
Апострофы должны дублироваться, как показано в Таблице 8.7, потому что это обязательно для любой текстовой строки в команде SQL. При общем разборе текстовой строки внешние апострофы убираются, а каждая пара внутренних сводится к одному символу. Таким образом, функция ввода bytea
видит всего один апостроф, который она обрабатывает как обычный символ в данных. Дублировать же обратную косую черту при вводе bytea
не требуется: этот символ считается особым и меняет поведение функции ввода, как показано в Таблице 8.7.
В некоторых контекстах обратная косая черта должна дублироваться (относительно примеров выше), так как при общем разборе строковых констант пара таких символов будет сведена к одному; см. Подраздел 4.1.2.1.
Данные Bytea
по умолчанию выводятся в шестнадцатеричном формате (hex
). Если поменять значение bytea_output на escape
, «непечатаемые» байты представляются в виде соответствующих трёхзначных восьмеричных значений, которые предваряются одной обратной косой чертой. Большинство «печатаемых» байтов представляются обычными символами из клиентского набора символов, например:
SET bytea_output = 'escape'; SELECT 'abc \153\154\155 \052\251\124'::bytea; bytea ---------------- abc klm *\251T
Байт с десятичным кодом 92 (обратная косая черта) при выводе дублируется. Это иллюстрирует Таблица 8.8.
Таблица 8.8. Спецпоследовательности выходных значений bytea
Десятичное значение байта | Описание | Спецпоследовательность вывода | Пример | Выводимый результат |
---|---|---|---|---|
92 | обратная косая черта | \\ | '\134'::bytea | \\ |
от 0 до 31 и от 127 до 255 | «непечатаемые» байты | \ (значение байта) | '\001'::bytea | \001 |
от 32 до 126 | «печатаемые» байты | представление из клиентского набора символов | '\176'::bytea | ~ |
В зависимости от применяемой клиентской библиотеки PostgreSQL, для преобразования значений bytea
в спецстроки и обратно могут потребоваться дополнительные действия. Например, если приложение сохраняет в строках символы перевода строк, возможно их также нужно будет представить спецпоследовательностями.
39.7. Rules Versus Triggers
Many things that can be done using triggers can also be implemented using the PostgreSQL rule system. One of the things that cannot be implemented by rules are some kinds of constraints, especially foreign keys. It is possible to place a qualified rule that rewrites a command to NOTHING
if the value of a column does not appear in another table. But then the data is silently thrown away and that's not a good idea. If checks for valid values are required, and in the case of an invalid value an error message should be generated, it must be done by a trigger.
In this chapter, we focused on using rules to update views. All of the update rule examples in this chapter can also be implemented using INSTEAD OF
triggers on the views. Writing such triggers is often easier than writing rules, particularly if complex logic is required to perform the update.
For the things that can be implemented by both, which is best depends on the usage of the database. A trigger is fired once for each affected row. A rule modifies the query or generates an additional query. So if many rows are affected in one statement, a rule issuing one extra command is likely to be faster than a trigger that is called for every single row and must re-determine what to do many times. However, the trigger approach is conceptually far simpler than the rule approach, and is easier for novices to get right.
Here we show an example of how the choice of rules versus triggers plays out in one situation. There are two tables:
CREATE TABLE computer ( hostname text, -- indexed manufacturer text -- indexed ); CREATE TABLE software ( software text, -- indexed hostname text -- indexed );
Both tables have many thousands of rows and the indexes on hostname
are unique. The rule or trigger should implement a constraint that deletes rows from software
that reference a deleted computer. The trigger would use this command:
DELETE FROM software WHERE hostname = $1;
Since the trigger is called for each individual row deleted from computer
, it can prepare and save the plan for this command and pass the hostname
value in the parameter. The rule would be written as:
CREATE RULE computer_del AS ON DELETE TO computer DO DELETE FROM software WHERE hostname = OLD.hostname;
Now we look at different types of deletes. In the case of a:
DELETE FROM computer WHERE hostname = 'mypc.local.net';
the table computer
is scanned by index (fast), and the command issued by the trigger would also use an index scan (also fast). The extra command from the rule would be:
DELETE FROM software WHERE computer.hostname = 'mypc.local.net' AND software.hostname = computer.hostname;
Since there are appropriate indexes set up, the planner will create a plan of
Nestloop -> Index Scan using comp_hostidx on computer -> Index Scan using soft_hostidx on software
So there would be not that much difference in speed between the trigger and the rule implementation.
With the next delete we want to get rid of all the 2000 computers where the hostname
starts with old
. There are two possible commands to do that. One is:
DELETE FROM computer WHERE hostname >= 'old' AND hostname < 'ole'
The command added by the rule will be:
DELETE FROM software WHERE computer.hostname >= 'old' AND computer.hostname < 'ole' AND software.hostname = computer.hostname;
with the plan
Hash Join -> Seq Scan on software -> Hash -> Index Scan using comp_hostidx on computer
The other possible command is:
DELETE FROM computer WHERE hostname ~ '^old';
which results in the following executing plan for the command added by the rule:
Nestloop -> Index Scan using comp_hostidx on computer -> Index Scan using soft_hostidx on software
This shows, that the planner does not realize that the qualification for hostname
in computer
could also be used for an index scan on software
when there are multiple qualification expressions combined with AND
, which is what it does in the regular-expression version of the command. The trigger will get invoked once for each of the 2000 old computers that have to be deleted, and that will result in one index scan over computer
and 2000 index scans over software
. The rule implementation will do it with two commands that use indexes. And it depends on the overall size of the table software
whether the rule will still be faster in the sequential scan situation. 2000 command executions from the trigger over the SPI manager take some time, even if all the index blocks will soon be in the cache.
The last command we look at is:
DELETE FROM computer WHERE manufacturer = 'bim';
Again this could result in many rows to be deleted from computer
. So the trigger will again run many commands through the executor. The command generated by the rule will be:
DELETE FROM software WHERE computer.manufacturer = 'bim' AND software.hostname = computer.hostname;
The plan for that command will again be the nested loop over two index scans, only using a different index on computer
:
Nestloop -> Index Scan using comp_manufidx on computer -> Index Scan using soft_hostidx on software
In any of these cases, the extra commands from the rule system will be more or less independent from the number of affected rows in a command.
The summary is, rules will only be significantly slower than triggers if their actions result in large and badly qualified joins, a situation where the planner fails.