Connettore: SQL Server
Source e Sink per Microsoft SQL Server, basati sul driver mssql (tedious, puro JS — vedi ADR-0004). Codice: src/connectors/sqlserver/.
Configurazione
loadMssqlConfig(prefix?, env?) costruisce la config mssql dalle variabili <PREFIX>HOST|PORT|USER|PASSWORD|DATABASE (default prefisso MSSQL_) e opzionali <PREFIX>ENCRYPT / <PREFIX>TRUSTCERT. Default dev (cert self-signed): encrypt=true, trustServerCertificate=true. In produzione usare MSSQL_TRUSTCERT=false con un certificato valido. Credenziali solo in .env (vedi .env.example).
import mssql from "mssql";
import {
loadMssqlConfig,
mssqlSource,
mssqlSink,
} from "../connectors/sqlserver/index.js";
const pool = await new mssql.ConnectionPool(
loadMssqlConfig("MSSQL_"),
).connect();
try {
const source = mssqlSource<Order>({
pool,
query:
"SELECT id, amount, updated_at FROM dbo.orders WHERE updated_at > @since ORDER BY updated_at",
inputs: { since: ctx.params.since },
highWaterMark: 1000,
});
const sink = mssqlSink<FactOrder>({
pool,
table: "dbo.fact_orders",
columns: ["orderId", "amount", "date"],
conflictTarget: ["orderId"],
});
} finally {
await pool.close(); // il chiamante possiede il ciclo di vita del pool
}Source — mssqlSource
- Streaming (
request.stream) con backpressure viapause()/resume(): non carica l'intero result-set. - Parametri nominali via
inputs(@nome), per estrazione incrementale (watermark dactx.params). - Rispetta
ctx.signal(annullamento). Errori di estrazione →ExtractError.
Sink — mssqlSink
- Idempotente:
MERGE ... ON (conflictTarget)in transazione.conflictTargetvuoto → INSERT non idempotente (sconsigliato).- tutte le colonne nella chiave → MERGE con solo
WHEN NOT MATCHED(insert-if-absent).
- Batch (
batchSize, default 200): SQL Server limita a 2100 i parametri per richiesta, quindibatchSize × numColonnedeve restare sotto tale soglia. - Errori di caricamento →
LoadErrorconROLLBACK.
Lookup — mssqlLookup
Alimenta il nodo lookup del grafo (ADR-0041): risolve un batch di chiavi con un solo round-trip, in sola lettura.
mssqlLookup({
pool,
instanceId: "dwh",
query: "SELECT chiave, ordine FROM v WHERE chiave IN (@chiavi)",
keyColumn: "chiave",
});La parte indipendente dal DBMS (controllo di sola lettura, segnaposto, normalizzazione, raggruppamento) è condivisa con postgresLookup e vive in src/connectors/shared/sql-lookup.ts: la stessa query funziona sui due database.
- Il segnaposto
@chiavi(KEYS_PLACEHOLDER) viene espanso in un parametro per chiave (@k0, @k1, …): si generano solo i nomi, i valori restano bindati. Con zero chiavi diventa un insieme vuoto valido, non SQL invalido. keyColumndice quale colonna del result set riassocia le righe alle chiavi. La chiave è normalizzata (trim + maiuscolo) sui due lati, così unUPPER()dimenticato nella vista non produce zero match in silenzio. Al DB partono le chiavi normalizzate senza duplicati; le righe tornano indicizzate per la chiave così come l'ha passata il nodo (" a"riceve le righe diA). Una colonna chiave assente, o non scalare, è unLookupError— non zero risultati.- Sola lettura imposta a
build():assertReadOnlyQueryrifiuta tutto ciò che non è una singolaSELECT/WITH(nienteINSERT/UPDATE/DELETE/MERGE/EXEC/INTO, niente;multipli). Commenti e stringhe letterali sono rimossi prima dell'analisi, cosìWHERE nota = 'da aggiornare (update)'passa e una parola vietata dentro un commento no.
⚠️ È un controllo sintattico, non un modello di sicurezza: la garanzia vera che la pipeline non scriva resta l'utenza di sola lettura sul database. Serve a trasformare un errore di configurazione in un fallimento immediato e leggibile.
Sicurezza SQL
- I valori sono sempre parametri (
@p0,@p1, ...), mai concatenati. - Identificatori (tabella/colonne) dal codice, validati e quotati con
[...](quoteIdentifier); stringa non conforme →ConfigError.
Ambiente di test
SQL Server in Docker via docker-compose.yml (servizio mssql-test). Vedi il runbook local-test-databases. Su Apple Silicon gira in emulazione (immagine solo linux/amd64).
Test
- Unit (CI):
src/connectors/sqlserver/{query,config,lookup}.test.ts— MERGE building, parsing config e resolver del lookup con pool finto. La parte comune del lookup è testata insrc/connectors/shared/sql-lookup.test.ts. - Integrazione:
tests/integration/mssql.integration.test.ts— MERGE idempotente e streaming contro il container reale.
docker compose up -d # avvia SQL Server di test
pnpm run test:integration # esegue i test di integrazione (auto-skip senza env)
