Как именно работает однобайтовый тип char в PostgreSQL?

9

Я часто вижу, как люди говорят "char". Я никогда не использовал это. Это определено в документах как,

Тип "char" (обратите внимание на кавычки) отличается от char (1) тем, что он использует только один байт памяти. Он используется внутри системных каталогов как упрощенный тип перечисления.

И далее,

"char"  1 byte  single-byte internal type

Итак, если это один байт, что такое домен и как бы вы его использовали? Это подписано или не подписано? В этом посте @Erwin Brandstetter он излагает это , но я все еще в замешательстве. Он использует ascii()и chr(), и обеспечивает это

SELECT i
     , chr(i)::"char"        AS i_encoded
     , ascii(chr(i)::"char") AS i_decoded
FROM   generate_series(1,256) i;

Это делает что-то действительно странное между 10 и 11.

  i  | i_encoded | i_decoded 
-----+-----------+-----------
...
   8 | \x08      |         8
   9 |           |         9
  10 |          +|        10
     |           |           -- WTF is going on here.
  11 | \x0B      |        11
  12 | \x0C      |        12
...

Это также становится действительно странным:

 126 | ~         |       126
 127 | \x7F      |       127
 128 |           |       128
 129 |           |       128
 130 |           |       128
 131 |           |       128

Почему все к северу от 128 декодируется как 128? Но чтобы немного поднять причуду, после 192 года происходит переключение, и они декодируются как 192.

 190 |           |       128
 191 |           |       128
 192 |           |       192
 193 |           |       192
 194 |           |       192
 195 |           |       192
 196 |           |       192
 197 |           |       192

Эрвин говорит

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

Я не уверен, почему мы вообще должны кодировать, хотя, если мы делаем именно то, что задает этот вопрос

CREATE TABLE foo AS
SELECT i::"char"
FROM   generate_series(-128,127) i;

Это отлично работает. Мы можем получить обратно, используя

SELECT i::int FROM foo;

Короче говоря,

  1. Что код Эрвина делает между 10-11, где я становится нулевым?
  2. Почему 128 повторяется так много раз?
  3. Почему 192 повторяется так много раз?
  4. Как вызвать неспособность хранить 0, когда Эрвин говорит, что вы не можете кодировать 0 таким образом (нулевой символ не разрешен)

    CREATE TABLE foo AS SELECT 0::int::"char" AS x;
    SELECT x::int FROM foo;
     x 
    ---
    0
Эван Кэрролл
источник

Ответы:

11

1. chr(10)

... создает символ LINEFEED (иначе escape-последовательность \n), а psql отображает символ с новой строкой (обозначенной +). Там все правильно.

2. & 3. ascii()производит 128 или 192?

Это начинается с ошибки, которую я сделал. Я небрежно предположить , "char"будет охватывать диапазон в беззнаковое 1-байтовое целое число ( от 0 до 255) в ссылочного ответа (исправлено), но это на самом деле диапазон в подписанном 1-байтовое целое число (-128 до 127) внутренне.

ascii()принимает textпараметр, неявное приведение из "char"к textпроизводит многобайтовый символ в юникоде, и функция возвращает ( согласно документации наascii() ):

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

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

4. Нулевой байт

Невозможность хранения нулевых байтов влияет только на регулярные типы символов ( text, char, varchar), а не "char". Это относится к моему глючному примеру, потому что я брошен в textкачестве ступеньки. При наложении между "char"и integerнепосредственно ограничение не применяется. Руководство по chr():

Символ NULL (0) недопустим, поскольку текстовые типы данных не могут хранить такие байты.

Не так для "char", где 0отображается на пустую строку '':

SELECT ''::"char"::int  -- 0
     , 0::"char" = '';  -- t

Помните: "char"это все еще «внутренний» тип, предназначенный для простого и дешевого перечисления. Официально не предназначен для того, что мы здесь делаем, и не переносим на другие РСУБД. Нет никаких гарантий от проекта Postgres для этого.

Эрвин Брандштеттер
источник
Я все еще думаю, что результат отображения \ r psql- ошибка или что-то странное. Он заканчивает строку, а затем пропускает строку?
Эван Кэрролл
4
@Evan Нет, он не пропускает строку, пустая строка является продолжением предыдущего ряда (который является многострочным). Если бы вы могли заставить psql рисовать горизонтальные линии между выходными строками, это было бы более очевидно, но поскольку вы не можете визуально определить, что это «+».
Джек говорит, попробуйте topanswers.xyz
0

Чтобы перейти к диапазону со знаком, вы можете создать несколько функций, которые помогут вам. Этот список создаст функции, которые не приводятся, чтобы помочь в этом процессе перехода от неподписанного однобайтового диапазона int[0-255] к подписанному однобайтовому диапазону, который требуется символу[-128,127] .

пример

Выписка из ЧТЕНИЯ

Теперь вы можете, например, сохранить значения в диапазоне [0-255]на столе.

CREATE TABLE t(x) AS VALUES
  (to_uchar(255)),
  (to_uchar(0));

Преобразовать их в bit(8)

SELECT to_bit8(x) FROM t;
 to_bit8  
----------
 11111111
 00000000
(2 rows)

Возможно, вы хотите очистить младшие два бита, вы можете сделать это с помощью BITWISE-AND,

UPDATE t
  SET x = to_uchar( to_bit8(x) & (x'fc')::bit(8) );

SELECT to_bit8(x) FROM t;
 to_bit8  
----------
 11111100
 00000000
(2 rows)
Эван Кэрролл
источник