Skip to content

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).

ts
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 via pause()/resume(): non carica l'intero result-set.
  • Parametri nominali via inputs (@nome), per estrazione incrementale (watermark da ctx.params).
  • Rispetta ctx.signal (annullamento). Errori di estrazione → ExtractError.

Sink — mssqlSink

  • Idempotente: MERGE ... ON (conflictTarget) in transazione.
    • conflictTarget vuoto → 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, quindi batchSize × numColonne deve restare sotto tale soglia.
  • Errori di caricamento → LoadError con ROLLBACK.

Lookup — mssqlLookup

Alimenta il nodo lookup del grafo (ADR-0041): risolve un batch di chiavi con un solo round-trip, in sola lettura.

ts
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.
  • keyColumn dice quale colonna del result set riassocia le righe alle chiavi. La chiave è normalizzata (trim + maiuscolo) sui due lati, così un UPPER() 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 di A). Una colonna chiave assente, o non scalare, è un LookupError — non zero risultati.
  • Sola lettura imposta a build(): assertReadOnlyQuery rifiuta tutto ciò che non è una singola SELECT/WITH (niente INSERT/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 in src/connectors/shared/sql-lookup.test.ts.
  • Integrazione: tests/integration/mssql.integration.test.ts — MERGE idempotente e streaming contro il container reale.
bash
docker compose up -d           # avvia SQL Server di test
pnpm run test:integration      # esegue i test di integrazione (auto-skip senza env)

Noeva è un marchio registrato di 4D S.R.L.