Featured image of post Ne vous laissez pas tromper par la désinvolture de SQLite ! Quels sont les pièges des types dynamiques ? Pourquoi ALTER TABLE est-il à moitié opérationnel ? Comment construire une architecture de programmation défensive en Node.js pour des mises à niveau de schéma sans douleur ?

Ne vous laissez pas tromper par la désinvolture de SQLite ! Quels sont les pièges des types dynamiques ? Pourquoi ALTER TABLE est-il à moitié opérationnel ? Comment construire une architecture de programmation défensive en Node.js pour des mises à niveau de schéma sans douleur ?

SQLite adopte un système de types dynamique et faible ; insérer une chaîne dans une colonne INTEGER ne générera étonnamment pas d'erreur. Découvrez les pièges de Type Affinity dans SQLite, l'impact de l'absence de types Boolean et Date natifs, les limitations de ALTER TABLE ainsi que la stratégie de mise à niveau sécurisée en 'quatre étapes pour recréer et déplacer', et comment construire des architectures de programmation défensive avec des outils comme TypeScript, Zod et Prisma.

Imaginez un bac de recyclage sur lequel vous avez clairement apposé une étiquette indiquant "Bouteilles en plastique uniquement", mais lorsque quelqu’un y jette un morceau de papier, il l’accepte silencieusement sans un seul mot de protestation ?

C’est l’expérience effrayante que vivent les développeurs lorsqu’ils découvrent le système de types de SQLite pour la première fois.

Si vous êtes habitué au style rigide d’agent des douanes de PostgreSQL (où les types incorrects sont directement rejetés), la désinvolture de SQLite pourrait vous faire douter de votre vie.

Encore plus effrayant, lorsque vous souhaitez modifier la structure d’une table, il vous dira :

"La table ne peut pas être modifiée directement. Veuillez construire une nouvelle maison, y déménager les meubles, puis faire sauter l’ancienne maison."

Sous le capot, SQLite ne possède que 5 classes de stockage

Peu importe les noms de types élégants que vous déclarez dans CREATE TABLE (VARCHAR(255), BIGINT, DECIMAL), SQLite sous le capot ne reconnaît que ces 5 classes de stockage :

Classe de stockage Description
NULL Valeur nulle
INTEGER Entier (occupe automatiquement 1 à 8 octets selon l’importance de la valeur)
REAL Nombre à virgule flottante (fixé à 8 octets)
TEXT Chaîne de caractères (encodage UTF-8 par défaut)
BLOB Grand objet binaire (stocké exactement comme saisi)

Les types que vous définissez sur les colonnes sont simplement des "suggestions" pour SQLite, pas des "règles obligatoires".

C’est ce qu’on appelle la "Type Affinity" (Affinité de types). SQLite tentera de convertir vos données vers le type suggéré, mais s’il ne le peut pas, il insérera simplement les données d’origine sans générer d’erreur.

Vous pouvez déclarer une colonne age INTEGER dans SQLite puis y insérer la chaîne 'toujours dix-huit ans'; il l’acceptera volontiers.

Trois pièges de types les plus faciles à commettre

Piège 1 : Pas de Boolean nativo

SQLite n’a pas de type booléen. True et False ne peuvent être représentés que par les entiers 1 et 0.

Lorsque vous récupérez des données de SQLite à l’aide de Node.js, vous obtiendrez le nombre 1 ou 0, pas true ou false.

Si vous effectuez directement une vérification telle que if (user.is_admin === true), elle ne sera jamais vraie.

Piège 2 : Pas de type Date/Time

SQLite n’a pas de type date/heure. Vous ne pouvez stocker le temps que sous forme de :

Méthode de stockage Exemple Avantages & Inconvénients
TEXT (chaîne ISO-8601) '2026-05-19T18:00:00Z' Le plus recommandé, grande lisibilité, conversion parfaite lors du passage à PostgreSQL à l’avenir
INTEGER (Unix Timestamp) 1747656000 Faible encombrement, mais illisible pour l’homme

Ne stockez jamais de dates dans des formats personnalisés arbitraires comme 19/05/2026 18:00, sinon la future migration de données sera une catastrophe.

Piège 3 : Insérer une chaîne dans une colonne Integer ne générera pas d’erreur

Dans PostgreSQL, insérer une chaîne dans une colonne INTEGER génère une erreur immédiatement. Mais SQLite essaiera seulement de la convertir silencieusement, et en cas d’échec, l’acceptera telle quelle.

Cela signifie que des données sales peuvent s’infiltrer silencieusement dans votre base de données jusqu’à ce qu’un jour votre programme plante en raison de la réception d’un type inattendu, ce n’est qu’alors que vous découvrirez le problème.

Programmation défensive : Traiter une base de données désinvolte avec une attitude stricte

Face à la désinvolture de SQLite, vous devez construire des mécanismes de défense stricts dans le développement avec Node.js :

Niveau de défense Outil Rôle
Garde au compile-time TypeScript Capturer les types incorrects à l’étape d’écriture du code
Validation d’entrée d’API Zod Valider les données entrantes strictement (s’assurer que age est toujours un nombre)
Conversion implicite de types Prisma / Drizzle ORM Gérer automatiquement les différences de types entre SQLite et PostgreSQL

Déplacer le "videur de validation de données" de la couche base de données vers la couche application Node.js est une stratégie clé pour tirer parti de la rapidité de développement de SQLite tout en garantissant l’évolutivité future.

Lors de l’utilisation d’un ORM, tant que vous déclarez type: 'boolean' dans votre code, l’ORM le convertit automatiquement en 1/0 lors de la sauvegarde dans SQLite, et le reconvertit en true/false lors de la lecture, masquant parfaitement les différences de types sous-jacentes.

ALTER TABLE est à moitié opérationnel : Ce qui peut et ne peut pas être modifié

Le support de SQLite pour la modification des structures de tables est très limité :

Opération Pris en charge
Ajouter une colonne (ADD COLUMN) Oui
Renommer une colonne (RENAME COLUMN) Oui
Supprimer une colonne (DROP COLUMN) Oui (dans les versions plus récentes)
Renommer une table (RENAME TO) Oui
Modifier le type d’une colonne Non
Ajouter/supprimer les contraintes UNIQUE, NOT NULL Non
Modifier la clé primaire Non
Modifier la clé étrangère Non

Une fois que vous devez effectuer l’une des modifications "non prises en charge", SQLite exige que vous exécutiez la stratégie de "recréer et déplacer".

Les quatre étapes pour recriar et déplacer : La façon de mettre à niveau les tables de SQLite

Comme cela ne peut pas être modifié directement, la pratique standard recommandée par les documents officiels est de construire une nouvelle maison, d’y déménager les meubles, de faire sauter l’ancienne maison et d’apposer la nouvelle plaque nominative :

Étape Action Description
1 Créer une nouvelle table CREATE TABLE users_new (...) en utilisant la structure correcte
2 Copier les données INSERT INTO users_new SELECT ... FROM users
3 Supprimer l’ancienne table DROP TABLE users
4 Renommer la table ALTER TABLE users_new RENAME TO users

Ces quatre étapes doivent être exécutées d’une seule traite; toute coupure de courant ou crash d’application à mi-chemin entraînera la perte de données.

Garantir que les mises à niveau ne perdent pas de données : Deux lignes de défense de sécurité

Ligne de défense 1 : Défense physique, copier le fichier directement

SQLite est essentiellement un simple fichier. Avant d’exécuter des modifications de schéma, copiez simplement le fichier .db en tant que sauvegarde.

const fs = require('fs');
fs.copyFileSync('my_project.db', 'my_project_backup.db');

Si les choses tournent mal, le remplacement du fichier restaure tout en un clin d’œil. C’est un avantage que d’autres grandes bases de données ne peuvent pas offrir.

Ligne de défense 2: Enveloppe de Transaction, la machine à remonter le temps de la base de données

Enveloppez toutes les étapes de migration dans une seule Transaction; si une étape échoue, tout le processus sera automatiquement annulé (Rollback) comme si rien ne s’était passé.

const Database = require('better-sqlite3');
const db = new Database('my_project.db');

const migrateData = db.transaction(() => {
  db.prepare(`
    CREATE TABLE users_new (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      age INTEGER NOT NULL DEFAULT 18
    )
  `).run();

  db.prepare(`
    INSERT INTO users_new (id, name, age)
    SELECT id, name, COALESCE(age, 18) FROM users
  `).run();

  db.prepare('DROP TABLE users').run();
  db.prepare('ALTER TABLE users_new RENAME TO users').run();
});

try {
  migrateData();
  console.log('Table mise à niveau avec succès');
} catch (error) {
  console.error('La mise à niveau a échoué, données restaurées en toute sécurité :', error.message);
}

"Fichier de sauvegarde + Liaison par Transaction" constitue l’airbag de la migration de votre base de données.

Contrôlez la désinvolture de SQLite avec une attitude stricte

Domptez la désinvolture de SQLite avec une architecture stricte de couche d’application pour profiter de sa vitesse de développement ultrarapide tout en évitant les dettes techniques futures.

Le système de types de SQLite est très désinvolte, et ALTER TABLE présente de nombreuses limitations.

Cependant, tant que vous effectuez la vérification des types TypeScript + validation Zod + l’abstraction ORM du côté de Node.js, combinées à la stratégie de sécurité de sauvegarde physique + Transaction, vous pouvez profiter en toute sécurité de l’efficacité de développement offerte par SQLite tout en ouvrant la voie à une migration aisée vers PostgreSQL à l’avenir.

Reference

All rights reserved,未經允許不得隨意轉載
Généré avec Hugo
Thème Stack conçu par Jimmy