Tipizzazione dati (TEXT-everywhere)
Perché tutte le colonne dei progetti sono TEXT, e quando potrebbe cambiare.
Versione MDX dell'ADR
docs/adr/0002-text-everywhere-data-plane.md. Per il dettaglio della decisione, link e file rilevanti, vedi l'ADR.
I dati prodotto vengono caricati in tabelle Postgres dinamiche per progetto: company_<companyId>.project_<projectId>. Le colonne di queste tabelle corrispondono ai DatabaseField definiti dall'utente in UI (title, price, stock, ean, …). L'utente può aggiungere/rimuovere field a runtime.
Scelta di design: ogni nuova colonna viene creata come TEXT, senza eccezioni. Il rule engine fa cast on-demand via operatori (numeric:, date:).
Perché
- Schema dinamico: l'utente cambia field via UI.
ALTER COLUMN TYPEsu una tabella da milioni di righe è downtime non accettabile. - Source eterogenee: feed marketplace contengono
"€ 1.234,56","N/D", valori che l'engine sa normalizzare ma cheCASTrifiuterebbe a MERGE step. - Engine robusto: operatori
numeric:,int:,date:con fallback espliciti. Boundary test verdi. - Zero migration runtime: add/remove field è DDL O(1).
Trade-off
- Niente SQL aggregate diretti su prezzo/stock — uso engine per qualunque operazione tipizzata.
- Niente constraint DB su range numerici — validazione in engine.
- Sort lessicografico in SQL diretta su colonne numeriche (cura: usa engine).
Triggers di revisione
Riapri questa decisione se:
- analytics dashboard richiede
SUM(price::numeric)come hot path, - più di 3 ticket cliente in un quarter sul tema "ordinamento numerico sbagliato",
- profiling del rule engine mostra cast come bottleneck.
Strategia se serve typing
Modello opt-in (non implementato): DatabaseField.type enum (TEXT | NUMERIC | DATE), shadow column ${slug}_numeric popolata in MERGE, engine usa shadow se disponibile.
Backward compatible perché TEXT resta default e source originale non cambia.
File rilevanti
packages/shared/src/services/schema-service.ts→addColumn()(hardcoded TEXT)packages/worker/src/engine/rule-engine.ts→ operatori castpackages/worker/src/__tests__/rules-{to-export-boundary,per-export-scope}.int.test.ts→ contract test