Comment fonctionne exactement le type "char" à un octet dans PostgreSQL?

9

Je vois souvent des gens parler "char". Je ne l'ai jamais utilisé. Il est défini dans les documents comme,

Le type "char" (notez les guillemets) est différent de char (1) en ce qu'il n'utilise qu'un octet de stockage. Il est utilisé en interne dans les catalogues système en tant que type d'énumération simpliste.

Et plus loin,

"char"  1 byte  single-byte internal type

Donc, si c'est un octet, quel est le domaine et comment l'utiliseriez-vous? Est-il signé ou non signé? Dans cet article de @Erwin Brandstetter, il l'expose , mais je suis toujours confus. Il utilise ascii()et chr(), et fournit cette

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

Cela fait quelque chose de vraiment bizarre entre 10 et 11.

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

Cela devient aussi vraiment bizarre ici:

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

Pourquoi tout au nord de 128 est-il décodé en 128? Mais pour reprendre un peu le bizzare, après 192 il y a un interrupteur et ils sont décodés en 192 ..

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

Erwin dit

Plusieurs caractères ne sont pas destinés à être affichés. Donc, encodez avant de stocker et de décoder avant d'afficher ...

Je ne sais pas pourquoi nous devrions coder du tout si nous faisons exactement ce que cette question demande

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

Ça marche bien. Nous pouvons récupérer les ints en utilisant

SELECT i::int FROM foo;

Bref,

  1. Que fait le code d'Erwin entre 10 et 11 où le i devient nul?
  2. Pourquoi 128 est-il répété autant de fois?
  3. Pourquoi 192 est-il répété autant de fois?
  4. Comment puis-je déclencher l'incapacité à stocker 0, quand Erwin dit que vous ne pouvez pas coder 0 de cette façon (caractère nul non autorisé)

    CREATE TABLE foo AS SELECT 0::int::"char" AS x;
    SELECT x::int FROM foo;
     x 
    ---
    0
    
Evan Carroll
la source

Réponses:

11

1. chr(10)

... produit le caractère LINEFEED (aka séquence d'échappement \n) et psql affiche le caractère avec une nouvelle ligne (indiquée par +). Tout est correct là-bas.

2. & 3. ascii()produit 128 ou 192?

Cela commence par une erreur que j'ai commise. J'ai supposé négligemment que "char"cela couvrirait la plage d'un entier non signé de 1 octet (0 à 255) dans la réponse référencée (maintenant fixe), mais c'est en fait la plage d'un entier signé de 1 octet (-128 à 127) en interne.

ascii()prend un textparamètre, la conversion implicite de "char"to textproduit un caractère codé sur plusieurs octets en unicode, et la fonction retourne ( selon la documentation surascii() ):

Code ASCII du premier caractère de l'argument. Pour UTF8 renvoie le point de code Unicode du caractère. Pour les autres codages multi-octets, l'argument doit être un caractère ASCII.

Nous obtenons donc beaucoup de valeurs tronquées. 128 et 192 sont des valeurs d'octet pour l'octet de tête des caractères multi-octets.

4. L'octet nul

L'incapacité à stocker les octets nuls affecte uniquement les types de caractères réguliers ( text, char, varchar), non "char". Cela s'applique à mon exemple de buggy, car je jette un texttremplin. Lors de la diffusion entre "char"et integerdirectement, la limitation ne s'applique pas. Le manuel sur chr():

Le caractère NULL (0) n'est pas autorisé car les types de données texte ne peuvent pas stocker de tels octets.

Ce n'est pas le cas pour "char", où 0est mappé à la chaîne vide '':

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

N'oubliez pas: il "char"s'agit toujours d'un type "interne" destiné à une énumération simple et bon marché. Pas officiellement conçu pour ce que nous faisons ici, et non portable sur d'autres SGBDR. Il n'y a aucune garantie par le projet Postgres pour cela.

Erwin Brandstetter
la source
Je pense toujours que le résultat de l'affichage de \ r psqlest un bug ou quelque chose de bizarre. Il termine la ligne, puis saute une ligne?
Evan Carroll
4
@Evan non, il ne «saute pas de ligne», la ligne vierge est la continuation de la ligne précédente (qui est multiligne). Si vous pouviez obtenir psql pour tracer des lignes horizontales entre les lignes de sortie, ce serait plus évident, mais parce que vous ne pouvez pas, l'indice visuel est le «+».
Jack dit d'essayer topanswers.xyz
0

Pour effectuer le passage à la plage signée, vous pouvez créer certaines fonctions pour vous aider. Cette liste va créer des fonctions non moulages pour aider à aider dans ce processus de passer d' une gamme int un octet non signé[0-255] à une plage d' un octet signé ce caractère exige de[-128,127] .

Exemple

Un extrait du README

Maintenant, vous pouvez par exemple stocker les valeurs dans la plage de [0-255]sur la table.

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

Convertissez-les en bit(8)

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

Peut-être que vous voulez effacer les deux bits de poids faible, vous pouvez le faire avec 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)
Evan Carroll
la source