<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr">
  <title>Data Bene - Tag: 'Programmation'</title>
  <subtitle>Base de données relationnelles, open-source et adaptives.</subtitle>
  <link href="https://www.data-bene.io/fr/blog/tags/programmation.xml" rel="self" type="application/atom+xml" />
  <updated>2025-11-13T00:00:00Z</updated>
  <id>https://www.data-bene.io/fr/blog/tags/programmation.xml</id>
    <entry>
      <title>Le saviez-vous ? Les tables dans PostgreSQL sont limitées à 1600 colonnes</title>
      <link href="https://www.data-bene.io/fr/blog/le-saviez-vous-les-tables-dans-postgresql-sont-limitees-a-1600-colonnes/" />
      <updated>2025-11-13T00:00:00Z</updated>
      <id>https://www.data-bene.io/fr/blog/le-saviez-vous-les-tables-dans-postgresql-sont-limitees-a-1600-colonnes/</id>
     <content type="html"><![CDATA[ <p><strong>Saviez-vous qu’une table ne peut pas contenir plus de 1 600 colonnes ?</strong> Cet article de blog est inspiré d’une conversation avec Pierre Ducroquet.</p>
<h2 id="tout-dabord-la-documentation"><a class="heading-anchor" href="#tout-dabord-la-documentation">Tout d’abord, la documentation</a></h2>
<p><a href="https://www.postgresql.org/docs/current/limits.html" rel="noopener">L’annexe K</a> de la documentation PostgreSQL  indique qu’une table peut contenir jusqu’à 1 600 colonnes.</p>
<p>Il s’agit d’une <strong>limite codée en dur</strong>, visible dans le code source à l’adresse <code>src/include/access/htup_details.h</code> :</p>
<pre class="language-plaintext"><code class="language-plaintext">#define MaxTupleAttributeNumber 1664
#define MaxHeapAttributeNumber	1600</code></pre>
<h2 id="atteindre-la-limite-de-maniere-classique"><a class="heading-anchor" href="#atteindre-la-limite-de-maniere-classique">Atteindre la limite de manière classique</a></h2>
<p>Vérifions d’abord cette affirmation par quelques tests.</p>
<h3 id="jouer-avec-la-definition-de-la-table"><a class="heading-anchor" href="#jouer-avec-la-definition-de-la-table">Jouer avec la définition de la table</a></h3>
<p>Nous utilisons ici un script bash simple, facile à adapter aux différents tests.</p>
<pre class="language-sql"><code class="language-sql"><span class="token comment">-- Exemple classique</span>

<span class="token keyword">DO</span> $$
<span class="token keyword">DECLARE</span>
    i <span class="token keyword">int</span><span class="token punctuation">;</span>
<span class="token keyword">BEGIN</span>
    <span class="token keyword">EXECUTE</span> <span class="token string">'DROP TABLE IF EXISTS tint_1601;'</span><span class="token punctuation">;</span>
    <span class="token keyword">EXECUTE</span> <span class="token string">'CREATE TABLE tint_1601(i_1 int);'</span><span class="token punctuation">;</span>
    <span class="token keyword">FOR</span> i <span class="token operator">IN</span> <span class="token number">2.</span><span class="token number">.1601</span> <span class="token keyword">LOOP</span>
        <span class="token keyword">EXECUTE</span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">'ALTER TABLE tint_1601 ADD COLUMN i_%s int;'</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">END</span> <span class="token keyword">LOOP</span><span class="token punctuation">;</span>
<span class="token keyword">END</span> $$<span class="token punctuation">;</span></code></pre>
<p>Le résultat est le suivant :</p>
<pre class="language-plaintext"><code class="language-plaintext">NOTICE:  table "tint_1600" does not exist, skipping
ERROR:  tables can have at most 1600 columns
CONTEXT:  SQL statement "ALTER TABLE tint_1600 ADD COLUMN i_1601 int;"
PL/pgSQL function inline_code_block line 8 at EXECUTE</code></pre>
<p>Jusqu’ici, tout va bien (ou du moins, tout fonctionne comme prévu).</p>
<p>Vous pourriez tenter de remplacer le type <code>int4</code> par le type <code>int2</code> pour arriver à créer une table de plus de 1 600 colonnes. Mais cela ne fonctionnera pas plus, car il s’agit d’une limite codée en dur.</p>
<h3 id="jouer-avec-le-contenu-dune-table"><a class="heading-anchor" href="#jouer-avec-le-contenu-dune-table">Jouer avec le contenu d’une table</a></h3>
<p>Générons une table de 1600 colonnes à partir du script sql précédent :</p>
<pre class="language-sql"><code class="language-sql"><span class="token keyword">DO</span> $$
<span class="token keyword">DECLARE</span>
    i <span class="token keyword">int</span><span class="token punctuation">;</span>
<span class="token keyword">BEGIN</span>
    <span class="token keyword">EXECUTE</span> <span class="token string">'DROP TABLE IF EXISTS tint_1600;'</span><span class="token punctuation">;</span>
    <span class="token keyword">EXECUTE</span> <span class="token string">'CREATE TABLE tint_1600(i_1 int);'</span><span class="token punctuation">;</span>
    <span class="token keyword">FOR</span> i <span class="token operator">IN</span> <span class="token number">2.</span><span class="token number">.1600</span> <span class="token keyword">LOOP</span>
        <span class="token keyword">EXECUTE</span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">'ALTER TABLE tint_1600 ADD COLUMN i_%s int;'</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">END</span> <span class="token keyword">LOOP</span><span class="token punctuation">;</span>
<span class="token keyword">END</span> $$<span class="token punctuation">;</span></code></pre>
<p>Construisons puis insérons un tuple de 1600 colonnes :</p>
<pre class="language-sql"><code class="language-sql"><span class="token keyword">DO</span> $$
<span class="token keyword">DECLARE</span>
    s <span class="token keyword">TEXT</span><span class="token punctuation">;</span>
    rows_inserted <span class="token keyword">int</span><span class="token punctuation">;</span>
<span class="token keyword">BEGIN</span>
    s :<span class="token operator">=</span> <span class="token function">format</span><span class="token punctuation">(</span>
                 <span class="token string">'INSERT INTO tint_1600 VALUES (1%s);'</span>
               <span class="token punctuation">,</span> <span class="token keyword">repeat</span><span class="token punctuation">(</span> <span class="token string">',1'</span> <span class="token punctuation">,</span> <span class="token number">1599</span> <span class="token punctuation">)</span>
               <span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">EXECUTE</span> s<span class="token punctuation">;</span>

    GET DIAGNOSTICS rows_inserted <span class="token operator">=</span> ROW_COUNT<span class="token punctuation">;</span>
    RAISE NOTICE <span class="token string">'Rows inserted: %'</span><span class="token punctuation">,</span> rows_inserted<span class="token punctuation">;</span>
<span class="token keyword">END</span> $$<span class="token punctuation">;</span></code></pre>
<p>The output is:</p>
<pre class="language-plaintext"><code class="language-plaintext">NOTICE:  Rows inserted: 1
DO</code></pre>
<p>Sans surprise, c’est encore un succès.</p>
<h3 id="trop-gros-pour-echouer"><a class="heading-anchor" href="#trop-gros-pour-echouer">Trop gros pour échouer ?</a></h3>
<p>Continuons à repousser les limites.</p>
<p>On créé maintenant une autre table de 1 600 colonnes, cette fois avec le type de données <code>char(127)</code>.</p>
<p>On réutilise notre script bash avec quelques modifications :</p>
<pre class="language-sql"><code class="language-sql"><span class="token comment">-- Create a table with 1,600 columns: 1 x int + 1599 x char(127)</span>
<span class="token keyword">DO</span> $$
<span class="token keyword">DECLARE</span>
    i <span class="token keyword">int</span><span class="token punctuation">;</span>
<span class="token keyword">BEGIN</span>
    <span class="token keyword">EXECUTE</span> <span class="token string">'DROP TABLE IF EXISTS tint_1600;'</span><span class="token punctuation">;</span>
    <span class="token keyword">EXECUTE</span> <span class="token string">'CREATE TABLE tint_1600(i_1 int);'</span><span class="token punctuation">;</span>
    <span class="token keyword">FOR</span> i <span class="token operator">IN</span> <span class="token number">2.</span><span class="token number">.1600</span> <span class="token keyword">LOOP</span>
        <span class="token keyword">EXECUTE</span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">'ALTER TABLE tint_1600 ADD COLUMN c_%s char(127) NOT NULL;'</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">END</span> <span class="token keyword">LOOP</span><span class="token punctuation">;</span>
<span class="token keyword">END</span> $$<span class="token punctuation">;</span>

<span class="token comment">-- Insert a tuple - 1 x int + 1599 x char(127)</span>
<span class="token keyword">DO</span> $$
<span class="token keyword">DECLARE</span>
    s <span class="token keyword">TEXT</span><span class="token punctuation">;</span>
<span class="token keyword">BEGIN</span>
    s :<span class="token operator">=</span> <span class="token function">format</span><span class="token punctuation">(</span>
                 <span class="token string">'INSERT INTO tint_1600 VALUES (1%s);'</span>
               <span class="token punctuation">,</span> <span class="token keyword">repeat</span><span class="token punctuation">(</span> $q$<span class="token punctuation">,</span><span class="token string">'1'</span>::<span class="token keyword">char</span><span class="token punctuation">(</span><span class="token number">127</span><span class="token punctuation">)</span>$q$ <span class="token punctuation">,</span> <span class="token number">1599</span> <span class="token punctuation">)</span>
               <span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">EXECUTE</span> s<span class="token punctuation">;</span>
<span class="token keyword">END</span> $$<span class="token punctuation">;</span><span class="token identifier"><span class="token punctuation">`</span><span class="token punctuation">`</span></span><span class="token punctuation">`</span>

Le résultat est :</code></pre>
<p>ERROR:  row is too big: size 25616, maximum size 8160</p>
<pre class="language-plaintext"><code class="language-plaintext">
Comme on peut le voir, la table comporte effectivement 1 600 colonnes, mais cette fois, le tuple ne peut pas tenir dans une seule page heap, ce qui explique l'erreur « row is too big: size 25616, maximum size 8160 ». Si vous avez fait attention au script, vous avez pu constater que les colonnes sont définies comme `NOT NULL`. Aussi, lors de la création de la table, PostgreSQL aurait pu se rendre compte que l'insertion de données serait impossible. Mais il n'y a pas eu d'alerte.

## Quid des JOINs ?

Pour simplifier, testons avec une jointure automatique :

```sql
SELECT a.*,b.* FROM tint_1600 a, tint_1600 b;
ERROR:  target lists can have at most 1664 entries</code></pre>
<p>Maintenant, c’est la clause <code>SELECT</code> de (<code>a.*,b.*</code>) qui a atteint sa propre limite (<code>MaxTupleAttributeNumber = 1664</code>).</p>
<h2 id="atteindre-la-limite-de-colonnes-de-maniere-inattendue"><a class="heading-anchor" href="#atteindre-la-limite-de-colonnes-de-maniere-inattendue">Atteindre la limite de colonnes de manière inattendue</a></h2>
<p>Il arrive que vous deviez modifier votre application, et que cela entraîne des modifications de schéma.<br>
La plupart du temps, il s’agit de modifications de tables, comme l’ajout ou la suppression de colonnes.</p>
<h3 id="explorer-add-/-drop-column"><a class="heading-anchor" href="#explorer-add-/-drop-column">Explorer <code>ADD</code> / <code>DROP COLUMN</code></a></h3>
<p>Voyons ce qui se passe côté SQL lorsque nous ajoutons, puis supprimons, une colonne.</p>
<pre class="language-sql"><code class="language-sql"><span class="token operator">=</span><span class="token comment"># CREATE TABLE tadc_1600(i_1 int NOT NULL);</span>

<span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span>

<span class="token operator">=</span><span class="token comment"># ALTER TABLE tadc_1600 ADD COLUMN i_2 int NOT NULL;</span>

<span class="token keyword">ALTER</span> <span class="token keyword">TABLE</span>

<span class="token operator">=</span><span class="token comment"># SELECT attname,attnum,attstorage,attnotnull,attisdropped</span>
   <span class="token keyword">FROM</span> pg_attribute
   <span class="token keyword">WHERE</span> attrelid<span class="token operator">=</span><span class="token punctuation">(</span>
                   <span class="token keyword">SELECT</span> oid
                   <span class="token keyword">FROM</span> pg_class
                   <span class="token keyword">WHERE</span> relname<span class="token operator">=</span><span class="token string">'tadc_1600'</span>
                   <span class="token punctuation">)</span>
     <span class="token operator">AND</span> attnum <span class="token operator">></span> <span class="token number">0</span> <span class="token keyword">ORDER</span> <span class="token keyword">BY</span> attnum<span class="token punctuation">;</span>

 attname <span class="token operator">|</span> attnum <span class="token operator">|</span> attstorage <span class="token operator">|</span> attnotnull <span class="token operator">|</span> attisdropped
<span class="token comment">---------+--------+------------+------------+--------------</span>
 i_1     <span class="token operator">|</span>      <span class="token number">1</span> <span class="token operator">|</span> p          <span class="token operator">|</span> t          <span class="token operator">|</span> f
 i_2     <span class="token operator">|</span>      <span class="token number">2</span> <span class="token operator">|</span> p          <span class="token operator">|</span> t          <span class="token operator">|</span> f
<span class="token punctuation">(</span><span class="token number">2</span> <span class="token keyword">rows</span><span class="token punctuation">)</span>

<span class="token operator">=</span><span class="token comment"># ALTER TABLE tadc_1600 DROP COLUMN i_2;</span>

<span class="token keyword">ALTER</span> <span class="token keyword">TABLE</span>

<span class="token operator">=</span><span class="token comment"># SELECT attname,attnum,attstorage,attnotnull,attisdropped</span>
   <span class="token keyword">FROM</span> pg_attribute
   <span class="token keyword">WHERE</span> attrelid<span class="token operator">=</span><span class="token punctuation">(</span>
                   <span class="token keyword">SELECT</span> oid
                   <span class="token keyword">FROM</span> pg_class
                   <span class="token keyword">WHERE</span> relname<span class="token operator">=</span><span class="token string">'tadc_1600'</span>
                   <span class="token punctuation">)</span>
     <span class="token operator">AND</span> attnum <span class="token operator">></span> <span class="token number">0</span> <span class="token keyword">ORDER</span> <span class="token keyword">BY</span> attnum<span class="token punctuation">;</span>

           attname            <span class="token operator">|</span> attnum <span class="token operator">|</span> attstorage <span class="token operator">|</span> attnotnull <span class="token operator">|</span> attisdropped
<span class="token comment">------------------------------+--------+------------+------------+--------------</span>
 i_1                          <span class="token operator">|</span>      <span class="token number">1</span> <span class="token operator">|</span> p          <span class="token operator">|</span> t          <span class="token operator">|</span> f
 <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>pg<span class="token punctuation">.</span>dropped<span class="token punctuation">.</span><span class="token number">2.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token operator">|</span>      <span class="token number">2</span> <span class="token operator">|</span> p          <span class="token operator">|</span> f          <span class="token operator">|</span> t
<span class="token punctuation">(</span><span class="token number">2</span> <span class="token keyword">rows</span><span class="token punctuation">)</span></code></pre>
<p>Lors de la suppression d’une colonne, cette colonne</p>
<ul class="list">
<li>est renommée en « . » + « pg.dropped.» + attnum + « . »,</li>
<li>devient NULLable,</li>
<li>est marquée comme supprimée.</li>
</ul>
<h3 id="iterer-sur-lajout/la-suppression-de-colonnes"><a class="heading-anchor" href="#iterer-sur-lajout/la-suppression-de-colonnes">Itérer sur l’ajout/la suppression de colonnes</a></h3>
<p>On peut se demander s’il existe une limite au nombre d’opérations d’ajout/suppression qui peuvent être exécutées sur une table donnée.</p>
<p>Comme d’habitude, testons :</p>
<pre class="language-sql"><code class="language-sql"><span class="token comment">-- ADD / DROP COLUMN example</span>
<span class="token keyword">DO</span> $$
<span class="token keyword">DECLARE</span>
    i <span class="token keyword">int</span><span class="token punctuation">;</span>
<span class="token keyword">BEGIN</span>
    <span class="token keyword">EXECUTE</span> <span class="token string">'DROP TABLE IF EXISTS tadc;'</span><span class="token punctuation">;</span>
    <span class="token keyword">EXECUTE</span> <span class="token string">'CREATE TABLE tadc(i_1 int);'</span><span class="token punctuation">;</span>
    <span class="token keyword">FOR</span> i <span class="token operator">IN</span> <span class="token number">2.</span><span class="token number">.1601</span> <span class="token keyword">LOOP</span>
        <span class="token keyword">EXECUTE</span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">'ALTER TABLE tadc ADD COLUMN i_%s int;'</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">EXECUTE</span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">'ALTER TABLE tadc DROP COLUMN i_%s;'</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">END</span> <span class="token keyword">LOOP</span><span class="token punctuation">;</span>
<span class="token keyword">END</span> $$<span class="token punctuation">;</span></code></pre>
<p>Le résultat est :</p>
<pre class="language-plaintext"><code class="language-plaintext">ERROR:  tables can have at most 1600 columns
CONTEXT:  SQL statement "ALTER TABLE tadc ADD COLUMN i_1601 int;"
PL/pgSQL function inline_code_block line 8 at EXECUTE</code></pre>
<p>Oh oh ! On a atteint la limite des 1 600 ici aussi !</p>
<p>Explorons un peu ce qui se passe :</p>
<pre class="language-sql"><code class="language-sql"><span class="token operator">=</span><span class="token comment"># SELECT attname,attnum,attstorage,attnotnull,attisdropped</span>
   <span class="token keyword">FROM</span> pg_attribute
   <span class="token keyword">WHERE</span> attrelid<span class="token operator">=</span><span class="token punctuation">(</span>
                   <span class="token keyword">SELECT</span> oid
                   <span class="token keyword">FROM</span> pg_class
                   <span class="token keyword">WHERE</span> relname<span class="token operator">=</span><span class="token string">'tadc'</span>
                   <span class="token punctuation">)</span>
     <span class="token operator">AND</span> attnum <span class="token operator">></span> <span class="token number">0</span> <span class="token keyword">ORDER</span> <span class="token keyword">BY</span> attnum<span class="token punctuation">;</span>

             attname             <span class="token operator">|</span> attnum <span class="token operator">|</span> attstorage <span class="token operator">|</span> attnotnull <span class="token operator">|</span> attisdropped
<span class="token comment">---------------------------------+--------+------------+------------+--------------</span>
 i_1                             <span class="token operator">|</span>      <span class="token number">1</span> <span class="token operator">|</span> p          <span class="token operator">|</span> t          <span class="token operator">|</span> f
 <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>pg<span class="token punctuation">.</span>dropped<span class="token punctuation">.</span><span class="token number">2.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>    <span class="token operator">|</span>      <span class="token number">2</span> <span class="token operator">|</span> p          <span class="token operator">|</span> f          <span class="token operator">|</span> t
 <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>pg<span class="token punctuation">.</span>dropped<span class="token punctuation">.</span><span class="token number">3.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>    <span class="token operator">|</span>      <span class="token number">3</span> <span class="token operator">|</span> p          <span class="token operator">|</span> f          <span class="token operator">|</span> t
 <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>pg<span class="token punctuation">.</span>dropped<span class="token punctuation">.</span><span class="token number">4.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>    <span class="token operator">|</span>      <span class="token number">4</span> <span class="token operator">|</span> p          <span class="token operator">|</span> f          <span class="token operator">|</span> t
 <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>pg<span class="token punctuation">.</span>dropped<span class="token punctuation">.</span><span class="token number">5.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>    <span class="token operator">|</span>      <span class="token number">5</span> <span class="token operator">|</span> p          <span class="token operator">|</span> f          <span class="token operator">|</span> t

 <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>pg<span class="token punctuation">.</span>dropped<span class="token punctuation">.</span><span class="token number">1599.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token operator">|</span>   <span class="token number">1599</span> <span class="token operator">|</span> p          <span class="token operator">|</span> f          <span class="token operator">|</span> t
 <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>pg<span class="token punctuation">.</span>dropped<span class="token punctuation">.</span><span class="token number">1600.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token operator">|</span>   <span class="token number">1600</span> <span class="token operator">|</span> p          <span class="token operator">|</span> f          <span class="token operator">|</span> t
<span class="token punctuation">(</span><span class="token number">1600</span> <span class="token keyword">rows</span><span class="token punctuation">)</span></code></pre>
<p>La table <code>tadc</code> comporte 1 600 colonnes. Vous pouvez vous en apercevoir car toutes les modifications sont ajoutées à la table, et son contenu n’est pas réécrit.</p>
<p>Aussi, à ce stade, toute modification supplémentaire visant à ajouter ou supprimer des colonnes sera vouée à l’échec.</p>
<p>Comment faire pour me sortir de cette situation ?</p>
<h4 id="le-chevalier-du-vacuum-devrait-sauver-la-princesse-postgresql-non"><a class="heading-anchor" href="#le-chevalier-du-vacuum-devrait-sauver-la-princesse-postgresql-non">Le chevalier du VACUUM devrait sauver la princesse PostgreSQL, non ?</a></h4>
<p>Et non ! La commande <code>VACUUM</code> fonctionne au niveau du tuple, donc même si vous exécutez un <code>VACUUM FULL</code>, la structure de la table ne changera pas.</p>
<h4 id="donc-le-dragon-a-mange-le-chevalier-que-fait-on"><a class="heading-anchor" href="#donc-le-dragon-a-mange-le-chevalier-que-fait-on">Donc le dragon a mangé le chevalier, que fait-on ?</a></h4>
<p>Il ne s’agit pas d’un problème de tuples morts, mais plutôt d’un problème de catalogue.<br>
Vous devez créer une nouvelle définition de table.</p>
<p>Voici quelques solutions, des plus simples aux plus complexes :</p>
<ol class="list">
<li>
<p>Créer une nouvelle table avec interruption de service</p>
<ul class="list">
<li><code>CREATE TABLE LIKE (INCLUDING ALL)</code></li>
<li>Copier (<code>COPY</code>) les données de l’ancienne table vers la nouvelle</li>
<li>Renommer les tables</li>
<li>Supprimer l’ancienne table</li>
</ul>
</li>
<li>
<p>Exploiter la réplication logique pour minimiser les interruptions de service</p>
<ul class="list">
<li><code>CREATE TABLE LIKE (INCLUDING ALL)</code></li>
<li><code>CREATE local PUBLICATION/SUBSCRIPTION</code></li>
<li>Une fois les données synchronisées, arrêter/mettre en pause le service applicatif</li>
<li>Supprimer l’abonnement</li>
<li>Renommer les tables</li>
<li>Redémarrer/relancer l’application</li>
<li>Supprimer l’ancienne table</li>
</ul>
</li>
</ol>
<h2 id="contactez-nous"><a class="heading-anchor" href="#contactez-nous">Contactez-nous</a></h2>
<p>Vous avez d’autres idées pour résoudre ce problème ? Vous avez trouvé d’autres moyens étranges de déclencher cette erreur liée à la limite codée en dur ? <a href="https://www.data-bene.io/en/#contact" rel="noopener">Contactez-nous</a> ! Nous sommes toujours ravis d’échanger sur PostgreSQL.</p>
 ]]></content>
			<author>
				<name>Frédéric Delacourt</name>
			</author>
    </entry>
    <entry>
      <title>Statistiques cumulatives dans PostgreSQL 18</title>
      <link href="https://www.data-bene.io/fr/blog/statistiques-cumulatives-dans-postgresql-18/" />
      <updated>2025-09-29T00:00:00Z</updated>
      <id>https://www.data-bene.io/fr/blog/statistiques-cumulatives-dans-postgresql-18/</id>
     <content type="html"><![CDATA[ <p>Dans <strong>PostgreSQL 18</strong>, le sous-système de statistiques et de supervision bénéficie d’une refonte majeure: statistiques cumulatives étendues, nouvelle visibilité I/O par backend, possibilité pour les extensions d’exporter / importer / ajuster les statistiques, et améliorations des contrôles GUC ainsi que du comportement de cache et de snapshot. Ces changements ouvrent de nouvelles perspectives pour l’analyse de performance, la simulation inter-environnements et une meilleure intégration avec les extensions. Dans cet article, j’explore les nouveautés, les points d’attention, les paramètres GUC, ainsi que la manière dont les auteurs d’extensions peuvent exploiter la nouvelle API en C.</p>
<h2 id="introduction-and-motivation"><a class="heading-anchor" href="#introduction-and-motivation">Introduction &amp; motivation</a></h2>
<p>Les statistiques (au sens large: compteurs de supervision, métriques I/O, et estimations pour le planificateur / optimiseur) sont au cœur à la fois de l’optimisation de performance et des décisions internes de PostgreSQL. Des statistiques transparentes, fiables et manipulables permettent entre autres aux DBAs d’adresser directement l’efficacité de PostgreSQL, et donnent aux «extensions» la possibilité d’améliorer l’expérience utilisateur.</p>
<p>Cela dit, le système historique de statistiques de PostgreSQL n’était pas exempt de frictions: capacité limitée à réinitialiser les statistiques des relations, métriques exprimées dans des unités pas toujours alignées avec les besoins utilisateurs, absence d’API C pour utiliser le moteur de statistiques cumulatives de PostgreSQL. PostgreSQL 18 répond directement à ces préoccupations.</p>
<p>Voici un résumé des améliorations clés.</p>
<h2 id="un-avertissement-sur-les-statistiques"><a class="heading-anchor" href="#un-avertissement-sur-les-statistiques">Un avertissement sur les statistiques</a></h2>
<p>Bien que les statistiques soient extrêmement utiles, leur collecte peut consommer du temps et des ressources. PostgreSQL 18 introduit un point important: avec l’élargissement du spectre des métriques collectables, la taille maximale de la table de hachage a été augmentée. Gardez cela à l’esprit, surtout si vous concevez des systèmes à grande échelle avec des architectures «un ensemble de tables par client»: on a constaté que la limite de 1 Go pouvait être atteinte avec quelques millions de tables.</p>
<h2 id="quoi-de-neuf-dans-postgresql-18-et-les-«-stats-»"><a class="heading-anchor" href="#quoi-de-neuf-dans-postgresql-18-et-les-«-stats-»">Quoi de neuf dans PostgreSQL 18 et les « stats »</a></h2>
<p>Voici les nouvelles fonctionnalités majeures ou les améliorations concernant les statistiques et la supervision.</p>
<p>De manière générale, <a href="https://www.postgresql.org/docs/18/monitoring-stats.html#MONITORING-PG-STAT-IO-VIEW" rel="noopener">pg_stat_io</a> rapporte désormais l’activité I/O en octets plutôt qu’en pages, ce qui est plus pratique pour l’analyse. De plus, les statistiques WAL y ont été déplacées depuis <code>pg_stat_wal</code>, offrant ainsi une vue unique et complète.</p>
<h3 id="mises-a-jour"><a class="heading-anchor" href="#mises-a-jour">Mises à jour</a></h3>
<p><a href="https://www.postgresql.org/docs/18/pgupgrade.html" rel="noopener">pg_upgrade</a> est désormais capable de conserver les statistiques de l’optimiseur, supprimant la nécessité de relancer un <code>ANALYZE</code> complet pour obtenir de bons plans de requêtes après une montée de version: une avancée très attendue pour les grandes bases!</p>
<p>Attention toutefois: les statistiques personnalisées ajoutées par une extension ainsi que celles créées avec <a href="https://www.postgresql.org/docs/18/sql-createstatistics.html" rel="noopener">CREATE STATISTICS</a> ne sont pas conservées.</p>
<p>Vous voudrez sûrement examiner les nouvelles options de <a href="https://www.postgresql.org/docs/18/app-vacuumdb.html" rel="noopener">vacuumdb</a> (<code>--missing-stats-only</code>) pour n’analyser que ce qui est nécessaire.</p>
<p>Dans la même veine, l’option <code>--[no-]statistics</code> a été ajoutée à <a href="https://www.postgresql.org/docs/18/app-pgdump.html" rel="noopener">pg_dump</a>, <a href="https://www.postgresql.org/docs/18/app-pgdumpall.html" rel="noopener">pg_dumpall</a>, et <a href="https://www.postgresql.org/docs/18/app-pgrestore.html" rel="noopener">pg_restore</a>.</p>
<h3 id="maintenance"><a class="heading-anchor" href="#maintenance">Maintenance</a></h3>
<p>Il est désormais plus simple d’évaluer l’effort de maintenance sur les objets, puisque le temps total passé dans les opérations VACUUM et ANALYZE (y compris<br>
automatiques) est rapporté dans <a href="https://www.postgresql.org/docs/18/monitoring-stats.html#MONITORING-PG-STAT-ALL-TABLES-VIEW" rel="noopener">pg_stat_all_tables</a> et ses variantes.</p>
<p>Un nouveau GUC à ne pas oublier: <a href="https://www.postgresql.org/docs/18/runtime-config-statistics.html#GUC-TRACK-COST-DELAY-TIMING" rel="noopener">track_cost_delay_timing</a>. Il collecte le temps passé à dormir (suite aux opérations retardées) pour <code>VACUUM</code> et <code>ANALYZE</code>. Très intéressant, mais comme les autres GUC <code>track_io*</code>, il multiplie les appels à l’horloge système, ce qui peut provoquer un impact sévère sur certaines plateformes. Toujours vérifier avec un outil comme <a href="https://www.postgresql.org/docs/18/pgtesttiming.html" rel="noopener">pg_test_timing</a> pour s’assurer que votre système peut le supporter!</p>
<p>Plus de questions sur l’activité du checkpointer grâce à <a href="https://www.postgresql.org/docs/18/monitoring-stats.html#MONITORING-PG-STAT-CHECKPOINTER-VIEW" rel="noopener">pg_stat_checkpointer</a>. Le nouvel attribut <code>num_done</code> indique le nombre de checkpoints <strong>terminés</strong>. Vous pouvez aussi savoir quel type de buffers a été écrit grâce à <code>slru_written</code>, et <code>buffers_written</code> qui ne concerne désormais que <code>shared_buffers</code>: auparavant, journal et vue n’affichaient pas les mêmes totaux, car un compteur <code>SLRU</code> <a href="https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=17cc5f666" rel="noopener">existait dans un cas mais pas dans l’autre</a>.</p>
<h3 id="analyse"><a class="heading-anchor" href="#analyse">Analyse</a></h3>
<p>Envie d’en savoir plus sur l’I/O traité par un backend (PID) ? Appelez <a href="https://www.postgresql.org/docs/18/monitoring-stats.html#PG-STAT-GET-BACKEND-IO" rel="noopener">pg_stat_get_backend_io(int)</a> et vous obtiendrez une sortie similaire à la vue <code>pg_stat_io</code>, mais pour ce processus. Pour les stats WAL de ce PID: utilisez <a href="https://www.postgresql.org/docs/18/monitoring-stats.html#PG-STAT-GET-BACKEND-WAL" rel="noopener">pg_stat_get_backend_wal(int)</a>.</p>
<p>De nouveaux attributs <code>parallel_workers_to_launch</code> et <code>parallel_workers_launched</code> apparaissent dans <a href="https://www.postgresql.org/docs/18/monitoring-stats.html#MONITORING-PG-STAT-DATABASE-VIEW" rel="noopener">pg_stat_database</a>. Le ratio permet de savoir si nous avons assez de slots pour les workers parallèles.</p>
<p>Changements intéressants dans <a href="https://www.postgresql.org/docs/18/pgstatstatements.html" rel="noopener">pg_stat_statements</a>: plus de requêtes seront regroupées sous le même identifiant. Par exemple, les<br>
motifs <code>IN (1,2,3, ...)</code> n’utiliseront que la première et la dernière constante. Un changement plus contre-intuitif concerne le nom de table utilisé dans une requête: seul le nom est pris en compte, pas le schéma ni l’OID de relation. Cela permet de suivre les tables supprimées ou recréées, mais cela peut aussi regrouper les stats de tables sans lien ayant simplement le même nom. Pour séparer les stats de tables homonymes, il faudra les aliaser dans les requêtes (<code>FROM my.table mt, other.table ot</code>)…</p>
<p>Enfin, ajouts à <a href="https://www.postgresql.org/docs/18/view-pg-backend-memory-contexts.html" rel="noopener">pg_backend_memory_contexts</a> avec <code>path</code> (pour récupérer parent/enfant) et <code>type</code> pour distinguer les contextes <code>AllocSet</code>, <code>Generation</code>, <code>Slab</code> et <code>Bump</code>… Et au fait, que sont <code>Slab</code> et <code>Bump</code> ? Pas documentés ; il faut <a href="https://github.com/postgres/postgres/tree/master/src/backend/utils/mmgr" rel="noopener">lire les en-têtes des fichiers C ici</a>. Ils existent pour optimiser l’allocation, la réallocation et la réinitialisation mémoire selon les usages attendus. Par exemple, <code>Slab</code> est défini comme un «MemoryContext implementation designed for cases where large numbers of equally-sized objects can be allocated and freed efficiently with minimal memory wastage and fragmentation».</p>
<p>Ah, non, encore une dernière: <code>wal_buffers_full</code> a été ajouté à <code>pg_stat_statements</code> pour permettre un meilleur réglage de <code>wal_buffers</code>.</p>
<h3 id="replication"><a class="heading-anchor" href="#replication">Réplication</a></h3>
<p>De meilleures informations apparaissent désormais pour la gestion des conflits en réplication logique grâce à de nouveaux attributs dans <a href="https://www.postgresql.org/docs/18/monitoring-stats.html#MONITORING-PG-STAT-SUBSCRIPTION-STATS" rel="noopener">pg_stat_subscription_stats</a>. En référence, cet extrait du <a href="https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=6c2b5edec" rel="noopener">commit</a> liste les attributs introduits:</p>
<ul class="list">
<li><code>confl_insert_exists</code>: nombre de fois où une insertion de ligne a violé une contrainte unique NOT DEFERRABLE.</li>
<li><code>confl_update_origin_differs</code>: nombre de fois où une mise à jour a été faite sur une ligne déjà modifiée par une autre origine.</li>
<li><code>confl_update_exists</code>: nombre de fois où la valeur mise à jour d’une ligne viole une contrainte unique NOT DEFERRABLE.</li>
<li><code>confl_update_missing</code>: nombre de fois où le tuple à mettre à jour est manquant.</li>
<li><code>confl_delete_origin_differs</code>: nombre de fois où une suppression a été effectuée sur une ligne déjà modifiée par une autre origine.</li>
<li><code>confl_delete_missing</code>: nombre de fois où le tuple à supprimer est manquant.</li>
</ul>
<h3 id="avance"><a class="heading-anchor" href="#avance">Avancé</a></h3>
<p>Un <a href="https://www.postgresql.org/docs/18/functions-admin.html#FUNCTIONS-ADMIN-STATSMOD" rel="noopener">nouveau jeu de fonctions</a> permet désormais de gérer les statistiques de relations et d’attributs (<code>relpages</code>, <code>avg_width</code>, etc.). Cela offre la possibilité d’exporter, importer et ajuster les stats à volonté, afin de répliquer le comportement du planificateur hors production, de maintenir des stats corrigées, et plus encore.</p>
<h3 id="mon-prefere-pour-les-auteurs-dextensions-la-nouvelle-api-c-de-stats"><a class="heading-anchor" href="#mon-prefere-pour-les-auteurs-dextensions-la-nouvelle-api-c-de-stats">Mon préféré pour les auteurs d’extensions: la nouvelle API C de stats</a></h3>
<p>L’une des évolutions les plus enthousiasmantes est ce que PostgreSQL 18 <em>ouvre</em> aux auteurs d’extensions.</p>
<p>Cette petite ligne au bas de la section <a href="https://www.postgresql.org/docs/18/release-18.html#RELEASE-18-MODULES" rel="noopener">E.1.3.9 Modules</a> est celle qui nous intéresse :</p>
<blockquote>
<p>Allow extensions to use the server’s cumulative statistics API (Michael Paquier)</p>
</blockquote>
<p>Jusqu’ici, la manipulation des statistiques était réservée à l’interne ; désormais, une API officielle et structurée est disponible pour construire (ou envelopper) autour.</p>
<p>Le <a href="https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=7949d9594" rel="noopener">message de commit</a> est bien rédigé et couvre l’essentiel des nouveautés. Un sous-ensemble des options est <a href="https://www.postgresql.org/docs/18/xfunc-c.html#XFUNC-ADDIN-CUSTOM-CUMULATIVE-STATISTICS" rel="noopener">détaillé dans la documentation</a>. Cependant, il faut encore plonger dans le code source pour en savoir plus ; en particulier, il vaut la peine de regarder l’extension <code>injection points</code> (fournie avec PostgreSQL) qui utilise cette nouvelle API.</p>
<p>Pour aller plus loin sur la manière dont une extension peut exploiter ces nouvelles capacités, vous pourrez bientôt découvrir <strong>PACS (PostgreSQL Advanced Cumulative Statistics)</strong> sur Codeberg : mon projet qui propose une bibliothèque de wrappers et des utilitaires autour des nouvelles APIs de statistiques de PostgreSQL 18.</p>
<p>En attendant, la présentation que j’ai donnée à <a href="https://archive.fosdem.org/2025/schedule/event/fosdem-2025-4496-stats-roll-baby-stats-roll-/" rel="noopener">FOSDEM 2025</a> explore ce sujet plus en détail.</p>
 ]]></content>
			<author>
				<name>Cédric Villemain</name>
			</author>
    </entry>
    <entry>
      <title>Il était une fois une base confinée - PostgreSQL, QR Codes et l'art de la sauvegarde sans réseau</title>
      <link href="https://www.data-bene.io/fr/blog/sauvegarde-sans-reseau/" />
      <updated>2025-04-01T00:00:00Z</updated>
      <id>https://www.data-bene.io/fr/blog/sauvegarde-sans-reseau/</id>
     <content type="html"><![CDATA[ <h2 id="📦-le-fort-knox-des-bases-de-donnees"><a class="heading-anchor" href="#📦-le-fort-knox-des-bases-de-donnees">📦 Le Fort Knox des bases de données</a></h2>
<p>Il était une fois, dans une salle serveur lointaine, entourée de verre blindé et de béton armé, une base PostgreSQL tellement confinée, tellement isolée, qu’elle ne pouvait que rêver du cloud.</p>
<p>Pas de réseau.</p>
<p>Pas d’USB.</p>
<p>Aucun stockage externe accessible en écriture.</p>
<p>Juste un clavier, un écran, et le bourdonnement des filtres à air de qualité industrielle.</p>
<p>Ce n’était pas un simple environnement “air-gapped”. C’était une zone d’exfiltration zéro, avec une sécurité opérationnelle si rigoureuse qu’on aurait cru qu’elle protégeait des secrets d’État — ou pire, un système bancaire des années 80.</p>
<p>Et pourtant, dans cet oubli numérique, une question demeurait :<br>
<strong>Comment sauvegarder une base PostgreSQL sans jamais extraire un seul fichier ?</strong></p>
<hr>
<h2 id="🎥-quand-lecran-devient-ton-reseau"><a class="heading-anchor" href="#🎥-quand-lecran-devient-ton-reseau">🎥 Quand l’écran devient ton réseau</a></h2>
<p>Notre client ne voulait pas juste une sauvegarde. Il <em>en avait besoin</em>. La crainte n’était pas le vol, mais <strong>la perte totale</strong> : une carte mère qui meurt en silence, enfermant les données à jamais dans sa cage de verre. Et dans ce cas, casser le mur à la masse n’est pas un plan de reprise d’activité très élégant.</p>
<p>Nous avons envisagé toutes les options :</p>
<ul class="list">
<li>OCR d’un défilement de dump SQL ? Trop imprécis.</li>
<li>Filmer la sortie <code>psql</code> ? Beaucoup trop verbeux.</li>
<li>Imprimer l’hexadécimal ? On a quand même une conscience.</li>
</ul>
<p>Et puis, l’illumination : <strong>les QR codes</strong>.</p>
<p>Et si on faisait un <code>pg_dump</code> de la base…</p>
<p>…en QR codes…</p>
<p>…affichés à l’écran…</p>
<p>…filmés par une caméra haute fréquence…</p>
<p>…puis reconstitués image par image à l’extérieur ?</p>
<p>C’était absurde. Donc forcément prometteur.</p>
<hr>
<h2 id="🧠-hacking-pg_dump-la-version-pixelisee"><a class="heading-anchor" href="#🧠-hacking-pg_dump-la-version-pixelisee">🧠 Hacking <code>pg_dump</code> : la version pixelisée</a></h2>
<p>L’outil adoré <code>pg_dump</code> de PostgreSQL est modulaire. On l’a donc enrichi d’un nouvel “archiver” : <code>--format=qrcode</code>.</p>
<p>Voici le fonctionnement :</p>
<ol class="list">
<li><strong>Encodage QR</strong> : chaque portion de sortie SQL est convertie en QR code (format PNG).</li>
<li><strong>Streaming</strong> : au lieu de sauvegarder les images, on les envoie directement sur <code>stdout</code>.</li>
<li><strong>Mise en page</strong> : notre interface affiche plusieurs QR codes à l’écran en haute résolution (on parle de 2000+ pixels — de quoi afficher une grille bien dense).</li>
<li><strong>Capture vidéo</strong> : une machine dédiée avec une caméra 1280Hz filme l’écran et enregistre la séquence complète.</li>
</ol>
<p>Pas de macros. Pas de tricherie. Juste des photons et des images.</p>
<hr>
<h2 id="🔍-reconstruction-a-la-sortie-de-la-cage"><a class="heading-anchor" href="#🔍-reconstruction-a-la-sortie-de-la-cage">🔍 Reconstruction à la sortie de la cage</a></h2>
<p>Une fois la vidéo extraite du cube de verre :</p>
<ul class="list">
<li>Notre parseur lit la vidéo image par image.</li>
<li>Les QR codes sont détectés et décodés en parallèle.</li>
<li>Chaque bloc est identifié par son numéro de séquence.</li>
<li>Le tout est réassemblé en un fichier <code>pg_dump.sql</code> complet.</li>
</ul>
<p>Et voilà : la base renaît — <strong>entièrement exportée sans transfert numérique</strong>.<br>
Rien que de la lumière et des lentilles.</p>
<hr>
<h2 id="🧩-performances-et-fidelite"><a class="heading-anchor" href="#🧩-performances-et-fidelite">🧩 Performances et fidélité</a></h2>
<ul class="list">
<li><strong>QR Version</strong> : Version 40, mode binaire optimisé pour la densité maximale.</li>
<li><strong>Correction d’erreur</strong> : Niveau Q pour une robustesse accrue.</li>
<li><strong>Affichage</strong> : Grille 25×16 QR codes sur un écran 1920×1080 — soit 350 blocs par image (environ 1 Mo par frame).</li>
<li><strong>Vitesse de lecture</strong> : ~60 images/seconde → 21 000 blocs/seconde ≈ 60 Mo/s !</li>
<li><strong>Durée totale</strong> : Une sauvegarde logique complète de moins de 6 Go exportée en moins de 100 minutes !</li>
</ul>
<hr>
<h2 id="🛡️-pourquoi-cest-important"><a class="heading-anchor" href="#🛡️-pourquoi-cest-important">🛡️ Pourquoi c’est important</a></h2>
<p>Ce n’est pas juste une anecdote technique — c’est une preuve que <strong>la souplesse de PostgreSQL s’étend jusqu’à l’absurde</strong>. Les environnements ultra-sécurisés ne sont pas rares : défense, finance, infrastructures critiques… Et quand les outils classiques ne suffisent plus, l’architecture extensible de PostgreSQL permet d’innover, même en milieu extrême.</p>
<p>Chez <strong>Data Bene</strong>, on vit pour ce genre de défi. Optimiser les plans d’exécution ou imaginer des méthodes d’exfiltration qui ressemblent à de l’espionnage, c’est notre terrain de jeu.</p>
<hr>
<p><em>Envie d’essayer ? Écrivez-nous — on adore les sauvegardes bizarres.</em></p>
<p><em>Et si jamais vous pensez à utiliser <code>pg_restore</code> en spectacle laser, appelez-nous. On est curieux.</em></p>
 ]]></content>
			<author>
				<name>Cédric Villemain</name>
			</author>
    </entry>
    <entry>
      <title>Postgres Café : Développez vos capacités de surveillance avec StatsMgr</title>
      <link href="https://www.data-bene.io/fr/blog/postgres-cafe-developpez-vos-capacities-de-surveillance-avec-statsmgr/" />
      <updated>2025-01-07T00:00:00Z</updated>
      <id>https://www.data-bene.io/fr/blog/postgres-cafe-developpez-vos-capacities-de-surveillance-avec-statsmgr/</id>
     <content type="html"><![CDATA[ <p>L’année 2025 a commencé et nous sommes ravis de publier le deuxième épisode de <a href="https://www.youtube.com/watch?v=WwaJd2c9whM" rel="noopener">Postgres Café</a>, une série de blogs et de vidéos de nos équipes chez <a href="https://www.data-bene.io/fr/" rel="noopener">Data Bene</a> et <a href="https://xata.io/" rel="noopener">Xata</a>, réalisée dans le but d’explorer le monde de l’open source et de découvrir ses liens avec l’extensibilité de PostgreSQL. Tout au long de cette série, nous discutons de différentes extensions et des outils qui facilitent la vie des développeurs lorsqu’ils travaillent avec PostgreSQL. Dans notre deuxième épisode, nous explorons une toute nouvelle extension PostgreSQL appelée <a href="https://codeberg.org/data-bene/statsmgr" rel="noopener">StatsMgr</a> qui exploite les processus en tâche de fond (background workers) et la mémoire partagée pour capturer, gérer et interroger diverses statistiques pour WAL, SLRU, IO, checkpointing, etc.</p>
<h2 id="episode-2-statsmgr"><a class="heading-anchor" href="#episode-2-statsmgr"><strong>Épisode 2: StatsMgr</strong></a></h2>
<p>Dans cet épisode nous présentons l’extension open source StatsMgr, récemment publiée, créée pour surveiller et suivre en continu les événements sur PostgreSQL et le système sous-jacent. Voici un aperçu de ce que cet épisode a couvert :</p>
<h3 id="traitement-personnalise-des-metriques"><a class="heading-anchor" href="#traitement-personnalise-des-metriques"><strong>Traitement personnalisé des métriques</strong></a></h3>
<p>À l’origine, l’idée était de fournir une interface simplifiée pour les métriques, tout en les enrichissant avec une grande variété de types disponibles. Cette fonctionnalité a ensuite été étendue pour résoudre des problèmes tels que :</p>
<ul class="list">
<li><strong>Rendre les statistiques disponibles</strong> pour la collecte à partir de systèmes externes, sans interruption même lorsque ces systèmes externes sont en panne.</li>
<li><strong>Fournir une vue immédiate des statistiques PostgreSQL</strong> avec suivi historique, y compris les vues et fonctions pg_stat.</li>
<li><strong>Augmenter et réduire la quantité d’enregistrements historiques</strong> en cas de besoin grâce à l’allocation de mémoire tampon dynamique.</li>
<li><strong>Déboguer des instances PostgreSQL</strong> avec analyse historique et sans redémarrages requis.</li>
</ul>
<p>Cette extension est aussi très bien pour gérer des situations telles que lorsque…</p>
<ul class="list">
<li><strong>…votre agent de surveillance est en panne</strong> ; l’utilisation de StatsMgr comme sauvegarde vous permet de vous assurer que vous ne perdrez pas de statistiques dans cet événement, car les événements sont capturés indépendamment et stockés pour être collectés ultérieurement par votre agent de surveillance.</li>
<li><strong>…vous avez des pics ou un comportement inhabituel sur votre système de production.</strong> Cette extension vous permet d’obtenir un aperçu de l’activité pour des informations de débogage utiles.</li>
</ul>
<h3 id="collection-etendue-et-historique-de-mesures"><a class="heading-anchor" href="#collection-etendue-et-historique-de-mesures"><strong>Collection étendue et historique de mesures</strong></a></h3>
<p>Actuellement, les types de statistiques pris en charge incluent :</p>
<ul class="list">
<li>WAL</li>
<li>SLRU</li>
<li>BGWriter</li>
<li>Checkpointer</li>
<li>Archiver</li>
<li>IO</li>
</ul>
<p>Chacun est enregistré auprès d’un gestionnaire qui vous permet de récupérer et de gérer ces statistiques, et est également accompagné de structures de mémoire partagée pour stocker des instantanés historiques.</p>
<p>Certaines des prochaines étapes du projet comprendront l’ajout de statistiques dynamiques telles que pg_stat_user_tables, entre autres.</p>
<p>Il reste encore beaucoup de choses à faire, des améliorations subtiles aux nouvelles fonctionnalités majeures. Il existe donc bien sûr de nombreuses opportunités de contribuer au projet, que vous soyez un nouveau venu ou un développeur PostgreSQL confirmé. Vous souhaitez participer à l’effort ? Consultez <a href="https://codeberg.org/Data-Bene/StatsMgr/src/branch/main/CONTRIBUTING.md" rel="noopener">CONTRIBUTING.md</a> dans le projet.</p>
<h3 id="regardez-lepisode-complet"><a class="heading-anchor" href="#regardez-lepisode-complet"><strong>Regardez l’épisode complet</strong></a></h3>
<p>Pour une exploration approfondie de StatsMgr et de ses capacités, regardez l’épisode complet ici :</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/UMzCLFwCPI8?si=_ZCEoWYtFW_UCKXm" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<h3 id="restez-a-lecoute-pour-plus-doutils-postgres"><a class="heading-anchor" href="#restez-a-lecoute-pour-plus-doutils-postgres"><strong>Restez à l’écoute pour plus d’outils Postgres</strong></a></h3>
<p>Il y a encore beaucoup à venir sur Postgres Café. <a href="https://www.youtube.com/playlist?list=PLf7KS0svgDP_zJmby3RMzzOVO45qLbruA" rel="noopener">Abonnez-vous à la playlist</a> pour découvrir des épisodes qui présentent d’autres outils open source comme <a href="https://pgroll.com/" rel="noopener">pgroll</a> pour les migrations de schémas sans interruption de service, ou <a href="https://www.citusdata.com/" rel="noopener">Citus Data</a> une extension pour des bases PostgreSQL distribuées et évolutives, et bien plus encore. Restez en contact pour découvrir comment chaque outil peut rendre le travail avec Postgres plus fluide et plus efficace.</p>
 ]]></content>
			<author>
				<name>Sarah Conway</name>
			</author>
    </entry>
    <entry>
      <title>Transformations inattendues des types de données</title>
      <link href="https://www.data-bene.io/fr/blog/transformations-inattendues-de-types-de-donn%C3%A9es/" />
      <updated>2024-12-02T00:00:00Z</updated>
      <id>https://www.data-bene.io/fr/blog/transformations-inattendues-de-types-de-donn%C3%A9es/</id>
     <content type="html"><![CDATA[ <h2 id="les-types-de-donnees-de-votre-fonction-ont-change-et-vous-navez-pas-ete-informes"><a class="heading-anchor" href="#les-types-de-donnees-de-votre-fonction-ont-change-et-vous-navez-pas-ete-informes">Les types de données de votre fonction ont changé et vous n’avez pas été informés</a></h2>
<p>Cet article est le résultat d’une revue de code demandée par l’un de nos clients.</p>
<p>Notre client a exécuté la commande <code>pg_dump --schema-only</code> pour nous fournir<br>
les définitions des objets de niveau <code>database</code> ainsi que le code PLpgsql à auditer.<br>
Jusque là, tout allait bien.</p>
<p>J’ai commencé à lire le code et tout d’un coup je fus pris d’un doute, vous savez<br>
le méchant doute qui vous donne l’impression que votre château de cartes va s’écrouler.<br>
Voici la fonction que j’avais sous les yeux:</p>
<pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">FUNCTION</span> xxx<span class="token punctuation">(</span> p_id <span class="token keyword">character</span><span class="token punctuation">,</span> p_info <span class="token keyword">character</span> <span class="token keyword">varying</span> <span class="token punctuation">)</span>
<span class="token keyword">RETURNS</span> <span class="token keyword">integer</span>
<span class="token keyword">LANGUAGE</span> plpgsql
<span class="token keyword">AS</span> $$
<span class="token keyword">DECLARE</span>
<span class="token keyword">BEGIN</span>
   <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
   <span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> t1
   <span class="token keyword">SELECT</span> <span class="token operator">*</span> <span class="token keyword">FROM</span> t2 <span class="token keyword">WHERE</span> t2<span class="token punctuation">.</span>id <span class="token operator">=</span> p_id<span class="token punctuation">;</span>
   <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token keyword">END</span><span class="token punctuation">;</span>
$$
<span class="token punctuation">;</span></code></pre>
<p>Peut-être que la première lecture de la fonction ne vous a pas choqué.<br>
Et si je vous disais que la définition de la table <code>t2</code> ressemble à ce qui suit:</p>
<pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> t2 <span class="token punctuation">(</span>
   id <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">130</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span>
   <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
   <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span>id<span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>En pratique <code>t2.id</code> est toujours une chaîne de 130 caractères et il y a 400<br>
millions de tuples dans cette table. Comme vous l’avez déjà probablement compris,<br>
il semble étonnant d’avoir <code>p_id CHARACTER</code> pour manipuler <code>id VARCHAR(130)</code>. Pour<br>
mémoire, <code>CHARACTER</code> est l’alias de <code>CHAR(1)</code>.</p>
<p>Notre client utilise cette fonction depuis plusieurs années et il n’a pas eu de<br>
problème avec ce code. Néanmoins, notre client m’a dit que la définition de cette<br>
fonction ne correspond pas à ce qu’il a écrit: c’était <code>p_id CHARACTER(130)</code> et<br>
non <code>CHARACTER</code>.</p>
<p>Mais que s’est-il donc passé ? Attaquons la pelotte de laine et voyons si Ariane<br>
nous est d’un grand secours.</p>
<pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">FUNCTION</span> test<span class="token punctuation">(</span> c <span class="token keyword">character</span><span class="token punctuation">,</span> d <span class="token keyword">character</span> <span class="token keyword">varying</span> <span class="token punctuation">)</span>
<span class="token keyword">RETURNS</span> void
<span class="token keyword">LANGUAGE</span> plpgsql
<span class="token keyword">AS</span> $$
<span class="token keyword">BEGIN</span>
  RAISE NOTICE <span class="token string">'c=%, d=%'</span><span class="token punctuation">,</span> c<span class="token punctuation">,</span>d<span class="token punctuation">;</span>
<span class="token keyword">END</span><span class="token punctuation">;</span>
$$<span class="token punctuation">;</span>

<span class="token keyword">SELECT</span> test<span class="token punctuation">(</span> <span class="token string">'123465789'</span><span class="token punctuation">,</span> <span class="token string">'987654321'</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
NOTICE:  c<span class="token operator">=</span><span class="token number">123465789</span><span class="token punctuation">,</span> d<span class="token operator">=</span><span class="token number">987654321</span>
 test 
<span class="token comment">------</span>
 
<span class="token punctuation">(</span><span class="token number">1</span> <span class="token keyword">row</span><span class="token punctuation">)</span></code></pre>
<p>Voilà un résultat bien intéressant: la chaîne ‘123456789’ n’a pas été réduite à<br>
‘1’ (pas de CAST <code>CHAR(1)</code>). Augmentons le niveau de détails:</p>
<pre class="language-sql"><code class="language-sql"><span class="token keyword">EXPLAIN</span> <span class="token punctuation">(</span>COSTS <span class="token keyword">OFF</span><span class="token punctuation">,</span><span class="token keyword">ANALYZE</span><span class="token punctuation">,</span>VERBOSE<span class="token punctuation">)</span>
        <span class="token keyword">SELECT</span> test<span class="token punctuation">(</span> <span class="token string">'123465789'</span><span class="token punctuation">,</span> <span class="token string">'987654321'</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
NOTICE:  c<span class="token operator">=</span><span class="token number">123465789</span><span class="token punctuation">,</span> d<span class="token operator">=</span><span class="token number">987654321</span>
                             QUERY <span class="token keyword">PLAN</span>                              
<span class="token comment">---------------------------------------------------------------------</span>
 Result <span class="token punctuation">(</span>actual <span class="token keyword">time</span><span class="token operator">=</span><span class="token number">0.040</span><span class="token punctuation">.</span><span class="token number">.0</span><span class="token number">.041</span> <span class="token keyword">rows</span><span class="token operator">=</span><span class="token number">1</span> loops<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">)</span>
   Output: test<span class="token punctuation">(</span><span class="token string">'123465789'</span>::bpchar<span class="token punctuation">,</span> <span class="token string">'987654321'</span>::<span class="token keyword">character</span> <span class="token keyword">varying</span><span class="token punctuation">)</span>
 Planning <span class="token keyword">Time</span>: <span class="token number">0.023</span> ms
 Execution <span class="token keyword">Time</span>: <span class="token number">0.053</span> ms
<span class="token punctuation">(</span><span class="token number">4</span> <span class="token keyword">rows</span><span class="token punctuation">)</span></code></pre>
<p>Nous observons un CAST vers <code>bpchar</code>. Pour mémoire, <code>BPCHAR</code> représente une<br>
chaîne pouvant contenir 10 485 760 caractères. Il est aussi un alias de<br>
<code>CHARACTER</code> ce qui explique partiellement le passage de <code>CHARACTER</code> à <code>BPCHAR</code>.</p>
<p>Explorons une autre facette du problème:</p>
<pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">FUNCTION</span> test<span class="token punctuation">(</span>c <span class="token keyword">character</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token keyword">RETURNS</span> <span class="token keyword">character</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span>
<span class="token keyword">LANGUAGE</span> <span class="token keyword">sql</span>
<span class="token keyword">AS</span> $$
<span class="token keyword">select</span> c<span class="token punctuation">;</span>
$$<span class="token punctuation">;</span></code></pre>
<p>Nous avons changé le langage de la fonction pour savoir si le comportement observé<br>
est propre à PLpgsql. Les données en entrée et en sortie sont de type <code>CHAR(4)</code>.<br>
Comment cela va-t-il se comporter ?</p>
<pre class="language-sql"><code class="language-sql"><span class="token keyword">SELECT</span> test<span class="token punctuation">(</span><span class="token string">'123456789'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
   test    
<span class="token comment">-----------</span>
 <span class="token number">123456789</span>
<span class="token punctuation">(</span><span class="token number">1</span> <span class="token keyword">row</span><span class="token punctuation">)</span>

<span class="token keyword">EXPLAIN</span> VERBOSE <span class="token keyword">SELECT</span> test<span class="token punctuation">(</span><span class="token string">'123456789'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                QUERY <span class="token keyword">PLAN</span>                 
<span class="token comment">-------------------------------------------</span>
 Result  <span class="token punctuation">(</span>cost<span class="token operator">=</span><span class="token number">0.00</span><span class="token punctuation">.</span><span class="token number">.0</span><span class="token number">.01</span> <span class="token keyword">rows</span><span class="token operator">=</span><span class="token number">1</span> width<span class="token operator">=</span><span class="token number">32</span><span class="token punctuation">)</span>
   Output: <span class="token string">'123456789'</span>::bpchar
<span class="token punctuation">(</span><span class="token number">2</span> <span class="token keyword">rows</span><span class="token punctuation">)</span></code></pre>
<p>La bonne nouvelle (si si) c’est que le comportement observé est le même dans<br>
les deux contextes. En revanche, alors que l’on s’attend à manipuler des données<br>
de type <code>CHAR(4)</code>, la fonction manipule des données de taille arbitraire (limitée<br>
à ~10M caractères) !!</p>
<p>Cependant, ne vous précipitez pas dans la mailing list de PostgreSQL pour vous plaindre !</p>
<p>En fait, ce comportement n’est pas un bug. La <a href="https://www.postgresql.org/docs/current/sql-createfunction.html" rel="noopener">documentation</a> affirme:</p>
<blockquote>
<p>“The full SQL type syntax is allowed for declaring a function’s arguments and return value. However, parenthesized type modifiers (e.g., the precision field for type numeric) are discarded by CREATE FUNCTION. Thus for example CREATE FUNCTION foo (varchar(10)) … is exactly the same as CREATE FUNCTION foo (varchar) …”</p>
</blockquote>
<p>Ceci explique que <code>CHARACTER(x)</code> devient <code>CHARACTER</code> transformé (par aliasing)<br>
en <code>BPCHAR</code> qui n’est pas <code>CHAR(1)</code> mais plutôt <code>VARCHAR(10485760)</code>. Le<br>
comportement est maintenant complétement expliqué.</p>
<p>Attendez une seconde ! L’intention était de traiter des données <code>CHAR(4)</code> pas<br>
de taille différente.</p>
<p>N’y a-t-il pas de solution ? Non, désolé… (à mois que…)</p>
<p>Plus loin dans la page de la documentation référencée ci-dessus, on peut lire<br>
que <code>argtype</code> et <code>rettype</code> peuvent être de type <code>base</code>, <code>composite</code>, <code>domain</code><br>
ou même du type d’une table/colonne.</p>
<p>L’astuce est de créer un type <code>composite</code> ou un <code>domain</code> a utiliser en tant que<br>
<code>argtype</code> et/ou <code>rettype</code>.</p>
<p>Voici quelques exemples:</p>
<pre class="language-sql"><code class="language-sql"><span class="token comment">-- Works in simple case trick</span>
<span class="token keyword">SELECT</span> test<span class="token punctuation">(</span> <span class="token string">'12345789'</span>::<span class="token keyword">char</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">-- Domain trick</span>
<span class="token keyword">CREATE</span> DOMAIN c4 <span class="token keyword">AS</span> <span class="token keyword">char</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">CREATE</span> <span class="token keyword">FUNCTION</span> test<span class="token punctuation">(</span>param c4<span class="token punctuation">)</span>
<span class="token keyword">RETURNS</span> c4
<span class="token keyword">AS</span> $$
<span class="token keyword">BEGIN</span>
  RAISE NOTICE <span class="token string">'param=%'</span><span class="token punctuation">,</span> param<span class="token punctuation">;</span>
  <span class="token keyword">RETURN</span> param<span class="token punctuation">;</span>
<span class="token keyword">END</span><span class="token punctuation">;</span>
$$ <span class="token keyword">LANGUAGE</span> plpgsql<span class="token punctuation">;</span>

<span class="token keyword">SELECT</span> test<span class="token punctuation">(</span> <span class="token string">'123456789'</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
ERROR:  <span class="token keyword">value</span> too long <span class="token keyword">for</span> <span class="token keyword">type</span> <span class="token keyword">character</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span>

<span class="token keyword">SELECT</span> test<span class="token punctuation">(</span> <span class="token string">'123456789'</span>::<span class="token keyword">char</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
NOTICE:  param<span class="token operator">=</span><span class="token number">1234</span>
 test 
<span class="token comment">------</span>
 <span class="token number">1234</span>
<span class="token punctuation">(</span><span class="token number">1</span> <span class="token keyword">row</span><span class="token punctuation">)</span>

<span class="token keyword">SELECT</span> test<span class="token punctuation">(</span> <span class="token string">'123456789'</span>::c4<span class="token punctuation">)</span><span class="token punctuation">;</span>
NOTICE:  param<span class="token operator">=</span><span class="token number">1234</span>
 test 
<span class="token comment">------</span>
 <span class="token number">1234</span>
<span class="token punctuation">(</span><span class="token number">1</span> <span class="token keyword">row</span><span class="token punctuation">)</span>

<span class="token keyword">SELECT</span> pg_typeof<span class="token punctuation">(</span> test<span class="token punctuation">(</span> <span class="token string">'123456789'</span>::<span class="token keyword">char</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
NOTICE:  param<span class="token operator">=</span><span class="token number">1234</span>
 pg_typeof 
<span class="token comment">-----------</span>
 c4
<span class="token punctuation">(</span><span class="token number">1</span> <span class="token keyword">row</span><span class="token punctuation">)</span></code></pre>
<p>Le résultat obtenu devrait vous satisfaire.</p>
<p>Vous n’êtes pas satisfait ?</p>
<p>Je vous propose une dernière astuce.</p>
<pre class="language-sql"><code class="language-sql"><span class="token comment">-- Map a table structure</span>
<span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> qq <span class="token punctuation">(</span> c <span class="token keyword">char</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">CREATE</span> <span class="token keyword">FUNCTION</span> test<span class="token punctuation">(</span><span class="token operator">IN</span> c qq<span class="token punctuation">,</span> <span class="token keyword">OUT</span> d qq<span class="token punctuation">)</span>
<span class="token keyword">LANGUAGE</span> <span class="token keyword">sql</span>
<span class="token keyword">AS</span> $$
<span class="token keyword">SELECT</span> c<span class="token punctuation">;</span>
$$<span class="token punctuation">;</span>

<span class="token keyword">SELECT</span> <span class="token operator">*</span> <span class="token keyword">FROM</span> test<span class="token punctuation">(</span><span class="token keyword">ROW</span><span class="token punctuation">(</span><span class="token string">'12345'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ERROR:  <span class="token keyword">value</span> too long <span class="token keyword">for</span> <span class="token keyword">type</span> <span class="token keyword">character</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span>

<span class="token keyword">SELECT</span> <span class="token operator">*</span> <span class="token keyword">from</span> test<span class="token punctuation">(</span><span class="token keyword">ROW</span><span class="token punctuation">(</span><span class="token string">'1234'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  c   
<span class="token comment">------</span>
 <span class="token number">1234</span></code></pre>
<p>Hmm, d’accord mais en quoi est-ce différent de l’utilisation d’un <code>domain</code> ?</p>
<pre class="language-sql"><code class="language-sql"><span class="token comment">-- Easy Type Alteration</span>
<span class="token keyword">ALTER</span> <span class="token keyword">TABLE</span> qq <span class="token keyword">ALTER</span> c <span class="token keyword">TYPE</span> <span class="token keyword">char</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">SELECT</span> <span class="token operator">*</span> <span class="token keyword">FROM</span> test<span class="token punctuation">(</span> <span class="token keyword">ROW</span><span class="token punctuation">(</span><span class="token string">'12345'</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
   c   
<span class="token comment">-------</span>
 <span class="token number">12345</span></code></pre>
<p>Essayer de modifier un <code>domain</code>, vous verrez comme c’est simple et pas impactant (…).</p>
<p>L’astuce basée sur la définition de table permet de la flexibilité:</p>
<pre class="language-sql"><code class="language-sql"><span class="token keyword">ALTER</span> <span class="token keyword">TABLE</span> qq <span class="token keyword">ADD</span> ee <span class="token keyword">int</span><span class="token punctuation">;</span>

<span class="token keyword">SELECT</span> test<span class="token punctuation">(</span> <span class="token keyword">ROW</span><span class="token punctuation">(</span><span class="token string">'12345'</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
   test   
<span class="token comment">----------</span>
 <span class="token punctuation">(</span><span class="token number">12345</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">)</span>

<span class="token keyword">SELECT</span> <span class="token operator">*</span> <span class="token keyword">FROM</span> test<span class="token punctuation">(</span> <span class="token keyword">ROW</span><span class="token punctuation">(</span><span class="token string">'12345'</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
   c   <span class="token operator">|</span> ee 
<span class="token comment">-------+----</span>
 <span class="token number">12345</span> <span class="token operator">|</span>  <span class="token number">4</span></code></pre>
<p>Nous espérons que vous avez apprécié cet article et que vous avez appris quelque chose de nouveau et d’intéressant.</p>
 ]]></content>
			<author>
				<name>Frédéric Delacourt</name>
			</author>
    </entry>
</feed>
