Skip to content

feat(sql): standard DuckDB macros per il layer CLEAN#416

Merged
Gabrymi93 merged 6 commits into
mainfrom
feat/standard-macros-sql
Jul 21, 2026
Merged

feat(sql): standard DuckDB macros per il layer CLEAN#416
Gabrymi93 merged 6 commits into
mainfrom
feat/standard-macros-sql

Conversation

@Gabrymi93

Copy link
Copy Markdown
Member

Sintesi

7 macro DuckDB caricate automaticamente in ogni esecuzione clean.sql, per eliminare il boilerplate SQL che oggi è riscritto da zero in ogni dataset di dataset-incubator (TRY_CAST, REPLACE numeri italiani, CASE decode flag, TRIM).

Contesto collegato

Segue la discussione architetturale: toolkit può snellire DI? I pattern comuni nei clean.sql sono stati analizzati e le macro coprono l'80% del boilerplate.

Cosa cambia

  • Bug fix
  • Nuova funzionalità del motore
  • Nuovo plugin sorgente
  • Modifica contratto pubblico (dataset.yml, path output, schema parquet)
  • Refactor / performance
  • Documentazione
  • Dipendenze o CI

Impatto su contratti pubblici

Nessuno. Le macro sono additive — un clean.sql che non le usa funziona identico.

Cosa contiene

toolkit/sql/macros.sql — 7 macro DuckDB

Macro Trasformazione Frequenza in DI
normalize_italian_number(val) 1.234,561234.56 40%
normalize_italian_integer(val) 1.2341234 30%
decode_flag(val, 'X') 'X'TRUE 30%
normalize_string(val) TRIM + ''NULL 100%
cast_int(val) TRY_CAST(val AS INTEGER) 100%
cast_double(val) TRY_CAST(val AS DOUBLE) 100%
remove_dot_thousands(val) 1.2341234.0 20%

toolkit/clean/sql_execute.py — caricamento automatico

_load_standard_macros(con, logger) chiamata all'inizio di ogni _run_sql(), prima della vista raw_input. I file macro sono dentro il pacchetto toolkit (toolkit/sql/macros.sql), raggiungibili da qualsiasi dataset.

toolkit/scaffold/clean.py — documentazione nello scaffold

Il generated SQL ora include un commento con l'elenco delle macro disponibili.

Test: tests/test_macros_sql.py — 35 test pure_unit

  • 7 test normalize_italian_number (base, migliaia, decimali, null, non-numerico, vuoto)
  • 4 test normalize_italian_integer
  • 6 test decode_flag (match, mismatch, case-sensitive, trim, null)
  • 4 test normalize_string (trim, vuoto, whitespace, null)
  • 4 test cast_int (stringa, intero, non-numerico, null)
  • 4 test cast_double (stringa, decimale, non-numerico, null)
  • 4 test remove_dot_thousands
  • 1 test integrazione (simula un clean.sql reale con 3 righe e tutte le macro)

Esempio di impatto su un clean.sql reale

Prima (ade-cinque-per-mille):

TRY_CAST(REPLACE(REPLACE("Importo delle scelte espresse"::VARCHAR, '.', ''), ',', '.') AS DOUBLE) AS importo_scelte_espresse,
CASE WHEN TRIM("ETS") = 'X' THEN TRUE ELSE FALSE END AS flag_ets_onlus,
TRIM("Codice fiscale") AS codice_fiscale,

Dopo:

normalize_italian_number("Importo delle scelte espresse") AS importo_scelte_espresse,
decode_flag("ETS", 'X') AS flag_ets_onlus,
normalize_string("Codice fiscale") AS codice_fiscale,

Verifica

pytest tests/test_macros_sql.py -v
pytest -m pure_unit -q
  • pytest tests/test_macros_sql.py passa (35 test)
  • pytest -m pure_unit passa (457 test)
  • ruff check . passa
  • Tutti i test nuovi sono marker pure_unit
  • 2 test esistenti aggiornati per escludere i commenti macro dalle assertions (test_scaffold_clean, test_scout_infer)

Checklist PR

  • Perimetro stretto: un file macro SQL + caricamento + test
  • Nessuna modifica a contratti pubblici esistenti
  • Test presenti con marker appropriato

Note per chi revisiona

  • Le macro sono CREATE OR REPLACE — sicure da eseguire più volte
  • La funzione _load_standard_macros() ignora silenziosamente se macros.sql non esiste (sicura in sviluppo)
  • remove_dot_thousands è intenzionalmente solo per interi — numeri con virgola decimale vanno con normalize_italian_number

Aggiunge 7 macro SQL automaticamente caricate in ogni esecuzione clean:
- normalize_italian_number(val) — 1.234,56 → 1234.56
- normalize_italian_integer(val) — 1.234 → 1234
- decode_flag(val, yes_value) — 'X' → TRUE
- normalize_string(val) — TRIM + '' → NULL
- cast_int(val) — TRY_CAST(val AS INTEGER)
- cast_double(val) — TRY_CAST(val AS DOUBLE)
- remove_dot_thousands(val) — 1.234 → 1234.0

Meccanismo:
- toolkit/sql/macros.sql: definizioni CREATE OR REPLACE MACRO
- clean/sql_execute.py:_load_standard_macros() — caricate all'inizio
  di ogni connessione DuckDB nel layer CLEAN
- scaffold/clean.py: commento nel generated SQL che elenca le macro

Test: 35 test DuckDB per tutte le macro + test di integrazione
…housands

- pyproject.toml: aggiunto [tool.setuptools.package-data] con
  toolkit = ['sql/*.sql'] — le macro ora sono incluse nel wheel
- sql_execute.py: _load_standard_macros ora solleva FileNotFoundError
  se macros.sql non esiste (nessun skip silenzioso)
- macros.sql: normalize_italian_integer documenta che DuckDB
  CAST(DOUBLE AS INTEGER) arrotonda (non tronca)
- macros.sql: remove_dot_thousands documenta precondizione
  'solo interi' con warning esplicito
- Test: 3 test nuovi (package data source + importlib +
  normalize_italian_integer .90 rounding)
- Test: remove_dot_thousands now documents the '1234.56 → 123456'
  mangling as expected behavior, not a bug
- 460 pure_unit test pass
Convertiti 5 dataset canonici a usare le macro standard del toolkit:
- smoke/bdap_ckan_csv: TRY_CAST+TRIM → cast_int/cast_double/normalize_string
- smoke/bdap_http_csv: 21 righe di TRY_CAST(TRIM(CAST(... AS VARCHAR)) AS DOUBLE)
  → cast_double (da 33 a 25 righe)
- smoke/finanze_http_zip_2023: TRY_CAST → cast_int/cast_bigint + normalize_string
- smoke/local_file_csv: CAST → cast_int/cast_double
- project-example: normalize_string + normalize_italian_number al posto
  di TRIM/REPLACE/REPLACE/NULLIF manuali

Questi dataset sono eseguiti negli smoke test CI — provano che le
macro funzionano end-to-end in pipeline reali.
- docs/standard-macros.md: documentazione completa di tutte le 8 macro
  con esempi prima/dopo, tabella comparativa con normalize.py, e
  comandi per test diretto via DuckDB CLI
- README.md: nuova sezione 'Scrivere clean.sql con le macro standard'
  sotto la CLI, con esempio concreto prima/dopo
- README.md: docs/standard-macros.md aggiunto alla tabella documenti
Il dry-run CLI validava il clean.sql senza caricare le macro DuckDB
standard, causando 'Catalog Error: Scalar Function cast_int does not exist'
sui dataset che usano le macro (project-example, smoke test).
Aggiunta chiamata a _load_standard_macros() in validate_sql_dry_run().
@Gabrymi93
Gabrymi93 merged commit c6fc479 into main Jul 21, 2026
3 checks passed
@Gabrymi93
Gabrymi93 deleted the feat/standard-macros-sql branch July 21, 2026 10:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant