Aller au contenu

Migration vers 8.3 et supérieures

Conversions implicites

Depuis PostgreSQL 8.3, plusieurs casts (transtypages, ou conversions) implicites depuis le type text vers les types temporels et numériques ont été supprimées. Le but de la manœuvre est de supprimer ces faux amis :

  • ils n’ont pas lieu d’exister par défaut ;
  • peuvent soulever des bogues durant les conversions ;
  • encouragent de mauvaises pratiques dans le code des applications cliente ;
  • peuvent dégrader les performances de façon dramatique: un cast est un appel de fonction. Il peut donc rendre impossible l’utilisation d’un index, et de façon totalement invisible si le transtypage est implicite. Il suffit par exemple d’une jointure entre un champ texte (qui devrait être entier) et un champ entier. L’index sur le champ entier devient inutilisable pour la jointure.

Ces conversions implicites étant probablement utilisées implicitement dans beaucoup de code applicatif client, un certain nombre de bogues peuvent apparaître lors de mises à jour de PostgreSQL < 8.3 vers 8.3 et supérieures.

Méthode 1 : corriger l’application

La meilleure et unique bonne solution à ces problèmes est bien entendu de corriger la partie applicative. Les conversions implicites précédentes ont été supprimées pour d’excellentes raisons. La correction des bogues remontés suite à leur suppression amène habituellement de gros gains en performance sur une application (surtout si elle entraîne des corrections d’anomalies sur le schéma).

Une méthode possible est de rejouer sur un serveur de pré-production pendant une période suffisante (à définir en fonction de l’application) les requêtes reçues sur le serveur de production. En traçant toutes les requêtes en erreur, il sera ainsi possible de les identifier et les corriger au niveau applicatif.

Voici un petit échantillon de bogues possibles :

Cas 1

ERROR:  operator does not exist: character = integer at character xxx
SELECT ... FROM ... WHERE c1=1;

Ici la colonne c1 est de type character(1) dans le schéma de la table. Trois solutions possibles :

  • modifier le type en entier, mais celui-ci occupera alors peut-être plus d’espace sur disque ;
  • corriger la requête en utilisant r.retenu=1::character ou r.retenu = '1' la seconde forme étant préférable ;
  • modifier le type de cette colonne en boolean et simplement écrire …AND r.retenu AND …. Il faudra alors convertir la colonne avec la requête suivante :
  ALTER TABLE testdalibo ALTER b TYPE BOOLEAN USING 
CASE 
  WHEN b='1' THEN TRUE 
  WHEN b='0' THEN FALSE
  ELSE NULL  
END;

Cas 2

ERROR:  operator does not exist: timestamp without time zone !~~ unknown at character xxx
SELECT ... FROM ... WHERE c2 NOT LIKE 'NULL'

L’erreur ici se situe sur c2 NOT LIKE 'NULL'. Le champ c2 est un timestamp et ne peut donc être comparé à une chaîne de caractère. De plus, nous testons ici si la colonne est NULL, ce n’est pas une comparaison de date. La solution est de réécrire la requête de la façon suivante :

SELECT ... FROM ... WHERE c2 IS NOT NULL

Cas 3

ERROR:  function to_date(timestamp without time zone, unknown) does not exist at character xxx
SELECT ... FROM ... WHERE to_date(c2, 'YYYY-MM-DD')=$1

Le problème ici vient de to_date(c2, 'YYYY-MM-DD') = $1. Ce test peut se réécrire de la façon suivante :

SELECT ... FROM ... WHERE c2::DATE = $1

Si ce type de corrections n’est pas réalisable dans des délais convenables, il existe deux méthodes pour « assouplir » PostgreSQL sans réécrire les requêtes SQL. Cependant, ces deux méthodes ne concernent pas le cas 3 présenté ci-dessus.

Méthode 2 : recréer les casts implicites

Il est possible, comme solution de contournement, de réactiver ces conversions implicites en exécutant le script suivant :

CREATE FUNCTION pg_catalog.text(INTEGER) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(int4out($1));';
CREATE CAST (INTEGER AS text) WITH FUNCTION pg_catalog.text(INTEGER) AS IMPLICIT;

CREATE FUNCTION pg_catalog.text(SMALLINT) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(int2out($1));';
CREATE CAST (SMALLINT AS text) WITH FUNCTION pg_catalog.text(SMALLINT) AS IMPLICIT;

CREATE FUNCTION pg_catalog.text(oid) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(oidout($1));';
CREATE CAST (oid AS text) WITH FUNCTION pg_catalog.text(oid) AS IMPLICIT;

CREATE FUNCTION pg_catalog.text(DATE) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(date_out($1));';
CREATE CAST (DATE AS text) WITH FUNCTION pg_catalog.text(DATE) AS IMPLICIT;

CREATE FUNCTION pg_catalog.text(DOUBLE PRECISION) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(float8out($1));';
CREATE CAST (DOUBLE PRECISION AS text) WITH FUNCTION pg_catalog.text(DOUBLE PRECISION) AS IMPLICIT;

CREATE FUNCTION pg_catalog.text(REAL) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(float4out($1));';
CREATE CAST (REAL AS text) WITH FUNCTION pg_catalog.text(REAL) AS IMPLICIT;

CREATE FUNCTION pg_catalog.text(TIME WITH TIME zone) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(timetz_out($1));';
CREATE CAST (TIME WITH TIME zone AS text) WITH FUNCTION pg_catalog.text(TIME WITH TIME zone) AS IMPLICIT;

CREATE FUNCTION pg_catalog.text(TIME WITHOUT TIME zone) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(time_out($1));';
CREATE CAST (TIME WITHOUT TIME zone AS text) WITH FUNCTION pg_catalog.text(TIME WITHOUT TIME zone) AS IMPLICIT;

CREATE FUNCTION pg_catalog.text(TIMESTAMP WITH TIME zone) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(timestamptz_out($1));';
CREATE CAST (TIMESTAMP WITH TIME zone AS text) WITH FUNCTION pg_catalog.text(TIMESTAMP WITH TIME zone) AS IMPLICIT;

CREATE FUNCTION pg_catalog.text(INTERVAL) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(interval_out($1));';
CREATE CAST (INTERVAL AS text) WITH FUNCTION pg_catalog.text(INTERVAL) AS IMPLICIT;

CREATE FUNCTION pg_catalog.text(BIGINT) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(int8out($1));';
CREATE CAST (BIGINT AS text) WITH FUNCTION pg_catalog.text(BIGINT) AS IMPLICIT;

CREATE FUNCTION pg_catalog.text(NUMERIC) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(numeric_out($1));';
CREATE CAST (NUMERIC AS text) WITH FUNCTION pg_catalog.text(NUMERIC) AS IMPLICIT;

CREATE FUNCTION pg_catalog.text(TIMESTAMP WITHOUT TIME zone) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(timestamp_out($1));';
CREATE CAST (TIMESTAMP WITHOUT TIME zone AS text) WITH FUNCTION pg_catalog.text(TIMESTAMP WITHOUT TIME zone) AS IMPLICIT;

Danger

Si possible, il est conseillé de n’activer que les casts nécessaires pour les raisons suivantes :

  • Ces conversions implicites provoquent une incompatibilité comme pour l’exemple suivant :

    SELECT 'value = ' || 123;
    ERREUR:  l'opérateur n'est pas UNIQUE : UNKNOWN || INTEGER
    LIGNE 1 : SELECT 'value = ' || 123;
    
    La solution est alors de faire un cast explicite sur la seconde valeur :
    SELECT 'value = ' || CAST(123 AS TEXT);
    ?COLUMN?   
    -------------
    VALUE = 123
    

  • Ces conversions implicites sont aussi incompatibles avec Slony-I ;

Sources :

Méthode 3 : créer de nouveaux opérateurs

Une solution alternative à la création des conversions implicites n’est pas de recréer les casts eux-mêmes, mais les opérateurs. Il est à noter toutefois que, même si pour le moment aucun problème n’est connu, cette méthode pourrait aussi provoquer des effets de bord.

CREATE FUNCTION pg_catalog.texteqint(text, INTEGER) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(int4out($2)) = $1;$$;
CREATE FUNCTION pg_catalog.texteqint2(text, SMALLINT) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(int2out($2)) = $1;$$;
CREATE FUNCTION pg_catalog.texteqoid(text, oid) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(oidout($2)) = $1;$$;
CREATE FUNCTION pg_catalog.texteqdate(text, DATE) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(date_out($2)) = $1;$$;
CREATE FUNCTION pg_catalog.texteqfloat8(text, DOUBLE PRECISION) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(float8out($2)) = $1;$$;
CREATE FUNCTION pg_catalog.texteqreal(text, REAL) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(float4out($2)) = $1;$$;
CREATE FUNCTION pg_catalog.texteqtimetz(text, TIME WITH TIME zone) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(timetz_out($2)) = $1;$$;
CREATE FUNCTION pg_catalog.texteqtime(text, TIME WITHOUT TIME zone) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(time_out($2)) = $1;$$;
CREATE FUNCTION pg_catalog.texteqtimestamptz(text, TIMESTAMP WITH TIME zone) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(timestamptz_out($2)) = $1;$$;
CREATE FUNCTION pg_catalog.texteqinterval(text, INTERVAL) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(interval_out($2)) = $1;$$;
CREATE FUNCTION pg_catalog.texteqint8(text, BIGINT) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(int8out($2)) = $1;$$;
CREATE FUNCTION pg_catalog.texteqnumeric(text, NUMERIC) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(numeric_out($2)) = $1;$$;
CREATE FUNCTION pg_catalog.texteqtimestamp(text, TIMESTAMP WITHOUT TIME zone) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(timestamp_out($2)) = $1;$$;

CREATE FUNCTION pg_catalog.inteqtext(INTEGER, text) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(int4out($1)) = $2;$$;
CREATE FUNCTION pg_catalog.int2eqtext(SMALLINT, text) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(int2out($1)) = $2;$$;
CREATE FUNCTION pg_catalog.oideqtext(oid, text) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(oidout($1)) = $2;$$;
CREATE FUNCTION pg_catalog.dateeqtext(DATE, text) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(date_out($1)) = $2;$$;
CREATE FUNCTION pg_catalog.float8eqtext(DOUBLE PRECISION, text) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(float8out($1)) = $2;$$;
CREATE FUNCTION pg_catalog.realeqtext(REAL, text) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(float4out($1)) = $2;$$;
CREATE FUNCTION pg_catalog.timetzeqtext(TIME WITH TIME zone, text) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(timetz_out($1)) = $2;$$;
CREATE FUNCTION pg_catalog.timeeqtext(TIME WITHOUT TIME zone, text) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(time_out($1)) = $2;$$;
CREATE FUNCTION pg_catalog.timestamptzeqtext(TIMESTAMP WITH TIME zone, text) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(timestamptz_out($1)) = $2;$$;
CREATE FUNCTION pg_catalog.intervaleqtext(INTERVAL, text) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(interval_out($1)) = $2;$$;
CREATE FUNCTION pg_catalog.int8eqtext(BIGINT, text) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(int8out($1)) = $2;$$;
CREATE FUNCTION pg_catalog.numericeqtext(NUMERIC, text) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(numeric_out($1)) = $2;$$;
CREATE FUNCTION pg_catalog.timestampeqtext(TIMESTAMP WITHOUT TIME zone, text) RETURNS BOOLEAN STRICT IMMUTABLE LANGUAGE SQL AS $$SELECT textin(timestamp_out($1)) = $2;$$;

CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.texteqint, LEFTARG=text, RIGHTARG=INTEGER, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.texteqint2, LEFTARG=text, RIGHTARG=SMALLINT, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.texteqoid, LEFTARG=text, RIGHTARG=oid, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.texteqdate, LEFTARG=text, RIGHTARG=DATE, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.texteqfloat8, LEFTARG=text, RIGHTARG=DOUBLE PRECISION, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.texteqreal, LEFTARG=text, RIGHTARG=REAL, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.texteqtimetz, LEFTARG=text, RIGHTARG=TIME WITH TIME zone, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.texteqtime, LEFTARG=text, RIGHTARG=TIME WITHOUT TIME zone, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.texteqtimestamptz, LEFTARG=text, RIGHTARG=TIMESTAMP WITH TIME zone, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.texteqinterval, LEFTARG=text, RIGHTARG=INTERVAL, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.texteqint8, LEFTARG=text, RIGHTARG=BIGINT, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.texteqnumeric, LEFTARG=text, RIGHTARG=NUMERIC, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.texteqtimestamp, LEFTARG=text, RIGHTARG=TIMESTAMP WITHOUT TIME zone, COMMUTATOR=OPERATOR(pg_catalog.=));

CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.inteqtext, LEFTARG=INTEGER, RIGHTARG=text, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.int2eqtext, LEFTARG=SMALLINT, RIGHTARG=text, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.oideqtext, LEFTARG=oid, RIGHTARG=text, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.dateeqtext, LEFTARG=DATE, RIGHTARG=text, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.float8eqtext, LEFTARG=DOUBLE PRECISION, RIGHTARG=text, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.realeqtext, LEFTARG=REAL, RIGHTARG=text, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.timetzeqtext, LEFTARG=TIME WITH TIME zone, RIGHTARG=text, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.timeeqtext, LEFTARG=TIME WITHOUT TIME zone, RIGHTARG=text, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.timestamptzeqtext, LEFTARG=TIMESTAMP WITH TIME zone, RIGHTARG=text, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.intervaleqtext, LEFTARG=INTERVAL, RIGHTARG=text, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.int8eqtext, LEFTARG=BIGINT, RIGHTARG=text, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.numericeqtext, LEFTARG=NUMERIC, RIGHTARG=text, COMMUTATOR=OPERATOR(pg_catalog.=));
CREATE OPERATOR pg_catalog.= ( PROCEDURE = pg_catalog.timestampeqtext, LEFTARG=TIMESTAMP WITHOUT TIME zone, RIGHTARG=text, COMMUTATOR=OPERATOR(pg_catalog.=));

Clés étrangères non uniques

Jusqu’en 7.4 il était possible de créer des clés étrangères sur une colonne non unique. Ce n’est plus possible.

CREATE TABLE doc_type (
    id BIGINT NOT NULL,
    titre CHARACTER VARYING(255)
);

CREATE TABLE doc (
    id BIGINT NOT NULL,
    reference CHARACTER VARYING(255) NOT NULL,
    description text,
    iddoctype BIGINT NOT NULL
);

ALTER TABLE ONLY doc
    ADD CONSTRAINT distfk1 FOREIGN KEY (iddoctype) REFERENCES doc_type(id) MATCH FULL;

Erreur retournée :

ERROR:  there is no unique constraint matching given keys for referenced table "doc_type"

La solution est de créer une contrainte unique sur la colonne référencée :

CREATE TABLE doc_type (
    id BIGINT UNIQUE NOT NULL,
    titre CHARACTER VARYING(255)
);

ou mieux, une clé primaire, si elle ne peut contenir de NULL (et qu’il n’y a pas déjà de clé primaire) :

CREATE TABLE doc_type (
    id BIGINT PRIMARY KEY,
    titre CHARACTER VARYING(255)
);

Cela peut poser un problème lors du chargement des données avec la présence de doublons.

La retouche du schéma peut être extrêmement fastidieuse suivant le nombre de ces clés étrangères. Voici un script set_unique_key_for_foreign_key.pl qui peut faciliter les choses :

#!/usr/bin/perl

my @content = ();
my %constraints = ();
open(IN, "$ARGV[0]") or die "FATAL: can not read file $ARGV[0]. $!\n";
while (my $l = <IN>) {
        chomp($l);
        push(@content, $l);
        if ($l =~ /ADD CONSTRAINT .* FOREIGN KEY .* REFERENCES ([^\(]+)\(([^\)]+)\)/) {
                push(@{$constraints{$1}}, $2);
        }
}
close(IN);

my $cur_table = '';
for (my $i = 0; $i <= $#content; $i++) {
        if ($content[$i] =~ /CREATE TABLE (.*) \(/) {
                $cur_table = $1;
        } elsif ($content[$i] =~ /^ALTER/) {
                $cur_table = '';
        }
        if ($cur_table && exists $constraints{$cur_table} && ($content[$i] =~ /^    ([^\s]+) /) ) {
                my $col = $1;
                if (grep(/^$col$/, @{$constraints{$cur_table}})) {
                        if ($content[$i] =~ /NOT NULL/) {
                                $content[$i] =~ s/NOT NULL/PRIMARY KEY/;
                        } else {
                                $content[$i] =~ s/,$/ UNIQUE,/;
                        }
                }
        }
        print "$content[$i]\n";
}

exit 0;

Il transforme un schéma généré par pg_dump -C -s database > fichier__traiter.sql fourni en entrée sous forme de fichier, et renvoie le schéma corrigé sur la sortie standard. Si le champ référencé contient NOT NULL dans sa définition, alors le script le transforme en PRIMARY KEY, sinon le script ajout UNIQUE dans la définition du champ.

Fonction de conversion d’encodage

La fonction convert(chaîne text, [codage_source name,] codage_destination name) n’existe plus avec les mêmes types de paramètres à partir de la version 8.3. Cela conduit à des erreurs de ce type :

function convert(text, unknown, unknown) does not exist at character 34

Voici les informations sur cette fonction en version 8.2 :

Fonction Type renvoyé Description Exemple Résultat
convert(chaîne text, [codage_source name,] codage_destination name) text Convertit une chaîne dans le codage codage_destination. Le codage initial est indiqué par codage_source. Si codage_source est omis, le codage de la base de données est utilisé. convert( 'texte_en_utf8', 'UTF8', 'LATIN1') texte_en_utf8 représenté dans le codage ISO 8859-1

Elle manipule maintenant uniquement des données de type bytea :

Fonction Type renvoyé Description Exemple Résultat
convert(chaine bytea, encodage_source name, encodage_destination name) bytea Convertit la chaîne en encodage encodage_destination. L’encodage d’origine est indiqué par encodage_source. La chaine doit être valide pour cet encodage. Les conversions peuvent être définies avec CREATE CONVERSION. De plus, il existe quelques conversions pré-définies. Voir Tableau 9.7, « Conversions intégrées » pour les conversions disponibles. convert( 'texte_en_utf8', 'UTF8', 'LATIN1') texte_en_utf8 représenté dans le codage ISO 8859-1

La conversion en bytea des champs impliqués est recommandé dans certaines conditions, mais pour obtenir la même fonctionnalité sans opérer de modification de type, il faut passer par trois fonctions en ayant connaissance de l’encodage d’origine. Par exemple :

SELECT CONVERT('schön', 'WIN1252', 'UTF8');

peut-être réécrit comme suit :

SELECT
  convert_from(
    CONVERT(
      convert_to('schön', 'WIN1252'), 'WIN1252', 'UTF8'
    ), 'UTF8'
  );

Avec une requête du style :

SELECT id, auteur, titre, CONVERT(description, 'LATIN1', 'UTF8'), datesortie FROM livres;

la fonction de conversion peut être omise du moment que l’encodage au niveau du client correspond à celui attendu.

SET client_encoding TO 'UTF8';
SELECT id, auteur, titre, description, datesortie FROM livres;

Création d’une fonction équivalente

Si le code applicatif ne peut être repris, voici une fonction de surcharge qui permet d’avoir l’équivalence :

CREATE OR REPLACE FUNCTION CONVERT (str text, codage_source name, codage_destination name) RETURNS text AS
$$
        SELECT convert_from(CONVERT(convert_to(str, codage_source), codage_source, codage_destination), codage_destination);
$$ LANGUAGE SQL
RETURNS NULL ON NULL INPUT;