Skip to content

Land the sqlserver connector: session and full-refresh loads #136

Description

@victorchutw

Implements ADR-0064 (full refresh), the connection facts of ADR-0060, and ADR-0068 (parallelism declaration); first live consumer of the #134 mapping and #135 Accept Family modules; test posture per ADR-0066. Part of the SQL Server destination cycle (map #106, sliced from #113).

Blocked by #131, #132, #135

Context

The first live mode. The connector bridges the sync Destination/DestinationWriter seam (src/connector.rs:290, :356) to async tiberius with a connector-contained current-thread tokio runtime — one runtime and one client per load, nothing async escaping the connector (the #107 research posture). Full refresh is the only mode that bootstraps a table; production tables are owned by their environment, so the write never replaces the table object.

The contract

  • Session: SQL auth; encryption: required → tiberius default encryption, optionalEncryptionLevel::Off (login encrypted where possible, data traffic per server policy — the production SSMS Encrypt=Optional parity posture); trust_server_certificate honored; a hardcoded 15 s connect timeout so a hang becomes a clean failure; application_name pinned to data-spark. The password is read from the password_env variable at connect time (its presence was validated offline in the Definition phase).
  • begin(full_refresh): table absent → CREATE TABLE in the created shape (Generate SQL Server DDL and bulk rows from the dataset schema #134); table present → introspect and validate against the Accept Family (Validate existing SQL Server tables against the Accept Family #135, full-refresh rules; violations fail incompatible_destination_table before any write). Then BEGIN TRAN and DELETE FROM [schema].[table] — object-preserving, never drop/rename (a staged sp_rename swap is rejected by ADR-0064: it strips permissions, indexes, triggers, FKs).
  • write_chunk: one bulk_insert per chunk inside the transaction (bulk load inside a user-defined transaction is documented TDS behavior and rolls back with it), rows in table column order via Generate SQL Server DDL and bulk rows from the dataset schema #134.
  • commit: terminal COMMIT. Write facts: atomicity: "atomic", strategy: "transactional_delete_insert" (new string, additive under the report contract's version-1 clause). A zero-survivor full refresh commits an empty table — ADR-0056's mirror semantics.
  • Failure: explicit best-effort ROLLBACK; failures surface as destination_write_failed (or incompatible_destination_table from validation). Every raising site classifies Transience::Terminal in this slice — the transience slice flips the decided sites later (ADR-0067's terminal-first-then-flip path).
  • Modes: supported_load_modes = full refresh only; append and merge keep declining via unsupported_load_mode_for_destination until their slices widen the list.
  • Parallelism: parallelism_limit declared 1 for all three modes — declared, not defaulted (ADR-0068); the report keeps stating the effective value beside the connector limit.
  • Tests (ADR-0066): pure logic ungated; live tests carry #[ignore = "needs SQL Server"], connect with fixed defaults matching the CI container of Run CI and release tests against a live SQL Server with --include-ignored #131, overridable via the DATA_SPARK_TEST_MSSQL_* env family (host, port, user, password), and never start containers themselves. CI proves them via --include-ignored.

Acceptance criteria

  1. Gated live tests:
    • Bootstrap: full refresh onto an absent table creates the created shape (introspection read-back asserts exact types — BIGINT, FLOAT, BIT, NVARCHAR(MAX), DATETIME2(6), DECIMAL(p,s) — and nullability) and lands the rows; values round-trip exactly (instants UTC-normalized, microsecond precision, i64::MAX, negative decimals, empty string ≠ NULL).
    • Object preservation: an index created out-of-band on the table survives a second full refresh; rows are replaced.
    • Zero survivors: an all-rejected source under a permissive threshold commits an empty table with atomic/transactional_delete_insert facts.
    • Incompatibility: an existing table with a VARCHAR column fails incompatible_destination_table at begin; the target's rows are untouched.
    • Representability sliver: a >32,767-character utf8 value fails the load as a write failure (never a Rejected Record), the transaction rolls back, and the destination is unchanged.
  2. Ungated units: config→client posture mapping (encryption/trust matrix), error-shape mapping.
  3. The load report carries the destination_summary echo, the atomic facts, and connector_parallelism_limit: 1; exit codes follow the existing contract.
  4. On a serverless machine cargo test --locked passes with the live tests visibly ignored; CI (with the Run CI and release tests against a live SQL Server with --include-ignored #131 container) runs them for real.
  5. CHANGELOG.md Unreleased entry: the sqlserver destination lands with full-refresh support, naming incompatible_destination_table in prose.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentFully specified and ready for an AFK agent

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions