<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr">
  <title>Data Bene - Frédéric Delacourt</title>
  <subtitle>Base de données relationnelles, open-source et adaptives.</subtitle>
  <link href="https://www.data-bene.io/fr/authors/frederic-delacourt/feed.xml" rel="self" type="application/atom+xml" />
  <updated>2025-11-13T00:00:00Z</updated>
  <id>https://www.data-bene.io/fr/authors/frederic-delacourt/feed.xml</id>
    <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>
    <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>
</feed>
