Bounded native ClickHouse to MSSQL transport¶
This opt-in route loads one ClickHouse query through bounded native files,
imports independent staging tables with BCP or Microsoft.Data.SqlClient, and
publishes once through the existing MSSQL transaction finalizer. The standard
dpone run composition is available for inert quality policy; deployments with
authored quality gates must inject their quality executor. It supports full_refresh and an
explicit UTC partition_replace window. Existing character-spool routes retain
their defaults. Retained evidence for dpone 0.80.0 at commit
6ae541d38ac223327d7edb23510859df91173bda establishes scoped local Docker
correctness and controlled recovery: six non-binary profiles, 64 rows per
fidelity cell, full refresh and explicit UTC partition replacement, with
encoding/import policies 2/1 and 1/2. Production workload performance,
independent source DDL and target-writer governance, and hard-failure recovery
remain UNVERIFIED. These results do not certify a new deployment.
Choose the verification backend¶
| Verification | Import | Effective path | Identity and journal | Compatibility |
|---|---|---|---|---|
| omitted | BCP | BCP plus Python business-row readback | v1 | Existing behavior is unchanged |
python_readback |
BCP | BCP plus Python business-row readback | v1 | Explicit form of existing behavior |
target_local |
BCP | Supervised BCP plus aggregate-only SQL Server verification | v2 | Opt-in; no fallback to v1 inside the invocation |
target_local |
mssql_sqlclient |
SqlBulkCopy plus session-bound aggregate verification |
v2 | Optional Linux x64 companion; no fallback to BCP |
The optimized selector belongs under native_transfer.execution:
native_transfer:
execution:
verification_backend: target_local
import_backend: mssql_sqlclient
layout_version: 2
Start with
examples/native/clickhouse-to-mssql-target-local.yaml
and inspect the resolved contract without opening connections:
The mssql_native plan reports verification_backend=target_local,
verification_identity_version=2, the selected writer capability and layout.
Its required
dependencies include stable_target_custody, supervised_bcp_writer, and
target_local_digest. Direct dpone run hydrates the declared connections,
uses the configured durable runtime storage roots, and composes the native state
machine. A deployment-owned runtime remains required when authored quality
policies are present.
flowchart LR
CH[Bounded ClickHouse query] --> FILES[Sealed native files]
FILES --> SELECT{Explicit writer}
SELECT -->|default| BCP[Supervised BCP]
SELECT -->|mssql_sqlclient| TDS[SqlBulkCopy companion]
TDS --> LOCK[Session grant lock]
BCP --> BARRIER[Held exact-stage barrier]
LOCK --> BARRIER
BARRIER --> RAW[Aggregate raw digest]
RAW --> PREP[Prepared aggregate verification]
PREP --> PUB[Atomic publication]
BCP -->|ambiguous outcome| UNKNOWN[UNKNOWN]
TDS -->|lost acknowledgement| UNKNOWN
UNKNOWN --> CUSTODY[Retained target custody]
Prepare and configure¶
The platform owner supplies dedicated connections, durable fenced storage,
source DDL exclusion, target writer exclusion and the transaction-state catalog.
The standard composition supplies transaction admission, source/schema binding,
target-local custody, operational evidence and stateless checkpoint ordering.
Set runtime.storage.work_dir and checkpoint_dir to storage that survives the
worker process and is available to the recovery operator. A local ephemeral path
cannot provide restart recovery.
The YAML example is for plan inspection; execution requires the platform capabilities described below. Use the native example and inspect it with:
Supply an explicit execution interval.interval_end in UTC when constructing the
load configuration. The example replaces the complete preceding day, including
an empty source interval. Rows outside [start, end) and NULL window values remain
unchanged. The interval never comes from values found in staging. Full refresh
omits strategy.window and replaces the complete target under its existing
catalog-preservation policy. Do not combine a native window with legacy
partition options.
By default, only one local plain MergeTree table in an Atomic database is admitted. The
mandatory source schema_guard_factory must exclude ALTER, RENAME, EXCHANGE,
DROP/recreate and relevant access-policy changes from metadata admission until
source cleanup. It does not need to freeze ordinary DML. A no-op context manager
is suitable only for synthetic tests. The source records the nonzero table UUID
and one query ID. A query ID is not a reusable snapshot token.
Exact raw rows from ReplacingMergeTree¶
The opt-in source selector preserves the multiset returned by one ordinary
non-FINAL query. Use the
raw snapshot example:
source:
options:
native_transfer:
source_snapshot:
mode: exact_raw_rows
replica_scope: single_server
execution:
verification_backend: target_local
single_server requires ReplacingMergeTree; connected_replica requires
ReplicatedReplacingMergeTree and a directly connected, identified replica.
Both require an Atomic database. Shared engines, views, Distributed tables,
custom SQL and ambiguous endpoints fail admission. BCP remains the default;
the existing SqlClient selector and resource limits are independent. Set
execution.verification_backend: target_local for raw mode: its durable v2
identity is required for source-free recovery. Raw mode with Python readback
fails before I/O; legacy manifests retain their defaults.
Raw mode also inherits the target-local layout matrix.
For example, a ReplacingMergeTree is_deleted UInt8 column normally maps to
tinyint, which this verifier does not admit. Preserve its values through an
explicit lossless bigint target mapping and provision the matching target:
The schema planner validates that mapping; it does not bypass target admission. Other unsupported wire types require their own supported mapping or a separately qualified verifier extension. Raw mode cannot fall back to Python readback.
Use a direct native TLS connection with certificate and hostname verification;
set the connection's ca_cert when the server uses a private CA. The raw session
has one host and rejects automatic reconnect or replica rotation. Its catalog
permission must expose relation, column, policy, settings, mutation and part
metadata. Missing permissions or unsupported metadata fail before success.
The physical inventory is bounded to 8,192 active parts. A bounded window must
use toYYYYMM, toYYYYMMDD or toDate on its UTC time column as the partition
expression; unsupported partition expressions fail admission. Patch partitions
are included conservatively. Timezone-less timestamp window columns require
proven UTC server and session calendars; explicitly UTC timestamp columns are
independent of those defaults. Inherited limit, offset and extremes are
neutralized and sorting overflow throws. Unsupported additional table/result
filters are rejected, never cleared; normal row policies remain enforced.
Use the stable error codes and remediation in
raw snapshot troubleshooting.
Raw rows are the query-visible versions that still exist when the SELECT
acquires its snapshot. Background merges may already have removed older
versions. dpone preserves identical duplicates and treats version and deletion
marker columns as ordinary values. It does not run FINAL, deduplicate, or
reconstruct insertion history. Normal access policies, lightweight-delete masks
and supported patch parts still apply.
Start with dpone plan examples/native/clickhouse-replacing-to-mssql-native.yaml
--format json. Planning reports declared semantics and pending live admission;
it performs no credential lookup or network access. Supply the normal explicit
UTC execution interval when running the example. Runtime checks the exact
source relation, schema, TLS authority, settings, policies and replica before
the data query. Pipeline check --connections is configuration-only and is not
a source snapshot certificate.
At EOF, completion_metadata.source_snapshot_v1 binds the bounded source
profile and EOF descriptors to the existing verified stage receipts. Hidden
part provenance is removed before target encoding. Physical part checksums
describe storage; the typed multiset digest verifies delivered values.
endpoint_authority_agreement means the observed endpoints agree; it does not
prove the absence of an intermediate change or equality with another replica.
Before EOF, settle the existing writer and target custody before starting a new invocation. Unknown custody remains held. After verified EOF, recovery validates the selector, source marker, descriptor hashes, schema, window and row count before opening the target; it never reconnects to ClickHouse. Use the existing native recovery commands below. Removing the selector affects future runs only and does not authorize cleanup of an uncertain publication.
Certification of this new source mode is UNVERIFIED until its own exact-build synthetic route, TLS, replica and recovery matrix is retained. Existing plain MergeTree or writer-only receipts do not certify raw snapshot support. See the approved source contract for the required cells and limitations.
The finite source types include scalar integers through UInt64, float, Decimal
through precision 38, String, UUID, Date/Date32 and UTC DateTime/DateTime64 through
six fractional digits, with nullable variants. Temporal values travel as integer
microseconds to avoid driver floating-point conversion. Unsupported engines,
views, computed columns, custom SQL, deduplication, CDC and uncomposed hooks fail
admission. All ordinary table columns are extracted; authored column selection is rejected.
NaN, Infinity, numeric overflow and fractional truncation are rejected.
Text decoding is strict. The native encoder can preserve arbitrary bytes for an
already admitted binary wire contract, but the canonical ClickHouse planner maps
String to Unicode text. There is currently no authored binary-semantic mapping
from ClickHouse String to MSSQL varbinary; a physical type override is rejected
as a cross-family conversion. Binary encoder tests therefore do not certify that
end-to-end source route. Its live binary profile remains UNVERIFIED.
VARCHAR/CHAR are currently rejected by the importer, including when a collation
is configured.
Target-local P1 layout matrix¶
verification_backend: target_local deliberately admits a narrower layout
than the general native BCP transport. The generated
TargetLocalLayoutMatrixV1 currently permits at most 100 business columns and
only these SQL Server wire types; each may be nullable:
| SQL Server wire type | ClickHouse source family | Target-local P1 |
|---|---|---|
bigint |
signed integer mapped to Int64 |
admitted |
float(53) |
Float64 |
admitted |
nvarchar(max) |
ClickHouse String with Unicode text semantics |
admitted |
datetime2(6) |
UTC DateTime64(6) |
admitted |
Decimal, smaller integer widths, UUID, Date/Date32, binary, varchar/char,
and other otherwise valid native BCP layouts remain on python_readback until
their target-local SQL digest framing has differential live proof. Readiness
rejects an unsupported target-local layout before source extraction or writer
launch. The content-addressed matrix is produced by
TARGET_LOCAL_LAYOUT_MATRIX_V1; its exact-commit certification artifact is
layout-matrix-v1.json.
The same synthetic receipt pack records only allowlisted runtime/service
versions and content-addressed image identities in
environment-v1.json;
it contains no host, port, login, database, schema, table, or path.
Prepared verification additionally admits only the framework-owned suffix
types varchar(26), varchar(32), varchar(64), nvarchar(max), int, and
datetime2(7). Application columns cannot use that suffix allowance.
Bounded delivery reuses frame sizes, projects canonical metadata in the prepared INSERT and computes business/full digests in one iterator. All four raw checks, the independent prepared prepublication check and the finalizer target-clock UPDATE remain. Follow delivery acceleration for structural evidence, optional observations and measurement instructions. These changes establish no measured acceleration and do not enable native partition SWITCH. Existing callers need no manifest or recovery migration.
Compose the runtime¶
Build the v2 identity with the framework-owned P1 factory. The plan already
binds the target, source query, window, schema, and wire contract; the factory
binds the reviewed BCP proof capabilities and timeout policy. Pass the same
identity to NativeRuntimeBindings and compose_native_stage_context:
from dpone.contracts.mssql_native_verification_identity import (
build_bcp_target_local_verification_identity,
)
from dpone.runtime.mssql_native_runtime import NativeRuntimeBindings
from dpone.runtime.sinks.mssql_native_composition import compose_native_stage_context
verification_identity = build_bcp_target_local_verification_identity(
plan,
timeout_seconds=3600,
)
context = compose_native_stage_context(
# existing required capabilities omitted here
verification_identity=verification_identity,
target_local_timeout_seconds=3600,
)
bindings = NativeRuntimeBindings(
service=service,
stage_context=context,
admission=transaction_admission,
verification_identity=verification_identity,
)
Do not construct the identity hashes manually. A changed timeout or frozen plan produces a different invocation key and cannot resume unfinished work.
NativeMssqlRuntime accepts these required application capabilities:
| Capability | Responsibility |
|---|---|
store, target_id |
Durable WindowStore, stable physical target identity and fenced CAS |
bindings(config, owner, lease, cancelled) |
Target-only restoration of one invocation and NativeRuntimeBindings |
source(config, binding) |
Owned context manager producing the existing LoadPayload; no source access during recovery |
preflight(config) |
Local and target capacity, permissions, required service admission |
quality(config, handle, lease) |
Configured quality gates against prepared staging before publication |
evidence(config, result, context, lease) |
Durable idempotent evidence bound to the exact commit receipt |
advance_state(config, result, lease) |
Fenced idempotent checkpoint CAS after evidence persistence |
custody_factory(store, target_id) |
Optional override for stable target custody; the framework default preserves old constructor calls and blocks v1 while v2 custody is held |
v2_journal_admission(journal, identity) |
Optional stricter deployment admission; the framework default requires the journal's immutable identity to equal the composed v2 identity |
The binding contains a service, NativeStageContext and fresh
MssqlTransactionAdmission. Set context.cancelled to the supplied event. Restore
stable invocation, query, schema, wire and interval identities before opening the
source. The context journal factory creates
dpone.adapters.mssql_native_chunks_journal.NativeChunkJournal against the same
store and lease. Do not create a new generation merely because a response was
lost.
Construct MSSQLSink with
native_staged_load_service_factory=lambda sink: MssqlNativeStagedLoadService(sink,
MssqlNativeStagePreparer(sink, context_factory)). This constructor injection avoids
mutating an already constructed sink. Use compose_native_stage_context from
dpone.runtime.sinks.mssql_native_composition to assemble the independent importers,
writer scopes, journal, verification, cleanup and capacity checks. Pass the existing BcpOptions class from
dpone.runtime.connectors.mssql_bulk as bcp_options_factory at the application
composition root. Its target
session must use the configured staging/target database; differing staging and
target databases are not admitted by this helper. The context owns:
BoundedNativeChunkswith a context-manager importer factory. Every importer owns and closes a separate target connector, native wire columns,MssqlNativeEncoder.encode_row, andstore.assert_lease.native_stage_writer_scopeon that same connector, keyed by the stable attempt identity. Old writers must settle before retry or removal. The preparation scope covers verification through commit and owns an independent same-database session created bytarget_connector.open_session(). The context releases its lock and closes that session on every exit. Reusing the target connector or opening a different database fails before preparation begins.- Real receipt verification and cleanup callbacks using importer
inspectandsettle; cleanup checks the table's persisted owner binding. - Capacity checks before extraction, preparation and publication. Use
require_native_target_capacityto require catalog, volume and log-space observations; denied or missing observations are admission failures. - The existing MSSQL schema preplanner, transaction admission and extraction lifecycle. The completed payload persists schema, physical source UUID, lifecycle, mutation plan and operation identity for source-free recovery.
Pass the resulting runtime through
DefaultProcessRunner(native_runtime_factory=lambda process_config: runtime).
The factory must select the runtime for the actual route. The executable
composition-order tests demonstrate
callback ordering and recovery, and the
real-finalizer tests exercise
the concrete preparation scope with commit, lost acknowledgement and unknown
outcomes using synthetic SQL fixtures. When COMMIT acknowledgement is lost,
the finalizer can close its own connection and confirm the exact receipt while
the preparation lock remains held. A confirmed commit continues to evidence and
checkpoint completion; an unknown outcome retains its existing recovery path.
These fixtures are not deployment authorities.
Resource limits and observations¶
native_transfer.execution.native_chunks requires max_total_encoded_bytes and
stage_allocated_bytes_stop_threshold. Defaults are 65,536 rows, 16 MiB per chunk,
1 MiB per business row, two pending chunks and 1,024 staging tables. Parallelism
defaults to 1 and is 1–64. Optional encoding_parallelism and
import_parallelism under native_chunks independently fall back to
execution.chunking.parallelism. Authored overrides must be strict integers
1–64; explicit YAML null is invalid. Python keyword-only overrides default to
None, retaining all eight historical positional fields. Follow the
concurrency how-to and its
new example
for resolved planner output and upgrade guidance. The pending bound is independent of the cumulative encoded-byte limit.
One prepared table also counts toward the staging-table limit. Rows are limited
to 1–1,000,000; chunk bytes to 1–1 GiB; row bytes must not exceed chunk bytes;
pending chunks and parallelism are 1–64. Required cumulative limits and staging
table counts are positive integers. Booleans and unknown limit keys are rejected.
The shared retained-work capacity is C = max(E, I) + max_pending, where E/I
are the effective encoding/import limits. Import concurrency bounds file
import/verify tasks, not all target connections. The local payload bound is
(C + 1) * max_bytes, plus
format/receipt overhead. The executor checks observed filesystem capacity with a
1 GiB reserve before worker startup. Driver allocation and process RSS are
separate observations; encoded-byte limits do not guarantee physical RSS caps. Application preflight also checks capacity
before extraction. SQL allocation counts reserved pages of all dpone_native_ staging tables in
the configured database, including other invocations. SQL allocation and log
headroom checks are observations, not
exclusive reservations. Already active imports can overshoot an observed SQL stop
threshold; exceeding it prevents successful stage completion/publication.
The journal stores per-phase worker observations, encoded bytes, rows, immutable file hashes and typed multiset digests. Effective parallelism uses observed worker active time and wall time. A digest is probabilistic evidence; small correctness certification cases must also compare exact typed multisets. Do not report the configured worker count as measured overlap.
Diagnose and recover¶
Every target-local invocation seals schema and transaction authority before ClickHouse business-row I/O. Read-only inspection needs only the durable SQLite journal. Live recovery actions require the original manifest selector and explicit confirmation; they reconnect only to MSSQL and never reopen the source.
Follow the MSSQL native recovery runbook for the
complete inspect, inspect-all, reconcile, resume, and retire commands,
state-to-action matrix, outputs, exit codes, safe retries, and escalation.
UNKNOWN retains custody across lease expiry. Held v2 custody blocks both v2
and rollback v1 invocations. Publication outcome is reconciled from the exact
transaction receipt before any replay, cleanup, or custody release. Layout-v2
recovery also binds its persisted hash and mutation-watermark identity, as
decided in ADR 0077.
For a first deployment, run admitted synthetic NULL, duplicate, Unicode, Decimal, empty-window and outside-window invariance cases in an explicitly approved disposable environment. Record authored binary source mapping as unsupported; encoder-only binary fixtures do not enable the canonical route. Test interrupted imports and lost commit acknowledgements. Record exact source commit, dependency/server versions and observed resources. Follow the approved design and route certification standard before promoting a specific environment. Existing manifests need no migration.