feat(db): les énumérations deviennent des tables, dans un schéma dédiéUnverified
Les domaines fermés (langue, thème, format des stats) passaient par un CHECK IN : les élargir demandait un DDL qui réécrit la contrainte. Ils deviennent des tables exego_enums.enum_xxx référencées par clé étrangère, où ajouter une valeur est un INSERT. Les colonnes métier gardent leur TEXT et leur valeur — la clé primaire de la table enum EST le code — donc aucune requête ne change. Le schéma dédié tient ces tables hors du scope applicatif : seules les FK y renvoient. todo_states, qui appliquait déjà le principe depuis public, rejoint le schéma sous le nom enum_todo_state ; ses deux FK entrantes suivent le déplacement. minutes > 0 reste un CHECK : ce n'est pas une énumération.
8c08a86 parent: 906353d added
migrations/0013_enum_tables.sql +84 -0 | new file mode 100644 | ||
| @@ -0,0 +1,84 @@ | ||
| 1 | +-- Principe : une valeur d'énumération n'est pas un CHECK, c'est une ligne. | |
| 2 | +-- Chaque domaine fermé devient une table `enum_xxx` référencée par une clé | |
| 3 | +-- étrangère — élargir une énumération est alors un INSERT (donc réversible, | |
| 4 | +-- inspectable, joignable) au lieu d'un DDL qui réécrit une contrainte. | |
| 5 | +-- | |
| 6 | +-- Ces tables vivent dans le schéma `exego_enums` pour ne pas encombrer le | |
| 7 | +-- scope applicatif : aucune requête de l'app ne les nomme, seules les clés | |
| 8 | +-- étrangères y renvoient (l'intégrité référentielle ignore le search_path). | |
| 9 | +CREATE SCHEMA IF NOT EXISTS exego_enums; | |
| 10 | + | |
| 11 | +COMMENT ON SCHEMA exego_enums IS | |
| 12 | + 'Tables de référence des énumérations (une ligne par valeur permise).'; | |
| 13 | + | |
| 14 | +-- Colonnes communes : `code` est la valeur stockée dans les tables métier, | |
| 15 | +-- `sort_order` l'ordre d'affichage, `description` un libellé de lecture | |
| 16 | +-- humaine EN BASE — jamais du texte d'interface (celui-ci reste dans i18n.rs). | |
| 17 | + | |
| 18 | +-- users.lang ----------------------------------------------------------------- | |
| 19 | +CREATE TABLE exego_enums.enum_lang ( | |
| 20 | + code TEXT PRIMARY KEY, | |
| 21 | + sort_order SMALLINT NOT NULL, | |
| 22 | + description TEXT NOT NULL | |
| 23 | +); | |
| 24 | +INSERT INTO exego_enums.enum_lang (code, sort_order, description) VALUES | |
| 25 | + ('fr', 1, 'français'), | |
| 26 | + ('en', 2, 'anglais'), | |
| 27 | + ('de', 3, 'allemand'), | |
| 28 | + ('pt-br', 4, 'portugais du Brésil'); | |
| 29 | + | |
| 30 | +ALTER TABLE users DROP CONSTRAINT IF EXISTS users_lang_check; | |
| 31 | +ALTER TABLE users ADD CONSTRAINT users_lang_fkey | |
| 32 | + FOREIGN KEY (lang) REFERENCES exego_enums.enum_lang(code); | |
| 33 | + | |
| 34 | +-- users.theme ---------------------------------------------------------------- | |
| 35 | +CREATE TABLE exego_enums.enum_theme ( | |
| 36 | + code TEXT PRIMARY KEY, | |
| 37 | + sort_order SMALLINT NOT NULL, | |
| 38 | + description TEXT NOT NULL | |
| 39 | +); | |
| 40 | +INSERT INTO exego_enums.enum_theme (code, sort_order, description) VALUES | |
| 41 | + ('organic', 1, 'crème et terracotta (défaut)'), | |
| 42 | + ('hc', 2, 'contraste élevé noir et blanc'); | |
| 43 | + | |
| 44 | +ALTER TABLE users DROP CONSTRAINT IF EXISTS users_theme_check; | |
| 45 | +ALTER TABLE users ADD CONSTRAINT users_theme_fkey | |
| 46 | + FOREIGN KEY (theme) REFERENCES exego_enums.enum_theme(code); | |
| 47 | + | |
| 48 | +-- users.stats_layout --------------------------------------------------------- | |
| 49 | +CREATE TABLE exego_enums.enum_stats_layout ( | |
| 50 | + code TEXT PRIMARY KEY, | |
| 51 | + sort_order SMALLINT NOT NULL, | |
| 52 | + description TEXT NOT NULL | |
| 53 | +); | |
| 54 | +INSERT INTO exego_enums.enum_stats_layout (code, sort_order, description) VALUES | |
| 55 | + ('columns', 1, 'colonnes empilées (défaut)'), | |
| 56 | + ('rows', 2, 'barres horizontales'); | |
| 57 | + | |
| 58 | +ALTER TABLE users DROP CONSTRAINT IF EXISTS users_stats_layout_check; | |
| 59 | +ALTER TABLE users ADD CONSTRAINT users_stats_layout_fkey | |
| 60 | + FOREIGN KEY (stats_layout) REFERENCES exego_enums.enum_stats_layout(code); | |
| 61 | + | |
| 62 | +-- todos.state_id ------------------------------------------------------------- | |
| 63 | +-- `todo_states` appliquait déjà le principe, mais depuis le schéma public. | |
| 64 | +-- Le déplacement emporte ses clés étrangères (todos, todo_state_changes) : | |
| 65 | +-- rien à recréer. La clé reste le SMALLINT, que le Rust connaît par les | |
| 66 | +-- constantes STATE_* de src/db/todos.rs. | |
| 67 | +ALTER TABLE todo_states SET SCHEMA exego_enums; | |
| 68 | +ALTER TABLE exego_enums.todo_states RENAME TO enum_todo_state; | |
| 69 | +ALTER TABLE exego_enums.enum_todo_state RENAME COLUMN state_order TO sort_order; | |
| 70 | +ALTER TABLE exego_enums.enum_todo_state RENAME COLUMN state_name TO code; | |
| 71 | +-- Les noms de contraintes sont cherchés, pas écrits en dur (leçon de la 0012). | |
| 72 | +DO $$ | |
| 73 | +DECLARE c text; | |
| 74 | +BEGIN | |
| 75 | + SELECT conname INTO c FROM pg_constraint | |
| 76 | + WHERE conrelid = 'exego_enums.enum_todo_state'::regclass AND contype = 'p'; | |
| 77 | + EXECUTE format('ALTER TABLE exego_enums.enum_todo_state RENAME CONSTRAINT %I TO enum_todo_state_pkey', c); | |
| 78 | + | |
| 79 | + SELECT conname INTO c FROM pg_constraint | |
| 80 | + WHERE conrelid = 'exego_enums.enum_todo_state'::regclass AND contype = 'u'; | |
| 81 | + IF c IS NOT NULL THEN | |
| 82 | + EXECUTE format('ALTER TABLE exego_enums.enum_todo_state RENAME CONSTRAINT %I TO enum_todo_state_code_key', c); | |
| 83 | + END IF; | |
| 84 | +END $$; | |
| new file mode 100644 | |||
| @@ -0,0 +1,84 @@ | |||
| 1 | +-- Principe : une valeur d'énumération n'est pas un CHECK, c'est une ligne. | ||
| 2 | +-- Chaque domaine fermé devient une table `enum_xxx` référencée par une clé | ||
| 3 | +-- étrangère — élargir une énumération est alors un INSERT (donc réversible, | ||
| 4 | +-- inspectable, joignable) au lieu d'un DDL qui réécrit une contrainte. | ||
| 5 | +-- | ||
| 6 | +-- Ces tables vivent dans le schéma `exego_enums` pour ne pas encombrer le | ||
| 7 | +-- scope applicatif : aucune requête de l'app ne les nomme, seules les clés | ||
| 8 | +-- étrangères y renvoient (l'intégrité référentielle ignore le search_path). | ||
| 9 | +CREATE SCHEMA IF NOT EXISTS exego_enums; | ||
| 10 | + | ||
| 11 | +COMMENT ON SCHEMA exego_enums IS | ||
| 12 | + 'Tables de référence des énumérations (une ligne par valeur permise).'; | ||
| 13 | + | ||
| 14 | +-- Colonnes communes : `code` est la valeur stockée dans les tables métier, | ||
| 15 | +-- `sort_order` l'ordre d'affichage, `description` un libellé de lecture | ||
| 16 | +-- humaine EN BASE — jamais du texte d'interface (celui-ci reste dans i18n.rs). | ||
| 17 | + | ||
| 18 | +-- users.lang ----------------------------------------------------------------- | ||
| 19 | +CREATE TABLE exego_enums.enum_lang ( | ||
| 20 | + code TEXT PRIMARY KEY, | ||
| 21 | + sort_order SMALLINT NOT NULL, | ||
| 22 | + description TEXT NOT NULL | ||
| 23 | +); | ||
| 24 | +INSERT INTO exego_enums.enum_lang (code, sort_order, description) VALUES | ||
| 25 | + ('fr', 1, 'français'), | ||
| 26 | + ('en', 2, 'anglais'), | ||
| 27 | + ('de', 3, 'allemand'), | ||
| 28 | + ('pt-br', 4, 'portugais du Brésil'); | ||
| 29 | + | ||
| 30 | +ALTER TABLE users DROP CONSTRAINT IF EXISTS users_lang_check; | ||
| 31 | +ALTER TABLE users ADD CONSTRAINT users_lang_fkey | ||
| 32 | + FOREIGN KEY (lang) REFERENCES exego_enums.enum_lang(code); | ||
| 33 | + | ||
| 34 | +-- users.theme ---------------------------------------------------------------- | ||
| 35 | +CREATE TABLE exego_enums.enum_theme ( | ||
| 36 | + code TEXT PRIMARY KEY, | ||
| 37 | + sort_order SMALLINT NOT NULL, | ||
| 38 | + description TEXT NOT NULL | ||
| 39 | +); | ||
| 40 | +INSERT INTO exego_enums.enum_theme (code, sort_order, description) VALUES | ||
| 41 | + ('organic', 1, 'crème et terracotta (défaut)'), | ||
| 42 | + ('hc', 2, 'contraste élevé noir et blanc'); | ||
| 43 | + | ||
| 44 | +ALTER TABLE users DROP CONSTRAINT IF EXISTS users_theme_check; | ||
| 45 | +ALTER TABLE users ADD CONSTRAINT users_theme_fkey | ||
| 46 | + FOREIGN KEY (theme) REFERENCES exego_enums.enum_theme(code); | ||
| 47 | + | ||
| 48 | +-- users.stats_layout --------------------------------------------------------- | ||
| 49 | +CREATE TABLE exego_enums.enum_stats_layout ( | ||
| 50 | + code TEXT PRIMARY KEY, | ||
| 51 | + sort_order SMALLINT NOT NULL, | ||
| 52 | + description TEXT NOT NULL | ||
| 53 | +); | ||
| 54 | +INSERT INTO exego_enums.enum_stats_layout (code, sort_order, description) VALUES | ||
| 55 | + ('columns', 1, 'colonnes empilées (défaut)'), | ||
| 56 | + ('rows', 2, 'barres horizontales'); | ||
| 57 | + | ||
| 58 | +ALTER TABLE users DROP CONSTRAINT IF EXISTS users_stats_layout_check; | ||
| 59 | +ALTER TABLE users ADD CONSTRAINT users_stats_layout_fkey | ||
| 60 | + FOREIGN KEY (stats_layout) REFERENCES exego_enums.enum_stats_layout(code); | ||
| 61 | + | ||
| 62 | +-- todos.state_id ------------------------------------------------------------- | ||
| 63 | +-- `todo_states` appliquait déjà le principe, mais depuis le schéma public. | ||
| 64 | +-- Le déplacement emporte ses clés étrangères (todos, todo_state_changes) : | ||
| 65 | +-- rien à recréer. La clé reste le SMALLINT, que le Rust connaît par les | ||
| 66 | +-- constantes STATE_* de src/db/todos.rs. | ||
| 67 | +ALTER TABLE todo_states SET SCHEMA exego_enums; | ||
| 68 | +ALTER TABLE exego_enums.todo_states RENAME TO enum_todo_state; | ||
| 69 | +ALTER TABLE exego_enums.enum_todo_state RENAME COLUMN state_order TO sort_order; | ||
| 70 | +ALTER TABLE exego_enums.enum_todo_state RENAME COLUMN state_name TO code; | ||
| 71 | +-- Les noms de contraintes sont cherchés, pas écrits en dur (leçon de la 0012). | ||
| 72 | +DO $$ | ||
| 73 | +DECLARE c text; | ||
| 74 | +BEGIN | ||
| 75 | + SELECT conname INTO c FROM pg_constraint | ||
| 76 | + WHERE conrelid = 'exego_enums.enum_todo_state'::regclass AND contype = 'p'; | ||
| 77 | + EXECUTE format('ALTER TABLE exego_enums.enum_todo_state RENAME CONSTRAINT %I TO enum_todo_state_pkey', c); | ||
| 78 | + | ||
| 79 | + SELECT conname INTO c FROM pg_constraint | ||
| 80 | + WHERE conrelid = 'exego_enums.enum_todo_state'::regclass AND contype = 'u'; | ||
| 81 | + IF c IS NOT NULL THEN | ||
| 82 | + EXECUTE format('ALTER TABLE exego_enums.enum_todo_state RENAME CONSTRAINT %I TO enum_todo_state_code_key', c); | ||
| 83 | + END IF; | ||
| 84 | +END $$; | ||
modified
src/db/todos.rs +2 -1 | @@ -2,7 +2,8 @@ use chrono::{DateTime, NaiveDate, Utc}; | ||
| 2 | 2 | use sqlx::PgPool; |
| 3 | 3 | use uuid::Uuid; |
| 4 | 4 | |
| 5 | -/// États — mêmes ids que la table enum `todo_states` (seedée en migration). | |
| 5 | +/// États — mêmes ids que la table enum `exego_enums.enum_todo_state` | |
| 6 | +/// (seedée en migration). | |
| 6 | 7 | pub const STATE_IN_PROGRESS: i16 = 1; |
| 7 | 8 | pub const STATE_TODO: i16 = 2; |
| 8 | 9 | pub const STATE_DONE: i16 = 3; |
| @@ -2,7 +2,8 @@ use chrono::{DateTime, NaiveDate, Utc}; | |||
| 2 | use sqlx::PgPool; | 2 | use sqlx::PgPool; |
| 3 | use uuid::Uuid; | 3 | use uuid::Uuid; |
| 4 | 4 | ||
| 5 | -/// États — mêmes ids que la table enum `todo_states` (seedée en migration). | 5 | +/// États — mêmes ids que la table enum `exego_enums.enum_todo_state` |
| 6 | +/// (seedée en migration). | ||
| 6 | pub const STATE_IN_PROGRESS: i16 = 1; | 7 | pub const STATE_IN_PROGRESS: i16 = 1; |
| 7 | pub const STATE_TODO: i16 = 2; | 8 | pub const STATE_TODO: i16 = 2; |
| 8 | pub const STATE_DONE: i16 = 3; | 9 | pub const STATE_DONE: i16 = 3; |