Des résultats surprenants pour les types de données avec modificateur de type

11

En discutant d'une solution CTE récursive pour cette question:

@ypercube est tombé sur une exception surprenante, ce qui nous a amenés à étudier la gestion des modificateurs de type. Nous avons trouvé un comportement surprenant.

1. La conversion de type conserve le modificateur de type dans certains contextes

Même lorsqu'on lui a demandé de ne pas le faire. L'exemple le plus élémentaire:

SELECT 'vc8'::varchar(8)::varchar

On pourrait s'y attendre varchar(pas de modificateur), du moins je le ferais. Mais le résultat est varchar(8)(avec modificateur). De nombreux cas liés dans le violon ci-dessous.

2. La concaténation de tableau perd le modificateur de type dans certains contextes

Sans besoin, cela se trompe donc du côté opposé:

SELECT ARRAY['vc8']::varchar(8)[]
     , ARRAY['vc8']::varchar(8)[] || 'vc8'::varchar(8)

La 1ère expression donne varchar(8)[]comme prévu.
Mais le 2e, après avoir enchaîné un autre, varchar(8)est édulcoré à juste varchar[](pas de modificateur). Comportement similaire à partir d' array_append()exemples dans le violon ci-dessous.

Tout cela n'a pas d'importance dans la plupart des contextes. Postgres ne perd pas de données et lorsqu'elle est affectée à une colonne, la valeur est de toute façon contrainte au bon type. Cependant , l'erreur dans des directions opposées aboutit à une exception surprenante:

3. CTE récursif exige que les types de données correspondent exactement

Compte tenu de ce tableau simplifié:

CREATE TABLE a (
  vc8  varchar(8)  -- with modifier
, vc   varchar     -- without  
);
INSERT INTO a VALUES  ('a',  'a'), ('bb', 'bb');

Bien que ce rCTE fonctionne pour la varcharcolonne vc, il échoue pour la varchar(8)colonne vc8:

WITH RECURSIVE cte AS (
   (
   SELECT ARRAY[vc8] AS arr  -- produces varchar(8)[]
   FROM   a
   ORDER  BY vc8
   LIMIT 1
   )

   UNION ALL
   (
   SELECT a.vc8 || c.arr  -- produces varchar[] !!
   FROM   cte c
   JOIN   a ON a.vc8 > c.arr[1]
   ORDER  BY vc8
   LIMIT 1
   )
   )
TABLE  cte;
ERREUR: la colonne 1 de la requête récursive "cte" a un caractère de type variant (8) [] en terme non récursif mais un caractère de type variant [] dans l'ensemble  
Astuce: transtypez la sortie du terme non récursif dans le type correct. Position: 103

Une solution de contournement rapide consisterait à effectuer un cast vers text.

Une UNIONrequête simple ne pose pas le même problème: elle se contente du type sans modificateur, ce qui garantit de conserver toutes les informations. Mais le rCTE est plus pointilleux.

En outre, vous ne rencontreriez pas de problèmes avec le plus couramment utilisé max(vc8)au lieu de ORDER BY/ LIMIT 1, car les max()amis se contentent textimmédiatement (ou du type de base respectif sans modificateur).

SQL Fiddle démontrant 3 choses:

  1. Une gamme d'exemples d'expressions incluant des résultats surprenants.
  2. Un rCTE simple qui fonctionne avec varchar(sans modificateur).
  3. Le même rCTE levant une exception pour varchar(n)(avec modificateur).

Le violon est pour pg 9.3. J'obtiens les mêmes résultats localement pour la page 9.4.4.

J'ai créé des tableaux à partir des expressions de démonstration pour pouvoir montrer le type de données exact, y compris le modificateur. Bien que pgAdmin affiche ces informations hors de la boîte, elles ne sont pas disponibles auprès de sqlfiddle. Remarquablement, il n'est pas non plus disponible en psql(!). C'est une lacune connue de psql et une solution possible a déjà été discutée sur pgsql-hackers - mais pas encore implémentée. Cela pourrait être l'une des raisons pour lesquelles le problème n'a pas encore été détecté et résolu.

Au niveau SQL, vous pouvez utiliser pg_typeof()pour obtenir le type (mais pas le modificateur).

Des questions

Ensemble, les 3 problèmes font le bordel.
Pour être précis, le problème 1. n'est pas directement impliqué, mais il ruine la correction apparemment évidente avec un plâtre au terme non récursif: ARRAY[vc8]::varchar[]ou similaire, ce qui ajoute à la confusion.
Lequel de ces éléments est un bug, un problème ou tout simplement comment il est censé être?
Suis-je en train de manquer quelque chose ou devons-nous signaler un bug?

Erwin Brandstetter
la source
Cela semble certainement assez suspect. Je soupçonne que la rétrocompatibilité des requêtes syndicales existantes peut jouer un rôle.
Craig Ringer
@CraigRinger: Je ne vois pas pourquoi la concaténation du tableau supprime le modificateur sans besoin et le casting ne le fait pas, même si cela est demandé. Ni pourquoi le rCTE doit être plus strict (moins intelligent) que les UNIONrequêtes simples . Se pourrait-il que nous ayons trouvé trois petits bogues indépendants à la fois? (Après des mois et des mois sans une telle découverte.) Lequel de ceux qui, selon vous, devrait être classé comme bug?
Erwin Brandstetter

Réponses:

1

Cela est dû aux attributs de relation (définis dans pg_classet pg_attribute, ou définis dynamiquement à partir d'une selectinstruction) prenant en charge les modificateurs (via pg_attribute.atttypmod), contrairement aux paramètres de fonction . Les modificateurs sont perdus lorsqu'ils sont traités via des fonctions, et puisque tous les opérateurs sont gérés via des fonctions, les modificateurs sont également perdus lorsqu'ils sont traités par des opérateurs.

Les fonctions avec des valeurs de sortie, ou qui renvoient des ensembles d'enregistrements, ou l'équivalent, returns table(...)ne peuvent pas non plus conserver les modificateurs inclus dans la définition. Cependant, les tables qui return setof <type>conserveront (en fait, probablement transtypées en) tous les modificateurs définis pour typedans pg_attribute.

Ziggy Crueltyfree Zeitgeister
la source