Repository navigation
refactor(linux)!: switch the Linux app to driver contract v2 - #3358
Conversation
A dialog torn down without a response made the binding's choose callback abort the process. The presenter now waits on the response and closed signals itself, so a forced close reads as a cancel.
The connect dialog is a gtk-rs subclass with CompositeTemplate .ui files in the GResource. It checks the form and emits a ConnectSubmission (request and PendingSave); the window connects, saves the entry and shows a Cancel button on the Connecting toast. Only Test connects from the dialog, and it closes the connection in the same task. - Security group: Encryption over the driver's TLS modes (Verify full by default), CA certificate, client certificate and key, TLS server name. The PEM files are read on the blocking pool with a generation, so Connect waits for the newest check. - SQLite file row with Choose and New Database through the portal. - SSH: agent, private key, password and keyboard-interactive; host aliases, port, user and jump hosts may come from the SSH config. - Remember password, read-only subtitle by enforcement, TLS remedies on the Test toast and on the failed-connect alert, which reopens the dialog on what was submitted. - PendingSave gains forget, so turning Remember off removes the old password from the keyring. - Server codes are named per engine in error text.
…list The SQL editor is a gtk-rs subclass with a CompositeTemplate. Each Run goes through the tab's own EditorSession one ScriptPlan batch at a time: one statement on semicolon engines, a whole GO batch (repeated for GO n) on SQL Server. A failed statement stops the script unless the engine runs batches independently, and the rest are shown as not run. - Results list: one line per result set, command (rows affected), notice, error (with Go to Error when the server gives a position), warning, stop, timeout or not-run statement, in an OverlaySplitView that folds below 560sp with a toggle to bring it back. - Run sends the selection when there is one (Run Selection). - The transaction state from the last run is shown above the results, and closing a tab with a transaction open asks first. - An unterminated string or comment adds a warning and is still sent. - A lost editor session asks the window's monitor to probe. - replace_all_undoable and CursorAnchor make Format, a replaced query (with an Undo toast) and file loads one undo step; dropped files are read asynchronously and non-UTF-8 files are refused.
Every engine has its own SqlDialect now, so the driver-id keyed EngineDialect, dialect_for and grammar_for go. The legacy DDL builder keeps its own identifier quoting until per-engine DDL dialects replace it, and the app and core tests use the PostgreSQL dialect or the core test dialect. core's MySQL COLUMN_TYPE parser goes too: the MySQL driver has its own scanner. - rust_decimal leaves the workspace and sqlx's features; deny.toml bans it, as decimals are SqlDecimal. - CI runs the tablepro-session docker tests in the ubuntu:26.04 image with the host's Docker socket, since storage needs GLib 2.88. - docs/testing.md documents the conformance and TLS conformance suites and drops the v1 examples. - linux/CHANGELOG.md gains the user-visible entries for this work.
Handoff: state of this PR on 2026-10-12, for the next machineWork moves to another machine. This comment and the ones after it hold everything the next agent needs; nothing lives only on the old machine. Branch head: Start here git fetch origin
git switch refactor/linux-driver-contract-v2 # tracks origin, head 89a9b2b4fThen read, in order: this comment, the plan comment ( Scope and decisions already made
Commit map (oldest first)
Verified on Linux (Ubuntu 26.04 arm64, GNOME 50), before 89a9b2b
Not yet verified: What is left in this PR (1a)
Known gaps to carry into 1b or later
Next PRs
How to work on itLinux verification needs Linux:
The V=.claude/skills/fix-issue/scripts/verify.sh
$V --platform linux setup # once per tree, and after a merge from linux
$V --platform linux build
$V --platform linux test <package> [filter] # tablepro-core, tablepro-postgres, tablepro-session, tablepro ...
$V --platform linux test # every Meson suite, as CI's fast job
$V --platform linux lint # rustfmt, clippy with and without default features
$V --platform linux l10n
$V --platform linux data
$V --platform linux docker <package> # the real engines, needs Docker
$V --platform linux secrets # Secret Service tests in a throwaway keyringBy hand, from
Rules for this work
Working preferences of the maintainerThese lived only in the old machine's agent memory:
Databases for manual app runsThe tests start their own containers through
Each server had user and password |
Analysis file
|
Analysis file
|
Analysis file
|
| Commit | What it added |
|---|---|
| 1da0822 | lossless value component types (value/*) |
| ad3d221 | column/* (ColumnType, ColumnKind, ReadForm, SqlTypeExpr), sql_syntax/*, ddl/referential_action.rs, WideInteger on num-bigint |
| 74672a7 | statement/* (Statement, BoundParam, ParamType, StatementBuilder, RowGuard, GuardedStatement, SqlBuildError), column/column_info.rs, query_result.rs, foreign_key_info.rs, edit/cell_input.rs |
| 84e2590 | meta/* (TableRef, SchemaInfo, ObjectScope, ObjectInfo/ObjectKind, IndexInfo/IndexKey, RowIdentity, EngineRowId/Part, page types, TableEstimate), browse/{split_page,row_key_layout,browse_page,browse_sql_error}, dml/key_component, save/{change_set,change_origin,save_plan}, dialect/{bind_target,like_form,keyset_direction,placeholder,...} |
| b2c75ae | edit/parse_literal_text.rs (parse_literal_text, parse_filter_text) |
| 745d78a | SqlDialect trait, dml/{build_insert,build_update,build_delete,predicate}, dialect/test_dialect.rs, dialect/engine_dialect.rs (EngineDialect keyed by driver id) |
| f4260f4 | dialect-driven build_filter/operators_for in filter.rs |
| e861c99 | Value switched: drivers return value::Value (lossless), QueryResult{columns: Arc<[ResultColumn]>}; old query::Value gone |
| 14dd128 | DriverError v2 + ErrorCategory + ServerDiagnostics (drivers classify) |
| f4a80e2 | OwnedCalls, StopRequest/StopReason, EngineStop/CallResource/StopDelivery/StopScope, WriteLedger, SessionFate |
| 7a4ee6e | EditorSession + RunOptions + ResultSink/ResultEvent/RunSummary, DriverCapabilities, LinkWatch, UserSql, RowLimit, TransactionState, WriteBatch, WriteReport, CommitToken |
| 92aea71 | W3-17: export split into export/*, value_text at column precision, JSON raw number tokens |
Commits after the plan that the plan does not know about, and that the switch must carry (confirmed): 9e00c03 column comments (ColumnInfo.comment, comment DDL in sql_ddl.rs), 3bcd8df StructureOp::RenameColumn (sql_ddl.rs:268, 1161, 1281), ec75581 SQL INSERT export (export/sql.rs, uses dialect_for), d91c70a CSV import (core/src/import/csv_import.rs, app/src/ui/app/import.rs on execute_in_transaction + dialect_for), e508f68 XLSX export.
v2 types present in core today (confirmed)
CallOptions (call_options.rs:11-28), DriverCapabilities + EndpointKind/ReadOnlyEnforcement/ScriptExecution/CommitResolutionSupport/EstimateQuality (capabilities.rs:8-90), DriverError v2 (error.rs:23-126, no FileExists, exhaustive category() 132-153), ErrorCategory (error_category.rs), ServerDiagnostics/ServerCode, ReadOnlyRefusal, LossPhase, TimeoutPhase, EditorSession/Id/State/Options (editor_session.rs:11-75), RunOptions, ResultSink/SinkFlow, ResultEvent incl. ColumnsResolved (result_event.rs:37-60), RunEnd/RunSummary/Truncation/ServerNotice, RowLimit (EDITOR_DEFAULT 10,000), UserSql, TransactionState, ServerInfo, LinkWatch/LinkState, OwnedCalls (owned_calls.rs:37 new, 47 run_read, 131 run_write, 209 shutdown), StopRequest/StopReason, EngineStop/CallResource/StopDelivery/StopScope (engine_stop.rs), WriteLedger/WriteAtomicity (write_ledger.rs:38-124), SessionFate, WriteBatch/WriteKind (write_batch.rs:10-25, with table, without foreign_keys), WriteReport/StatementOutcome, CommitToken/CommitResolution, meta::*, statement::*, column::*, value::*, browse::{split_page, RowKeyLayout, BrowsePage, BrowseSqlError}, dml::*, save::{ChangeSet, RowInsert, RowUpdate, ChangeOrigin, SavePlan}, dialect::*, edit::{CellInput, EditParseError, ReadOnlyReason, parse_literal_text, parse_filter_text}, plus W2-09's transport/TLS/endpoint types (NetworkEndpoint, FileEndpoint, TlsConfig, LivenessPolicy, Transport, TransportError, ...).
v2 types absent (confirmed by an empty workspace grep for each name)
Connection v2 trait, DatabaseDriver v2, ConnectTarget, NetworkTarget, Credentials enum, ConnectEnvironment, WorkspaceSettings, APPLICATION_NAME, stream_read/ReadStatement/StreamSummary, PageRequest, SortKey, PageSpec, build_page, build_count, paging_mode, build_export, plan_save, validate_raw_predicate, edit_text/parse_edit_text/empty_input_meaning/is_unchanged, EditContext, SqlDialect::ddl(), DdlDialect, TableDefinition/ColumnDefinition/ColumnAttributes, ForeignKeyMode/ForeignKeyCheck, fetch_table_definition, classify_default, check_runtime_version, WORKSPACE_SQL_MODE_STATEMENT, ConformanceEngine, OperationKind, current_dialect, literal_of (public), source_offsets, strip_comments.
v1 types the switch deletes, with every consumer (confirmed by grep over crates/)
| v1 item | Defined | Consumers |
|---|---|---|
Connection v1 (list_tables, fetch_rows, query, query_params, execute, execute_params, execute_in_transaction, ping, close(Box)) |
core/src/connection.rs:64-118 | all 5 drivers' lib.rs; core read_only.rs; test-support fake_connection.rs, call_counters.rs, connection_script.rs; app services/connection_service.rs, connection_monitor.rs (ping), ui/app/{mod,browse,row_ops,structure,import,connection,workspace_tabs}.rs, ui/browse_tab.rs, ui/connect_dialog.rs, services/change_tracker.rs; driver tests (all 5 integration.rs, sqlite/tests/csv_import.rs) |
ConnectOptions v1 (host, port, use_tls, service_endpoint, service_address, Default) |
connection.rs:25-62 | 5 drivers, app connection_service.rs, connect_dialog.rs; service_endpoint/service_address also in core transport.rs, net/src/direct_tcp.rs, ssh/src/forward.rs, mssql lib.rs |
AuthMode |
connection.rs:15-23 | stays (persisted, serde) but must move out of connection.rs; used by capabilities.rs, config_error.rs, storage connections.rs, app (7 files) |
DatabaseDriver v1 (default_port, is_file_based, ddl_is_transactional, reports_rows_affected, supports_integrated_auth, connect -> Box) |
driver.rs:7-49 | 5 drivers; test-support fake_driver.rs; app connect_dialog.rs (default_port, is_file_based, supports_integrated_auth), connection_service.rs, ui/app/structure.rs:255-258 (ddl_is_transactional), ui/app/row_ops.rs:40-44 (reports_rows_affected) |
TableInfo, ExecResult, v1 IndexInfo (columns: Vec), v1 ForeignKeyInfo (String actions) |
query.rs:3-41, re-exported at lib.rs:95 | drivers; read_only.rs; sql_ddl.rs:18; app ui/app/{mod,structure,connection,workspace_tabs}.rs, structure_tab/{mod,indexes,fks}.rs, structure_tab_dialogs.rs, connect_dialog.rs, sidebar_row.rs; test-support |
MAX_QUERY_ROWS |
lib.rs:99-101 | 5 drivers, clickhouse/tests/integration.rs |
ReadOnlyConnection |
read_only.rs:11-170 | app services/connection_service.rs |
sql_dialect.rs (quote_ident(driver_id), placeholder_for, build_update, build_order_and_pagination, build_single_cell_update, build_full_row_update, build_insert_from_draft, v1 BuildSqlError) |
core/src/sql_dialect.rs:1-631 | core engine_dialect.rs, sql_ddl.rs:19; clickhouse lib.rs:15 + tests; mssql lib.rs; app change_tracker.rs:35, ui/app/browse.rs:55-98,180, ui/error_text.rs:2 |
EngineDialect, dialect_for(driver_id), grammar_for(driver_id), parse_for_column (interim, not in plan) |
dialect/engine_dialect.rs:21-304 | app ui/app/{browse,import,row_ops}.rs, ui/export_dialog.rs:382, ui/editor/mod.rs:675,697 (grammar_for); core export/sql.rs tests; sqlite/tests/csv_import.rs |
classify_type_name, has_dynamic_storage |
column/classify.rs | all 5 drivers' column_type_of; engine_dialect.rs:228; app change_tracker.rs, browse_tab.rs, grid.rs:2191 (tests) |
normalize_pg_default / normalize_mssql_default / normalize_default_value |
postgres lib.rs:525, mssql lib.rs:631, sqlite lib.rs:521 | own driver |
sql_ddl.rs (driver_id builders, validate_safe_*, mssql_drop_default_constraint dynamic SQL at 347-360, supported_fk_actions, StructureOp, diff_to_ops, materialize_ops) |
core/src/sql_ddl.rs:1-2609 | app services/structure_tracker.rs:23,55, ui/structure_tab/{mod,columns}.rs, structure_tab_dialogs.rs:24,197, ui/app/{mod,structure}.rs; pg/mysql/mssql tests/integration.rs |
export::render_in_clause/InClause/in_clause_literal |
export/in_clause.rs:1-74 | app ui/grid.rs:1624 |
export::value_to_text (kept as an internal of value_text; public callers go) |
export/value_text.rs | drivers' bind fallbacks (pg 503, mysql 504, sqlite 501, mssql 475, clickhouse 758), sql_ddl.rs:169, engine_dialect.rs:127, app grid.rs:2131 |
1. Cross-cutting findings that decide the lane's shape
F1. ColumnKind / ColumnType / CatalogType diverge from FB-C5/C6 (confirmed; decision needed)
- Landed
ColumnKindis flat:Boolean, Integer(IntegerKind), Decimal, Float(FloatKind), Text(TextKind), Binary, Date, Time, Timestamp, Interval, Uuid, Json, Enumeration, Set, BitString, Network, Geometry, Array, Other(column/kind.rs:6-30),IntegerKindis a 10-variant enum (kind.rs:32-44). - FB-C5 (details.md:1852-1891) requires payloads:
Bool(BoolStorage),IntegerKind { width, signed }with W24/W256,Decimal { precision, scale },Money { scale },Text { length },Enum { labels, multiple },Bytes { length },Json { binary },Time { fractional_digits, with_offset, range },Timestamp { fractional_digits, zone },BitString { length, varying },Network(NetworkFamily),Xml,Spatial,Array { element },Variant,Composite. - Landed
ColumnTypehas private fields andcatalog: CatalogType::{Unknown, Oid(u32), Named(SqlTypeExpr)}(column_type.rs:6-28); FB-C6 (details.md:1909-1917) hascatalog: Option<CatalogType { declaration, cast_target }>. - Precision is recovered today by parsing the type name (
fractional_digits,decimal_scale,type_argumentsat column_type.rs:95-135). - Specs that cannot be met without the FB-C5/C6 payloads: W3-07 ServerText
$n::<cast_target>andMissingCatalogType; W3-08 teststinyint(1) -> Bool(Integer),money -> Money{Some(4)}, Spatial, enum labels inenumsortorder,int4range -> Other ServerText,bit(10)width; W3-10DraftType::Catalogsplicescatalog.declaration,MissingCatalogDeclaration; W3-13 "CHARACTER SET only for textual kinds, SRID only for Spatial, ON UPDATE only for Timestamp"; W3-17 "ColumnKind::{Time,Timestamp}.fractional_digits"; editReadOnlyReason::{Spatial, Composite, Variant}already exists (edit_parse_error.rs:58-66) with no ColumnKind to produce them. - Recommendation: W3-05 brings ColumnKind/ColumnType/CatalogType to FB-C5/C6 inside the switch (196
ColumnKind::references in 20 files, 40.kind()calls, 22CatalogType::sites; confirmed by grep). Keepingkind()as an accessor is fine;catalogmust carrydeclarationandcast_target.
F2. The dialect lives in core, keyed by driver id (confirmed; contradicts FB-D10 and W3-07)
EngineDialect { driver_id, grammar }withdialect_for(driver_id)falling back to PostgreSQL (dialect/engine_dialect.rs:21-58) implements all five engines throughdriver_idstring matches and calls v1sql_dialect::{quote_ident, placeholder_for, build_update, build_order_and_pagination}(engine_dialect.rs:4, 93, 107, 162, 194).- It is wrong against the normative C12 table in several places:
ESCAPE '\\'everywhere (engine_dialect.rs:156, default at sql_dialect.rs:90) instead of!; ClickHouse gets bound?-style placeholders instead ofCAST(<literal> AS <decl>); noE''/_utf8mb4 X''/N''literals; Bytes alwaysUnrepresentable(engine_dialect.rs:126); MSSQL probeCOUNT(*)/TOP 2notCOUNT_BIG(*) ... TOP (2) ... WITH (UPDLOCK, HOLDLOCK); no read_form projections; row-id projectionctidinstead of"ctid"::text AS "__tablepro_row_id". - W3-07 must delete engine_dialect.rs,
dialect_forandgrammar_for, and the editor's grammar choice (app ui/editor/mod.rs:675-698) needs another source. Proposal (inferred):SqlDialect::grammar() -> SqlGrammar, or agrammarfield inDriverCapabilitiesso the editor can lex before a connection exists.
F3. Binding paths are duplicated (confirmed)
StatementBuilder::push_value(&dyn PlaceholderStyle, value, ParamType)(statement/statement_builder.rs:40-53) is used only in tests (row_guard.rs:66-70;PlaceholderStyleimplemented only in a test at statement_builder.rs:72).- dml and filter assemble SQL and params by hand with a
first_ordinal(dml/predicate.rs:11-50, filter.rs:235-270). - W3-06 requires
StatementBuilder { push_value(dialect, value, BindTarget) }andbuild_filter(&mut StatementBuilder, ...) -> Result<bool, _>. Unify onSqlDialect::placeholder(which can inline ClickHouse CASTs) and deletePlaceholderStyle. Ordinal: FB-C10 says the ordinal is the count already emitted; engine_dialect.rs:107 doesordinal.saturating_sub(1). Fix it once in the builder.
F4. The conflict list adds scope to W2-11 and W3-06 (confirmed: plan.md:1068-1069)
- W2-11 adds
Connection::stream_readwithReadStatementandStreamSummary(XW1). - W3-06 adds
build_export,PageSpec.layout,browse::paging_mode, COUNT_BIG for SQL Server (XW3/13/15), and the core edit APIedit_text,parse_edit_text,empty_input_meaning,is_unchangedwith a non-#[non_exhaustive]EditParseError(XW9). - None of these exist; they are not in the per-task specs, so they are easy to miss.
F5. Review amendments that touch this lane
- W3-07, W3-11: ClickHouse identifiers and values go through
clickhouse::sql::Identifier/Query::param, or a drift test. Crate facts: the escape set ispub(crate)(clickhouse-0.15.2 src/sql/mod.rs:11pub(crate) mod escape; escape.rs:19-29), butIdentifieris public (sql/mod.rs:8, bind.rs:23-31) andQuery::sql_display()is public (query.rs:45-48) and renders bound args immediately (sql/mod.rs:83-103). So an offline drift test is feasible:Client::default().query("?").bind(Identifier(name)).sql_display().to_string() == ClickhouseDialect.quote_identifier(name). - PostgreSQL goes through
postgres_protocol::escape::{escape_identifier, escape_literal}. postgres-protocol 0.6.12:escape_literalemitsE'...'(leading space) when the input has a backslash (docs.rs src escape/mod.rs:13-16, 62-65). This matches W3-07'sE''golden; the golden must expect the leading space. It is not in Cargo.lock; W2-12 adds it (review W3-09 amendment). - Use one core hex helper (exists: core/src/hex.rs
encode_lower). ClickHousehex_encodein its lib.rs goes. - W2-16, FC-D5:
Client::query_rawreplacesescape_bind_markers(clickhouse lib.rs:787). W3-07's "escape_bind_markers stays" is stale; delete it withbind_placeholders,literalandescape_str(lib.rs:612, 728, 777).clickhouse-typesis 0.1.3 (lock and registry), not 0.1.2. - FB-D3, W3-03: WideInteger on num-bigint. Done (value/wide_integer.rs:4-10, 86-104: ASCII
-?[0-9]+check and 78-char cap beforeBigInt::from_str). The check is needed because num-bigint 0.4.8 accepts+and_(biguint/convert.rs:213-235, bigint/convert.rs:29). The lock has num-bigint 0.4.8 (core) and 0.5.1 (russh). Per the review, 0.4 is correct once russh leaves under FC-D17. - SqlDecimal stays custom (value/decimal.rs, MAX_INTEGER_DIGITS 131,072 at :42).
- FD-D19, W8-10: ban
rust_decimalin deny.toml "after the switch". Not banned yet (deny.toml [bans] lines 38-50). Still declared in the workspace Cargo.toml:42, the sqlx feature (:38), the tiberius feature (:39), app/Cargo.toml:43 (norust_decimal::use in app src, so likely unused; inferred), postgres Cargo.toml:18 (to_pg_decimallib.rs:791), and mysql/mssql dev-deps. - Query/DDL building: keep per-engine dialects; insta snapshot tests for golden SQL; avoid sea-query. insta is not in the lock. Latest is 1.49.0 (crates.io, 2026-10-03), which is under two weeks old, so take 1.48.0 (2026-06-11), the version the review cites.
- W2-14 / mssql default: sqlparser
MsSqlDialectparse_exprfor SQL Server defaults (W3-08 classify_default). - FC-D2/FC-D6 (relay removed):
ConnectEnvironment.private_socket_dirin W2-11 is stale. The OpenSSH forward socket belongs to tablepro-ssh (ADR 0013). Keep onlyclient_version(inferred). - FC-D8 (per-engine trust): the trust shape in
NetworkTarget { transport, credentials, database, tls, trust }must follow the per-engine trust decision (platform-verifier for PG/ClickHouse; trust_cert_ca for MSSQL; sqlx roots + CA for MySQL). Thetrustfield is probably aTrustSourcehandle the drivers interpret per engine (inferred). - FC-D1/W2-12: "Editor columns text-typed until Canceling connection does not actually cancel #1358".
ResultEvent::ColumnsResolvedalready exists, so no core change. - Relm4 replacement (FA-D1 amendment, problem.md): every app file these tasks rewrite moves to gtk-rs subclasses. In this lane that means the structure tab (StructureTab is a
SimpleComponent, ui/structure_tab/mod.rs:495) for W3-10/W3-12/W3-14, and BrowseTab (SimpleComponent, browse_tab.rs:1342) plus grid for W3-11. W3-11 only edits call sites, so leave the widget migration to the app lane and the step that rewrites the widget (W3-12 for the structure tab).
F6. ADR numbering is stale (confirmed)
The plan assigns 0011 to W3-12 and 0013 to W2-19 (plan.md:1073). The repo already has 0013-openssh-client-transport.md and 0015-gnome-50-platform-floor.md (docs/decisions/README.md index). The details file also names 0010-driver-contract-v2.md (W2-19, details.md:8541) and 0010-ddl-plans-and-table-definitions.md (W3-12, details.md:9611). Free numbers: 0008, 0010, 0011, 0012, 0014. Proposal: W3-12 keeps 0011. W2-19 takes 0010 (W1-01's openssh-config ADR is gone under FC-D17, because 0013 covers it) or 0016. The owner should choose.
F7. META extension missing: PostgreSQL xmin (confirmed)
details.md:8591 requires PgDialect engine_row_id to project ctid, xmin and the DML key to add xmin = $n::xid. EngineRowIdPart has only PostgresTableOid, PostgresCtid, SqliteRowid (meta/row_identity.rs:34-38), and EngineRowId::parts() (:44-50) has no xmin part. W3-06/W3-07 must add EngineRowIdPart::PostgresXmin (bound as ServerText(xid)), or the owner must drop the requirement.
2. Per-task reports
W2-11 · Core driver contract v2 (Connection, DriverError, EditorSession, writes, metadata, OwnedCalls, WriteLedger)
Status: partial.
Done (confirmed):
- DriverError v2 exactly per ERR minus FileExists, with an exhaustive
category()(error.rs:23-153). - ErrorCategory, ServerDiagnostics, ReadOnlyRefusal, LossPhase, TimeoutPhase.
- CallOptions with a single
new(call_options.rs:16-18). It usesOption<tokio::time::Instant>where CONN saysOption<Duration>. Instant is the better carrier for propagation; record the divergence. - DriverCapabilities with every enum (capabilities.rs), ServerInfo, LinkWatch (
mark_lost,state, lost future: link_watch.rs:21-29). - StopReason/StopRequest, StopScope/StopDelivery/EngineStop/CallResource, OwnedCalls run_read/run_write/shutdown, WriteLedger with
stop_permitted/interrupted/failed/guard_failed, SessionFate. - EDITOR types incl.
ColumnsResolved;RowLimit::EDITOR_DEFAULT; UserSql. - WriteKind/WriteBatch{kind, table, steps}, GuardedStatement/RowGuard, WriteReport, CommitToken/CommitResolution/StatementOutcome (no serde).
- All META types.
- core deps tokio [rt, time] and tokio-util [rt] (core/Cargo.toml:25-26).
Remaining work, core:
crates/core/src/connection.rs: replace with the CONN trait.server_info,dialect() -> Arc<dyn SqlDialect>,read_only,link_state,lost() -> WaitForCancellationFutureOwned,probe,list_schemas,list_objects,fetch_columns(&TableRef),fetch_indexes,fetch_foreign_keys,row_identity,table_estimate,query_bound(&Statement, RowLimit),count_rows,write_batch,resolve_commit,open_editor_session,close(&self). Plusstream_read(ReadStatement, sink) -> StreamSummary(F4) andfetch_table_definition(W3-18). MoveAuthModetoauth_mode.rs.crates/core/src/driver.rs: DRIVER trait (capabilities() -> &'static DriverCapabilities,connect(options, cancel) -> Arc<dyn Connection>).- New:
connect_options.rs,connect_target.rs,network_target.rs,credentials(addCredentials { Password { username, password: SecretString }, Integrated }beside the existing credentials/ prompt module),connect_environment.rs(client_versiononly, F5),workspace_settings.rs(read_only, statement_timeout, lock_timeout; workspace(read_only) = 60 s / 10 s),APPLICATION_NAME. - Optional, to follow the spec's file list: split
editor_session_options.rs,result_event.rs/run_summary.rs,stop_delivery.rs,call_resource.rsandobject_scope.rsout of the files where they now live. This is cosmetic; skip unless reviewers ask. - Delete
crates/core/src/read_only.rs,crates/core/src/query.rs(TableInfo, v1 IndexInfo, v1 ForeignKeyInfo, ExecResult),MAX_QUERY_ROWSand the v1 re-exports at lib.rs:95-102. Re-export the v2meta::IndexInfoandforeign_key_info::ForeignKeyInfoat the root. - Tests missing against the spec list: owned_calls has 8 tests (owned_calls.rs:430-664) under different names.
dropping_caller_future_still_calls_stop,connection_scope_waits_for_stop_before_release,statement_scope_releases_immediatelyandin_band_delivery_cancels_stop_request_and_never_calls_stophave no obvious counterpart (inferred from the names and the InBand-only fixtures at 498/525/598/667).write_ledger::outcome_table(full matrix) is not present as a table test (write_ledger.rs:239-373 are case tests).error_category::every_variant_has_a_category,row_limit::zero_is_none(a near equivalent exists, row_limit.rs:31),object_kind::browsable_and_structure_editable_table(near equivalent in object_info.rs) andlink_watch::mark_lost_resolves_lost_future(near equivalent link_watch.rs:48) need checking or renaming.
Remaining work, test-support:
crates/test-support/src/conformance.rs(new):ConformanceEngine+ the 8 scenarios.- Rewrite
fake_connection.rs,fake_driver.rs,call_counters.rsandconnection_script.rsto v2. Add aFakeDialect(W3-07 / conflict list plan.md:1075). crates/test-support/Cargo.toml.
Out of lane, but blocking the switch: every driver (W2-12..W2-16) and the app (W4-06, W6-*).
- Dependencies: none new. tokio and tokio-util are already present (tokio 1.53.1, tokio-util 0.7.19 in Cargo.lock;
drop_guardat tokio-util cancellation_token.rs:275,cancelled_ownedat :267,TaskTrackerat task/task_tracker.rs:158). - Tests the spec requires: the 11 owned_calls cases,
write_ledger::outcome_table,guard_failed_after_applied_is_partially_applied(exists as a_guard_mismatch_after_a_statement_landed_says_so, write_ledger.rs:360),error_category::every_variant_has_a_category,row_limit::zero_is_none,object_kind::browsable_and_structure_editable_table,link_watch::mark_lost_resolves_lost_future, and the conformance scenarios (run per driver by W9-07). - Depends on: W2-09 and W2-10 (landed: 969b0a6, 70835e1), W3-05, W4-01 (landed efcf0f5). Every driver task and W4-06 consume it.
- Stale or amended spec text:
ConnectEnvironment.private_socket_dir(relay removed, F5);FileExistsalready absent;CallOptions.deadlinetype; "deadpool Object::take" applies only to deadpool-postgres and the custom mysql/mssql managers (FC-D18 amendment); conformanceprobe_never_waits_for_pool_slotholds for SQLite through the single workspace connection. - Size: core and test-support about 1,800-2,300 changed lines (Connection/Driver about 250; connect options about 300; stream_read about 150; deletions about 260; missing tests about 400; conformance module about 600; fakes about 350). Driver and app adoption are counted in other lanes.
W3-05 · Lossless value, column metadata and statement types (contract switch, core model)
Status: partial (mostly done).
Done (confirmed):
Valuewith all 22 variants and boxed Other/Undecodable (value/mod.rs:27-51),is_null,variant_name,as_exact_u64, no serde.- The size test asserts ≤ 32 bytes (mod.rs:103-105), stricter than the spec's 40.
ValueIdentity+same_value(value/identity.rs; NaN, -0 and offset test at :98).ColumnInfo(with an extracomment),ResultColumn,QueryResult{Arc<[ResultColumn]>, rows, truncated}(query_result.rs:11-18).ColumnDefault {None, Literal, Expression}(column/default.rs),ForeignKeyInfowithOption<ReferentialAction>(foreign_key_info.rs:13-22),CellInput.Statement,BoundParam::newrejecting mismatches and Undecodable,ParamType::acceptswith Null always accepted,GuardedStatement/RowGuard,SqlBuildError,BindError/LiteralError.- The export lossless rules (value_text.rs tests
value_to_text_keeps_fraction_offset_and_full_scale, jsonjson_non_finite_float_is_string). - rust_decimal is gone from core (no entry in core/Cargo.toml). The drivers already emit v2 Value (e861c99).
Remaining:
- F1 reconciliation: bring
column/kind.rs,column/column_type.rs(public fields or accessors,catalog: Option<CatalogType { declaration, cast_target }>) andcolumn/read_form.rsto FB-C5/C6. Addkind_parts.rs(BoolStorage, IntegerWidth, FloatWidth, TextLength, TimeRange, TemporalZone, NetworkFamily). Updatefractional_digits/decimal_scaleto read the kind payloads. Then fix the 20 files that match on ColumnKind. - Decide on
ColumnInfo.comment, a post-plan field (9e00c03, column_info.rs:20-23) that W3-18 duplicates asColumnAttributes.comment. Proposal (inferred): move it intoColumnAttributesin W3-18 and keepColumnInfoper FB-C8. Otherwise the comment DDL in sql_ddl.rs needs both. EditContext { InsertDraft, UpdateRow }(W3-04 item, absent).- Delete the v1
query.rstypes (shared with W2-11). - Remove
rust_decimalfrom the workspace once W2-12, W2-13 and W2-15 drop it, and add the deny ban (F5). - test-support column, type and value builders (none exist).
- Dependencies: remove
rust_decimal 1(workspace Cargo.toml:42, the sqlx and tiberius feature lists :38-39, app/Cargo.toml:43, postgres/mysql/mssql). num-bigint 0.4.8 stays. - Tests the spec requires:
value_size_is_at_most_40_bytes(exists as ≤32),as_exact_u64_table(exists, mod.rs:108), the identity tests (exist, combined),bound_param_rejects_text_for_uuid_and_undecodable(exists),param_type_accepts_null_for_every_variant(exists),statement_builder_ordinals_are_continuous(exists),value_to_text_keeps_fraction_offset_and_full_scale(exists),json_non_finite_float_is_string(exists). New: ColumnKind helper table for the FB-C5 semantics (is_textualincludes Xml/Other,accepts_empty_stringincludes Enum with a "" label). - Depends on: W3-03 and W3-04 (landed, but W3-04's ColumnKind shape is not FB-C5), W4-01.
- Stale: "size ≤ 40" (32 holds); the "export adaptation" is done;
render_in_clausemoves out in W3-11. - Size: about 300 lines without F1. With F1, about 900-1,300 lines across core, the drivers'
column_type_ofand app grid/filter_strip.
W3-06 · SqlDialect trait, row-identity keys and dialect-driven core SQL builders
Status: partial.
Done (confirmed):
SqlDialecttrait with capabilities, quote_identifier, qualify, projection, sort_expression, count_all, placeholder, literal, escape_like_text, like_predicate, row_window, row_value_after, engine_row_id, engine_row_id_column, update/delete/insert_default_row/key_probe, parse_type and parse_expression (dialect/sql_dialect.rs:12-113).- DialectCapabilities, BindTarget incl. EngineRowId, Placeholder, LikeForm/LikeCase, KeysetDirection, BindError, LiteralError.
TestDialect(#[cfg(test)], dialect/test_dialect.rs).key_components(dml/key_component.rs:11-29, Unordered -> ReadOnlyRows);build_insert/build_update/build_delete/build_key_probewith IS NULL and UndecodableKey;BuildSqlErrorwith all 8 variants (dml/build_sql_error.rs).RowKeyLayout,BrowsePage,split_pagewith ShapeMismatch and None keys (browse/split_page.rs).ChangeSet/RowUpdate/ChangeOrigin/SavePlan<B>.build_filterwith Parse/Bind/OperatorNotAllowed,operators_for(ColumnKind)(filter.rs:107-270);parse_literal_text/parse_filter_text.
Remaining:
SqlDialect::ddl() -> Option<&dyn DdlDialect>(returns None until W3-12; missing from sql_dialect.rs).- Grammar accessor (F2).
like_predicatedefault ESCAPE char (spec engines use!).StatementBuilder::push_value(dialect, value, BindTarget); deletePlaceholderStyle(F3).filter.rs->filter/{mod,filter_set,build_filter,operators_for,build_filter_error}.rs.build_filter(&mut StatementBuilder, ...) -> Result<bool, _>. Delete the v1 leftovers (extra_is_blankfilter.rs:102). Addvalidate_raw_predicate(dialect, text)(sqlparser parse_expr + EOF; details.md:8590) and call it from build_page.browse/{page_request,sort_key,page_spec,build_page,build_count}.rs+paging_mode+build_export+PageSpec.layout(F4), keyset viarow_value_after, ctid windows, OFFSET with identity tiebreak, COUNT_BIG for MSSQL. Changesplit_pageto take&PageSpec(it takesvisible_columns, &RowKeyLayouttoday, split_page.rs:11-15).save/plan_save.rs: inserts, updates, deletes; guard table;WriteBatch{.., foreign_keys: Enforced}(needs W3-19).SavePlanis genericSavePlan<B>today; pin it toWriteBatch.edit/{parse_edit_text, edit_text, empty_input_meaning, is_unchanged}+EditContext(F4/XW9). EditParseError must not be#[non_exhaustive]; it isn't marked today (edit_parse_error.rs:13).- Delete
crates/core/src/sql_dialect.rs(631 lines) once W3-11 and the drivers stop using it. - Add
EngineRowIdPart::PostgresXmin(F7) if the owner keeps it. - Dependencies: none (sqlparser 0.63.0 present).
- Tests the spec requires that are not yet present:
build_update_engine_row_id_uses_part_columns_and_targets(the closest isan_engine_row_id_needs_a_dialect_that_has_one),build_insert_all_default_uses_default_row_or_errors(only the default-row half exists),key_components_unordered_is_read_only, all 8 browse build tests, the 3 save tests,parse_filter_text_integer_bounds_i8_to_u64,parse_literal_text_numeric_scale_exceeded. Present: the filter tests (filter.rs:456-589),split_page_*(4 of 4),build_update_null_key...,build_update_undecodable_key...,build_insert_omits_default_cells_and_generated_columns. - Depends on: W2-11, W3-05, W3-19 (ForeignKeyMode).
- Stale: the spec says
filter::classifyis deleted (it already is);dialect/mod.rsdoc says "the dialect trait itself lands with the per-engine implementations" (mod.rs:1-4), which is stale. - Size: about 1,600-2,000 added and about 700 deleted (sql_dialect.rs plus v1 filter remnants).
W3-07 · Per-engine SqlDialect implementations
Status: not started (the interim core EngineDialect is a placeholder to delete, F2).
Remaining:
- New:
crates/drivers/postgres/src/dialect.rs+param_oid.rs;crates/drivers/{mysql,sqlite,mssql,clickhouse}/src/dialect.rs. Each is built from ServerInfo at physical connect (PostgresDialect{server_version_num}, MySqlDialect{version, flavour}, SqliteDialect{version}, MssqlDialect{major_version}, ClickhouseDialect{version}) per C12. - PG:
quote_identifier=postgres_protocol::escape::escape_identifier; literal viaescape_literal(F5). - ClickHouse: identifier escape with the offline drift test against
Identifier+sql_display(F5). - Delete: core
dialect/engine_dialect.rs(431 lines),dialect_for,grammar_for,parse_for_column; the per-driverquote_identcopies (pg lib.rs:512, mysql :513, sqlite :510, mssql :683, clickhouse via core quote_ident); clickhousebind_placeholders(lib.rs:612),literal(:728),escape_str(:777),escape_bind_markers(:787, per the review, with query_raw in W2-16),hex_encode. - App: export_dialog.rs:382, row_ops.rs:131 and import.rs:195 take the dialect from the session or lease (with W3-11 and W4-06). editor/mod.rs:675-698 takes the grammar from the new accessor.
- test-support
FakeDialect. - Tests move to
tests/integration/<topic>.rs(plan.md:1078); newsqlite/tests/dialect.rs. - Dependencies:
postgres-protocol 0.6.12(direct in the postgres driver; also added by W2-12/W3-09);clickhouse-types 0.1.3direct in clickhouse (already in the lock transitively); devinsta 1.48.0in each driver that keeps golden SQL (review). - Tests the spec requires: the per-engine golden tests (PG NULL->ParamType, enum/array/char/money casts, text-into-uuid BindError,
$1::tid/$2::oid, E''; MySQL_utf8mb4 X'6162635c',FROM_UNIXTIME(1743465600.500000), row-id None/BindError; SQLite rowid?; MSSQL NULL varbinary,[escape; ClickHouse CAST DateTime64 and unhex; row_value_after per engine; literal round trip through each sqlparser dialect). Integration:like_escape_matches_literal_underscore_and_percenton 6 engines,insensitive_filter_matches_mixed_case(MySQL utf8mb4_bin, SQLite),clickhouse_ilike_is_case_insensitive. Add the ClickHouse identifier drift test. - Depends on: W2-12 (PG connection builds the dialect), W3-06, W8-10 (landed, c3af59a tiberius pin); F1 for cast_target and ServerText.
- Stale or amended: "escape_bind_markers stays" -> delete (review W2-16); clickhouse-types "0.1.2" -> 0.1.3; PG escaping -> postgres_protocol::escape; ClickHouse escape -> crate Identifier or drift test;
exact_row_countsfor MSSQL false per C12 (EngineDialect has it true, engine_dialect.rs:88). - Size: about 2,500-3,000 changed lines (5 dialects with goldens about 1,800, integration about 350, deletions about 900).
W3-08 · Classify columns and defaults from each engine's structured catalog
Status: not started. The drivers already query pg_catalog, information_schema and system.columns (pg lib.rs:90-114, mysql lib.rs:99-146, clickhouse lib.rs:188, sqlite PRAGMA table_xinfo lib.rs:93), but classify through the string classifier classify_type_name (column_type_of at pg :766, mysql :662, sqlite :465, mssql :797, clickhouse :1100) and the normalize_*default helpers.
Remaining:
- New
catalog.rs+ typed row structs per driver:pg_typmod.rs,pg_column_row.rs;mysql_column_row.rs,mysql_column_type.rs(the deterministic scanner),column_type_scan_error.rs;sqlite_column_row.rs,runtime_version.rs;mssql_column_row.rs;clickhouse_column_row.rs. - Shared
classify_defaultper engine usingsql_syntax(needs publicliteral_of,strip_commentsandparse_single_expression, which W3-04 specified and onlyValidatedExpression.literalprovides now: parse_expression.rs:54-62). - Delete core
column/classify.rs(classify_type_name,has_dynamic_storage), coresql_syntax/mysql_column_type.rs(it uses sqlparser for modifiers, contrary to W3-08's review fix "no sqlparser for modifiers"), the drivers'column_type_ofandnormalize_*default(pg :525-560, mssql :631-680, sqlite :521). - ClickHouse uses
DataTypeNode::new(clickhouse-types-0.1.3 data_types.rs:118). Enum labels come back asHashMap<i16, String>(:85), so sort by value to keep declaration order (inferred requirement). - Dependencies:
clickhouse-types 0.1.3direct (review);postgres-types 0.2.14forType::from_oid(added by W2-12). - Tests: the PG, MySQL and other unit tests listed in the spec (16), plus integration tests on PG 13/18, MySQL 8.4/MariaDB 11.4, MSSQL, ClickHouse and SQLite (
catalog.rs). - Depends on: W2-11, W2-12, W3-04, W3-05 (F1 payloads are mandatory here), W3-06.
- Stale: "MySQL scanner in driver" vs the core
mysql_column_typethat landed. The "SQLite floor 3.37.0" stands (sqlx 0.9 already needs it per the plan's risk list). - Size: about 2,200-2,700 changed lines.
W3-18 · Table definitions with column attributes, inbound foreign keys and SQLite dependents
Status: not started. core/src/ddl/ holds only referential_action.rs (ddl/mod.rs:1-3). ForeignKeyInfo::references() exists (foreign_key_info.rs:24-30) as a client-side inbound test, but no driver lists inbound keys.
Remaining:
- New core files:
ddl/{table_definition, column_definition, column_attributes, collation_name, generated_column, identity_style, column_visibility, column_key_roles, primary_key_constraint, inbound_foreign_key, table_source, schema_object_source, unmodeled_attribute}.rs. Connection::fetch_table_definition(the CONN amendment, landing with W2-11).- Per-engine readers:
drivers/*/src/catalog/table_definition.rs,mysql/src/catalog/mysql_extra.rs,sqlite/src/catalog/dependents.rs. dependents.rs uses the sql_syntax token stream, so it needs a public tokenizer helper;source_offsetsdoes not exist yet. - Execution in the 5 connection.rs files (W2 lanes); a scripted FakeConnection.
- ClickHouse
is_in_sorting_key/is_in_partition_keyreplacesplit_key_expression(clickhouse lib.rs:563, review). ColumnInfo.commentdisposition (W3-05).- Dependencies: none beyond sqlparser 0.63.0 (
Tokenizer::new/with_unescape/tokenize_with_locationat sqlparser-0.63.0 src/tokenizer.rs:894, 932, 944). - Tests: unit
mysql_extra_parses_all_known_tokens_and_reports_unknown,sqlite_dependents_include_views_and_foreign_triggers_transitively,sqlite_dependents_ignore_name_inside_string_literal. Integration:fetch_table_definition_reports_pk_name_collation_comment(5 engines),..._lists_inbound_foreign_keys(PG, MySQL, MSSQL, SQLite), MSSQLdefault_constraint_name_is_read, SQLitedependents_and_source_are_read. - Depends on: W2-11, W3-05, W3-08, W4-01.
- Size: about 1,800-2,200 changed lines.
W3-19 · Foreign-key suspension in WriteBatch, SQLite write transactions, MySQL workspace sql_mode
Status: not started. WriteBatch has no foreign_keys (write_batch.rs:21-25). There is no BEGIN IMMEDIATE or foreign_key_check in the sqlite driver and no sql_mode statement in the mysql driver (greps empty).
Remaining:
- New core
foreign_key_mode.rs(ForeignKeyMode { Enforced, SuspendedThenVerified { checks } }) andforeign_key_check.rs. Add the field toWriteBatchandWriteBatch::new. - New
sqlite/src/foreign_key_verification.rs(multiset diff, WITHOUT ROWID by count). - SQLite write_batch protocol (in W2-14's connection.rs).
- New
mysql/src/workspace_session.rswithWORKSPACE_SQL_MODE_STATEMENT, run on each new workspace pool connection (W2-13). Unsupported { feature: "foreign key suspension" }in PG, MySQL, MSSQL and ClickHouse, and in the FakeConnection.- Tests
sqlite/tests/write_batch_foreign_keys.rsand MySQL integration. - Dependencies: none (sqlx 0.9.0
begin_with, already locked: sqlx-core 0.9.0). - Tests: units
introduced_violations_ignore_baseline_rows_across_rename,without_rowid_violations_compare_by_count; SQLite integration (4);non_sqlite_drivers_reject_suspension(×5); MySQL 8.4/MariaDB 11.4workspace_session_strips_no_backslash_escapes. - Depends on: W2-11, W3-05. Blocks W3-06 (plan_save fills Enforced), W3-13 and W3-16.
- Note: MySQL comment DDL already doubles backslashes as a defence (9e00c03). Once this sql_mode statement runs, that becomes deterministic rather than a guess.
- Size: about 800-950 changed lines.
W3-10 · Structure DDL on table definitions, typed defaults, structural validation, static SQL Server default drop
Status: not started. sql_ddl.rs is still v1. It takes driver_id, imports query::{ForeignKeyInfo, IndexInfo} (sql_ddl.rs:17-19), keeps validate_safe_type/validate_safe_default/FORBIDDEN_CONTROL_CHARS/MAX_TYPE_LEN/MAX_DEFAULT_LEN (:63-115) and the dynamic DECLARE ... EXEC(...) default drop (:347-360). It has gained post-plan comment DDL (:338-440) and RenameColumn (:268).
Remaining:
crates/core/src/sql_ddl.rs: builders take(driver_id, &dyn SqlDialect)and quote through the dialect.DraftColumn { original: Option<ColumnDefinition>, type_text: DraftType::{Catalog, Typed}, default: ColumnDefault };InvalidType/InvalidDefault/Literal(LiteralError).- Static
DROP CONSTRAINT [<name>]fromattributes.default_constraint_name;sp_renamewithN''literals; FK actions&'static [ReferentialAction];build_create_indexonIndexInfo.keysand predicate. - Carry the post-plan comment DDL onto the dialect: MSSQL extended-property literals become
N''throughdialect.literal. - App:
ui/structure_tab/{mod,columns}.rs,ui/structure_tab_dialogs.rs,services/structure_tracker.rs,ui/app/structure.rs. LoadTableDefinitionviasession.run(OperationKind::Metadata, ...). Default ComboRow (None / Value / SQL expression) + EntryRow. Validate Type throughdialect.parse_type. Error class, subtitle andProperty::Description. Take the dialect fromSession::current_dialect(). - Per-engine golden tests in each driver's
src/dialect.rs. - Dependencies: none.
- Tests: core
typed_type_is_validated_structurally,catalog_declaration_is_not_reparsed; 8 driver goldens; MSSQLalter_default_on_column_with_named_constraint(CI only, no arm64 image);#[gtk::test]default_combo_produces_literal_and_expression,invalid_expression_marks_error_and_blocks_save. - Depends on: W3-07, W3-08, W3-18, W4-06 (Session::run, OperationKind, current_dialect: app lane).
- Stale or amended: the line numbers (
sql_ddl.rs:289-300is now 347-360). Comment handling is not in the spec. The Relm4 question: StructureTab is a relm4SimpleComponent(structure_tab/mod.rs:495). Recommend leaving the gtk-rs subclass conversion to W3-12, which rewrites the same files at step 35, rather than converting twice. - Size: about 1,300-1,700 changed lines (core about 700, app about 400, goldens about 400).
W3-11 · App SQL generation, row keys and copied SQL on dialects and column kinds
Status: partial (small).
Already done (confirmed): browse_tab.rs has no classify_type/TypeKind, and parse_input_for_column calls edit::parse_literal_text (browse_tab.rs:2679-2689). filter_strip.rs maps operators_for(kind) (filter_strip.rs:73-74). RowKey::Persisted already holds ValueIdentity (KeyValue = ValueIdentity, change_tracker.rs:73).
Remaining:
crates/core/src/sql_copy/{mod,in_clause,render_in_clause,render_insert}.rs(new). Deleteexport/in_clause.rs.- Reconcile the post-plan
export/sql.rsrender_sql_insert(unqualifieddialect.quote_identifier(table)at sql.rs:25; writes/* omitted */ NULLon LiteralError) withsql_copy::render_insert(dialect, &TableRef, &[ColumnInfo], row). Share one renderer: qualified, generated columns skipped, the error policy per caller. services/change_tracker.rs:change_set() -> ChangeSetreplacesmaterialize(:524-694); delete the v1 imports (:35),RowKey::from_pk_values/KeyValue(:47-73); drafts asCellInput.ui/row_object.rs: addkey: RefCell<Option<RowKey>>.ui/app/row_ops.rs:plan_save+lease.write_batch; deletereports_rows_affected(:40-44),compute_concurrency_warning(:156-220) anddialect_for(:131); add the RowGuardFailed messages.ui/app/browse.rs:build_page+query_bound+split_pageinside the op; deletefetch_rows/query_params/v1 quoting (:55-104, :167-191).ui/grid.rs: deleteis_*_type/classify_editor_kind/column_accepts_empty(:742-889);CellEditorKindfrom ColumnKind; deletepk_col_indices(:81) and thetracked_keypk scan (:126-131); new render_in_clause signature (:1624).ui/browse_tab.rs: delete the pk scans (:368, :789, :919, :1203, :2105) andbuild_persisted_row_key(:2641).ui/error_text.rs: switch fromsql_dialect::BuildSqlError(:2-17) to the dml/filter/bind/literal errors.ui/app/mod.rs: deletecurrent_driver_id/driver_id()(:116, :617).ui/app/workspace_tabs.rs.- Post-plan CSV import
ui/app/import.rs:123-231: build aWriteBatch(RowChanges)ofGuardedStatement::unguardedinserts through the lease dialect andwrite_batch, replacingexecute_in_transaction+dialect_for. Not in any task spec; assign it here. ui/export_dialog.rs:382.- Integration tests: PG, MySQL,
sqlite/tests/row_identity.rs. - Dependencies: none.
- Tests: the app units (change_tracker ×3, row_ops ×2, filter_strip ×2, grid ×1), core sql_copy ×3, driver goldens ×5, integration ×7 (spec list).
- Depends on: W2-11, W3-06, W3-07, W3-09 (other lane), W3-18, W4-06 (app lane: Session, lease, OperationKind::Save).
- Stale: all the app line numbers in the spec; parse_input_for_column and filter_strip are already done; CSV import and SQL export are not mentioned.
- Size: about 2,000-2,500 changed lines.
W2-19 · Document driver contract v2, transports, trust, pooler requirements and per-engine behaviour (step 31)
Status: not started. docs/adding-drivers.md (239 lines) and docs/error-handling.md (131) describe v1. docs/drivers/ does not exist. No contract ADR exists.
Remaining:
- Rewrite
docs/adding-drivers.mdanddocs/error-handling.md; add a driver-layer section toARCHITECTURE.md. - New
docs/drivers/{postgresql,mysql,sqlite,sql-server,clickhouse}.mdand the ADR (number per F6). - Content must follow the review, not the plan:
- No forks.
- tokio-postgres, postgres-types and postgres-protocol from crates.io; deadpool-postgres with RecyclingMethod::Verified.
- Cursor row limit (DECLARE/FETCH, SAVEPOINT-wrapped) and per-statement transaction status with the SAVEPOINT probe (and its server-log ERROR line); COPY FROM STDIN refused; editor columns text-typed until rust-postgres#1358.
- MySQL over the OpenSSH forward socket with no relay; tiberius on the tiberius-rs main pin with upstream
cancel_query; ClickHousequery_rawand the Unix-socket connector. - SSH via system OpenSSH, with the Flatpak limits.
- Per-engine trust sets (platform-verifier for PG/ClickHouse, trust_cert_ca for MSSQL, sqlx roots + CA for MySQL; freedesktop-sdk missing resize cursor #1905).
- ARCHITECTURE "tablepro-net, Transport, relay, trust" loses "relay".
- Update
docs/decisions/README.md. - Dependencies: none.
- Tests: none. lychee offline link check (review W9-04 amendment) where configured.
- Depends on: W2-12..W2-17.
- Stale: forks, relay, PgBouncer text that mentions the text portal; "ADR 0010 on the tokio-postgres move, the forks" becomes "no forks, upstream pins and removal gates" (review W9-18 also lists that ADR).
- Size: about 900-1,300 lines of Markdown.
W3-17 · Export values at column precision with exact JSON numbers (step 32)
Status: done in substance (commit 92aea71).
export/{mod,csv_options,value_text,csv,tsv,json,json_field_names,markdown}.rsexist, plus post-plan html/xml/xlsx/sql.value_text(&Value, &ColumnType)andcell_text, with fraction and scale from the column (value_text.rs tests).- JSON through
serialize_mapandRawValue::from_string(json.rs:2, 20, 64, 93);row_to_json(:119); RFC 3339 timestamps. - The workspace
serde_jsonhasraw_value(Cargo.toml:29). - App uses the new signatures: grid.rs:1563-1586 and :1657, browse_tab.rs:2367, export_dialog.rs:376.
Residual:
- (a)
value_textderives digits by parsing the type name (column_type.rs:95-135). After F1 it must readColumnKind::{Time,Timestamp}.fractional_digitsandDecimal{scale}. - (b) Public
export::value_to_textstill has non-export callers (grid.rs:2131 display text, drivers' bind fallbacks, sql_ddl.rs:169, engine_dialect.rs:127). Make it crate-private once W3-07, W3-09 and W3-10 remove them. - (c) Spec test names differ (
json_emits_raw_number_tokens_for_u64_max_and_decimal_40_20covers the same ground asa_number_wider_than_a_double_keeps_its_digits;csv_formula_rule_unchanged_after_splitshould be checked). - Dependencies: none.
- Depends on: W3-11 (plan). In practice it landed before the switch, so only (a) and (b) follow.
- Size: about 60-150 changed lines.
W3-12 · Generate DDL through per-engine DdlDialect plans (step 35)
Status: not started.
Remaining:
- Delete
core/src/sql_ddl.rs(2,609 lines incl. tests). - New
core/src/ddl/{ddl_dialect,ddl_capabilities,column_capabilities,permission,refusal_reason,table_draft,draft_column,draft_type,draft_problem,table_changes,column_alteration,changed_attributes,diff_table,ddl_plan,drop_table_options,build_ddl_error,ddl_operation}.rs. - New
drivers/*/src/ddl.rs(5), with the golden tests moved there fromdialect.rs;SqlDialect::ddl()returns each. - App:
services/structure_tracker.rs(PendingStructure),ui/structure_tab/{mod,columns,fks,indexes}.rs,structure_tab_dialogs.rs,ui/app/{structure,mod}.rs. Capabilities drive sensitivity and tooltips; New Table hides withoutcreate_table(app/mod.rs sidebar "+"); Show Create Table usescreate_table_script. Deletedriver_types/default_type_for/driver_can_*(columns.rs:19-115, fks.rs:11) andsupported_fk_actions(dialogs :197). - This is where StructureTab should become a gtk-rs subclass with CompositeTemplate (review Relm4 amendment; F5).
- Carry the post-plan comment and rename behaviour into
TableChanges. W3-14 owns rename ordering, so W3-12 must at least not regress 3bcd8df's rename (renames first) and 9e00c03's COMMENT handling. - Docs:
docs/adding-drivers.mdDdlDialect section; ADR 0011 (free). W9-09's DDL conformance suite ships in the same commit. - Dependencies: none.
- Tests: core ×3, per-engine goldens ×7 groups,
#[gtk::test]×3, structure tracker ×1 (spec list). - Depends on: W3-10, W3-11, W3-18, W4-06, W4-07, W4-10 (app lane, steps 31/34).
- Stale: "NoChange" deletion and rename land in W3-14, but RenameColumn already exists in v1. Write
TableChangeswithrenamed_columnsfrom the start, or rename becomes a regression between steps 35 and 38 (inferred). - Size: about 3,200-4,000 changed lines plus about 2,600 deleted; plus about 1,500 if the Relm4 conversion of the structure tab folds in.
W3-13 · Preserve every column attribute when MySQL, PostgreSQL and SQL Server redefine a column (step 36)
Status: not started. The post-plan comment work already restates MySQL COMMENT in MODIFY (9e00c03 message; sql_ddl.rs:403-440). That is a partial precursor in v1 shape.
Remaining:
drivers/mysql/src/ddl.rs+ddl/mysql_column_definition.rs(render(original, draft): CHARACTER SET, COLLATE, GENERATED, NULL, DEFAULT, ON UPDATE, AUTO_INCREMENT, INVISIBLE, SRID, COMMENT with'and\doubled).- Unmodeled attributes or an odd collation name ->
Refused(UnmodeledAttribute). drivers/postgres/src/ddl.rs: TYPE ... COLLATE ... USING.drivers/mssql/src/ddl.rs: restate COLLATE, NULL and SPARSE.- Integration tests in the three drivers'
tests/integration/. - Dependencies: none.
- Tests: 5 units, 5 integration (spec list). The MSSQL ones run only in CI.
- Depends on: W3-12, W3-18, W3-19 (sql_mode), and F1 kinds for textual, Spatial and Timestamp gating.
- Size: about 600-800 changed lines.
W3-14 · Rename columns, change primary keys and toggle auto-increment (step 38)
Status: partial (rename only, v1 shape). StructureOp::RenameColumn with its own phase before alters (sql_ddl.rs:268, 1161, 1281; commit 3bcd8df). There is no rename ordering for swaps, no PK change and no auto-increment toggle.
Remaining:
- New
core/src/ddl/{column_rename,order_renames,primary_key_change}.rs; edittable_changes.rs,diff_table.rs,changed_attributes.rs. - The 5
ddl.rsfiles: PG RENAME first, PK change, identity on withsetval(pg_get_serial_sequence(...)), identity off; MySQL RENAME/CHANGE, PK; MSSQLsp_rename N'[s].[t].[c]'with]doubled, PK order; SQLite native RENAME; ClickHouse RENAME for non-key columns. - Delete
NoChange. - App:
ui/structure_tab/{columns,mod}.rs(Name entry editable by capability; PK on forces NOT NULL). sqlite/tests/ddl.rs(new) + integration tests.- Dependencies: dev
proptest 1.11already in the workspace (Cargo.toml:49) fornon_empty_changes_never_plan_empty. - Tests: core ×6, goldens ×5, integration ×6+,
#[gtk::test]×1 (spec list). - Depends on: W3-13.
- Size: about 1,000-1,300 changed lines.
W3-15 · Drop and re-add SQL Server default constraints around column type changes (step 39)
Status: not started. The dynamic DECLARE/EXEC drop still exists (sql_ddl.rs:347-360). W3-10 makes it static and W3-15 adds the re-add ordering.
Remaining:
crates/drivers/mssql/src/ddl.rs: per-column drop, ALTER, re-add (ADD CONSTRAINT [name] DEFAULT (...) FOR [c], or unnamed when there was no name); global ordering after FK/PK drops and before PK/FK adds.crates/drivers/mssql/tests/integration/....- Dependencies: none.
- Tests: units ×5, SQL Server 2022 integration ×3. CI only: no arm64 image (problem.md).
- Depends on: W3-14.
- Size: about 250-350 changed lines.
3. Implementation order (this lane)
Inside the step-30 stacked branch (one squashed refactor(linux)! commit at the end):
- W3-05 reconciliation (F1): ColumnKind, ColumnType and CatalogType to FB-C5/C6; EditContext; decide
ColumnInfo.comment. Everything below matches on these. - W3-19 core types (ForeignKeyMode, ForeignKeyCheck,
WriteBatch.foreign_keys). Tiny, and W3-06's plan_save needs it. - W2-11 core contract: Connection v2 (incl.
stream_read,fetch_table_definitionsignature), DatabaseDriver v2, ConnectOptions v2 family, AuthMode move, v1 deletions, missing OwnedCalls and ledger tests; test-support fakes,FakeDialectstub and the conformance module. - W3-06:
ddl()and grammar accessors, StatementBuilder unification (F3), filter split +validate_raw_predicate, browse build_page/build_count/paging_mode/build_export/PageSpec.layout, plan_save, edit API (XW9), xmin part (F7). Delete sql_dialect.rs at the end of the branch. - W3-18 core model (TableDefinition and friends), so the drivers can implement
fetch_table_definitionwhile they are rewritten. - W3-07 dialects, W3-08 catalogs, W3-18 readers, W3-19 driver protocol: one driver at a time, alongside that driver's W2-1x rewrite (PostgreSQL first, because W3-07 and W3-08 depend on W2-12; then SQLite, MySQL, ClickHouse, SQL Server). Delete engine_dialect.rs and classify.rs when the last driver switches.
- W3-11 then W3-10 on top of W4-06's Session, lease and
current_dialect. - Tip gates: clippy
-D warnings, the unit and gtk suites, the docker matrix (MSSQL in CI), conformance.
After the switch:
- W2-19 (step 31): docs and ADR.
- W3-17 residual (step 32): kind-based precision;
value_to_textmade private. - W3-12 (step 35, with W9-09; StructureTab to a gtk-rs subclass here).
- W3-13 (step 36). 13. W3-14 (step 38). 14. W3-15 (step 39).
4. Open questions and risks that could block it
- FB-C5/C6 vs the landed ColumnKind/ColumnType (F1). Without a decision, W3-07, W3-08, W3-10, W3-13 and W3-17 cannot meet their specs. If the owner keeps the flat shape, those specs must be rewritten (no
cast_targetmeans no$n::<cast>for ServerText, and noMoney,SpatialorBool(Integer)). - Dialect location and grammar source (F2). Moving the dialects into the driver crates means the editor needs the grammar before a connection exists. That needs a
DriverCapabilitiesfield or aSqlDialect::grammar(), plus a fallback for the picker. - Hidden scope from the conflict list (F4):
stream_read,build_export,PageSpec.layout,paging_mode, COUNT_BIG and the four edit functions sit in no task spec body, and the W5 lane (BrowsePager, export streaming) depends on them. - Post-plan features on the v1 API: CSV import, SQL INSERT export, XLSX, column comments and RenameColumn. Their behaviour must survive the switch, and no task lists their files. Assigned here to W3-11 (import, SQL export), W3-05/W3-18 (comment) and W3-12/W3-14 (rename).
- xmin in EngineRowId (F7): required by details.md:8591 and absent from the code and from W3-06's own spec. The owner should confirm it.
- ADR numbers (F6): 0013 is taken. Assign W2-19's ADR before step 31.
- Relm4: the structure tab and browse tab are Relm4 components. Folding the gtk-rs migration into W3-10 and W3-11 would double the step-30 diff. Proposal: the structure tab converts in W3-12 (step 35), and browse/grid stay with the app lane's own rewrite steps. Coordinate with the app lane.
- Cross-lane gates: W3-10 and W3-11 need W4-06 (
Session::run,OperationKind,current_dialect, leases). W3-07 and W3-08 need the W2-12 PostgreSQL driver skeleton (connect_physicalbuilds the dialect fromserver_version_num). W3-12 needs W4-07 and W4-10. - Testing constraints: SQL Server has no arm64 image, so the W3-07, W3-08, W3-10, W3-13, W3-14 and W3-15 MSSQL integration tests run only in CI. Golden SQL via insta adds a new dev-dependency (1.48.0, because 1.49.0 is under two weeks old).
- rust_decimal removal needs W2-12 (pg
to_pg_decimal), W2-13 and W2-15 to finish first, and then the deny.toml ban (W8 lane). - One atomic commit: this lane alone is about 15,000-19,000 changed lines inside step 30 (W2-11 about 2.1k, W3-05 about 1.1k, W3-06 about 2.5k, W3-07 about 2.8k, W3-08 about 2.5k, W3-18 about 2.0k, W3-19 about 0.9k, W3-10 about 1.5k, W3-11 about 2.3k). Bisect and review risk is high; the stacked-branch mitigation (plan.md:1084) is mandatory.
5. New and removed dependencies (this lane)
| Change | Crate | Version | Where | Source |
|---|---|---|---|---|
| add (driver) | postgres-protocol | 0.6.12 | drivers/postgres (shared with W2-12/W3-09) | docs.rs postgres-protocol 0.6.12 escape module |
| add direct | clickhouse-types | 0.1.3 | drivers/clickhouse | Cargo.lock; registry clickhouse-types-0.1.3 data_types.rs:34,118 |
| add dev | insta | 1.48.0 | each driver with golden SQL (and core if desired) | crates.io API: 1.48.0 2026-06-11, 1.49.0 2026-10-03 |
| keep | num-bigint | 0.4.8 | core | Cargo.lock; num-bigint-0.4.8 |
| keep | sqlparser | 0.63.0 | core | Cargo.lock |
| keep | tokio 1.53.1, tokio-util 0.7.19 | core | Cargo.lock | |
| remove | rust_decimal | 1 | workspace, sqlx/tiberius features, app, drivers | workspace Cargo.toml:38-42 |
| remove (code) | core sql_dialect.rs, sql_ddl.rs, read_only.rs, query.rs, dialect/engine_dialect.rs, column/classify.rs, export/in_clause.rs, sql_syntax/mysql_column_type.rs |
Analysis file
|
| Piece | Where |
|---|---|
Transport trait |
core/src/transport.rs:14-26: open() -> Box<dyn TransportStream>, explain_closed_stream() |
TransportStream |
core/src/transport.rs:9: AsyncRead + AsyncWrite + Send + Unpin + 'static |
| Direct TCP transport | net/src/direct_tcp.rs:40-59, sequential lookup plus dial; net/src/socket_options.rs:7-15: nodelay, keepalive, TCP_USER_TIMEOUT |
| OpenSSH forward transport | ssh/src/forward.rs:88-122: TransportRoute::UnixSocket, open() = UnixStream::connect(socket) under connect_timeout |
| TLS client config | net/src/tls/client_config.rs:8-38: build_client_config(&TlsConfig) -> Option<Arc<ClientConfig>>, platform verifier with extra roots, Require = UnverifiedServerCertVerifier, client identity |
| TLS classification | net/src/tls/classify.rs:5-36 |
OwnedCalls::run_read and run_write |
core/src/owned_calls.rs:47,131; release and discard logic at 307-322; abandon flag set at 298 |
EngineStop and CallResource |
core/src/engine_stop.rs:33,50 |
WriteLedger |
core/src/write_ledger.rs |
EditorSession trait |
core/src/editor_session.rs:55-75 |
ResultEvent, including ColumnsResolved |
core/src/result_event.rs:37-60 |
DriverCapabilities |
core/src/capabilities.rs:60-73 |
CallOptions |
core/src/call_options.rs:12,17; the deadline is a tokio::time::Instant, not the plan's Duration |
ServerInfo |
core/src/server_info.rs:7-12: {product, version, major, minor}, not FC-C8's version_num/current_database/default_schema |
Statement, BoundParam, ParamType |
core/src/statement/*; ParamType has no Bits and no OID (param_type.rs:10-35) |
| Generic dialect | core/src/dialect/engine_dialect.rs: dialect_for("postgres"), ctid and tableoid row ids; there is no PgDialect yet |
| Script planner | core/src/sql_syntax/script/*: ScriptPlan::build(text, SqlGrammar::PostgreSql, LexicalSettings { backslash_escapes }) (script_plan.rs:23, lexical_settings.rs:5-14), with corpus tests for dollar quotes, a$b$, E'' strings, standard_conforming_strings off, nested comments, BEGIN ATOMIC and CASE…END (core/tests/script_corpus.rs:31-147) |
| PostgreSQL fixtures | PostgresFixture (postgres:17-alpine, fixture_image.rs), HbaMode::{Password, HostSslOnly, ClientCertificate} (hba_mode.rs:2-26) |
| PgBouncer fixture | PgBouncerFixture (edoburu/pgbouncer v1.25.2-p0, max_prepared_statements = 0, ignore_startup_parameters = extra_float_digits,options; pgbouncer_fixture.rs:52-77) |
| Test PKI | TestPki, RSA-SHA256 only (test_pki.rs:26,33); PkiFile::UnrelatedCa exists (pki_file.rs:4) |
| CI | .github/workflows/build-linux.yml:157-200 runs cargo nextest run -p tablepro-driver-postgres --run-ignored only per package, so new tests/integration/* modules need no workflow edit; linux/.config/nextest.toml:2,10 serialises the package |
Missing and needed: core ConnectOptions v2, ConnectTarget and NetworkTarget (grep finds none in core); CONN v2 Connection; FaultTransport (not in crates/test-support/src); the W9-07 conformance module; xmin in EngineRowIdPart (core/src/meta/row_identity.rs:24-38 has only tableoid, ctid and rowid). (confirmed)
2. Crate facts, checked against crates.io and docs.rs
| Crate | Version (exists, not yanked) | Source |
|---|---|---|
| tokio-postgres | 0.7.18 (2026-06-12, rust_version 1.85), newest | crates.io/api/v1/crates/tokio-postgres/versions |
| postgres-types | 0.2.14 (2026-06-12), newest | crates.io/api/v1/crates/postgres-types/versions |
| postgres-protocol | 0.6.12 (2026-06-12), newest | crates.io/api/v1/crates/postgres-protocol/versions |
| deadpool-postgres | 0.14.2 (2026-08-26, rust_version 1.85), newest | crates.io/api/v1/crates/deadpool-postgres/versions |
| deadpool | 0.13.1 (2026-08-26), newest; 0.12.4 is yanked | crates.io/api/v1/crates/deadpool/versions |
| tokio-postgres-rustls | 0.14.0 (2026-05-21), newest | crates.io/api/v1/crates/tokio-postgres-rustls/versions |
API facts (all confirmed at the URL given):
Config::connect_raw<S, T>(&self, stream: S, tls: T) -> Result<(Client, Connection<S, T::Stream>), Error>, whereS: AsyncRead + AsyncWrite + UnpinandT: TlsConnect<S>. Source: docs.rstokio_postgres/config/struct.Config.html.- The doc says every setting other than user, password, dbname, options and application_name is ignored.
- The source still reads
config.ssl_modeandssl_negotiation(connect_raw.rs:95-96,119-120) andconfig.channel_binding(:219,:274,:291-292), so setting them onConfigworks. BackendKeyDatais kept (:349-352), and the startup ReadyForQuery status is dropped (:362).
Client(docs.rsstruct.Client.html) hassimple_query_raw,batch_execute,query_typed_raw,execute_typed,cancel_token,is_closed,check_connectionandprepare. It has no transaction-status accessor.SimpleColumnexposes onlyname(), and itsnewispub(crate)(simple_query.rs:18-31).- In
SimpleQueryStream,ReadyForQuery(_) => Nonedrops the status. CopyIn, CopyOut and CopyData fall through toErr(unexpected_message)(simple_query.rs:85-112). SimpleQueryMessagehas Row, CommandComplete and RowDescription and is#[non_exhaustive](docs.rsenum.SimpleQueryMessage.html).Responses::poll_nextturns an ErrorResponse intoErr(Error::db). The stream keeps going to ReadyForQuery (client.rs:41-60).cancel_token()copies ssl_mode, ssl_negotiation, process_id and secret_key (client.rs:721-730).CancelToken::cancel_query_raw<S, T>(&self, stream: S, tls: T)is Clone, Send and Sync (docs.rsstruct.CancelToken.html).query_typedsends Parse("") Bind("") Describe Execute(0) Sync as one request (query.rs:75-86). It callsget_typefor every column (:102).get_typereturns early forType::from_oidbuilt-ins. For anything else it prepares a typeinfo statement nameds{n}from a process-wide counter (prepare.rs:59,66,135-144,195-210). This is the PgBouncer hazard behind the read_form rule.Connection::parameter(connection.rs:296-299) andpoll_message(:320-338) exist.- Notices are
AsyncMessage::Notice(:112-114). - ParameterStatus updates
parameters(:124-129). - The
Futureimpl only logs notices throughlog(:348-355).
- Notices are
tokio_postgres::Errorhas no public constructor and noFromimpls. Its methods areas_db_error,code,is_closedandinto_source(docs.rserror/struct.Error.html).ToSql::encode_format(&self, &Type) -> Formatis a provided method, so text-format parameters are possible (docs.rs postgres-typestrait.ToSql.html).postgres_types::Date<T>andTimestamp<T>are{PosInfinity, NegInfinity, Value(T)}(docs.rsenum.Date.html).- In postgres-types
chrono_04.rs, NaiveDateTime and NaiveDate usechecked_add_signed(lines 21, 119). NaiveTime uses+(line 142), so 24:00:00 wraps to midnight. Source: raw.githubusercontent.com rust-postgres tag postgres-types-v0.2.14. postgres_protocol::typeshas array, range, inet, macaddr, varbit, point, box, path, hstore, ltree, lquery, ltxtquery, lsn, date, time, timestamp, uuid and scalar*_from_sql/*_to_sql. It has no numeric and no interval (docs.rspostgres_protocol/types/index.html).- deadpool-postgres 0.14.2 (docs.rs
src/deadpool_postgres/lib.rs.html,config.rs.html,trait.Connect.html):pub trait Connect: Sync + Send { fn connect(&self, &PgConfig) -> Pin<Box<dyn Future<Output = Result<(PgClient, JoinHandle<()>), Error>> + Send + '_>> }.Manager::from_connect(pg_config, impl Connect + 'static, ManagerConfig)(lib.rs:118-127).recyclechecksis_closed(), then runssimple_query(recycling_method.query())(lib.rs:155-170). It cannot see the transaction status.RecyclingMethod::{Fast (default), Verified => "", Clean => DISCARD…, Custom(String)}(config.rs:308-373).ClientWrapperderefs toClient. ItsDropaborts the connection task (lib.rs:231-313), so a taken object closes without sending Terminate.- Normal dependencies: deadpool
^0.13.1[managed] and tokio-postgres^0.7.14with default features on non-wasm (crates.io/deadpool-postgres/0.14.2/dependencies).
- deadpool 0.13.1:
Object::take(this) -> M::Type(docs.rsmanaged/struct.Object.html).Pool::timeout_get(&Timeouts)(managed/struct.Pool.html).PoolError::{Timeout(TimeoutType), Backend(E), Closed, NoRuntimeSpecified, PostCreateHook}(managed/enum.PoolError.html).- PoolBuilder has
max_size,wait_timeout,create_timeout,recycle_timeout,runtime,pre_recycle,post_recycleandpost_create(managed/struct.PoolBuilder.html). - deadpool-postgres re-exports
Pool,PoolError,Object,Timeouts,RuntimeandHookas aliases (docs.rs index), so no direct deadpool dependency is needed. That last point is inferred.
- tokio-postgres-rustls 0.14.0 (docs.rs
src/tokio_postgres_rustls/lib.rs.html):MakeRustlsConnect::new(config: ClientConfig)takes the config by value, not anArc(L199).MakeTlsConnect<S>requiresS: AsyncRead + AsyncWrite + Unpin + Send + 'static(L248-261).RustlsConnectsits in a private module. It is reachable only as<MakeRustlsConnect as MakeTlsConnect<S>>::TlsConnectthroughmake_tls_connect(&mut self, hostname)(L54-77, L256).- Channel binding maps SHA-1, SHA-256 and ECDSA-SHA256 to SHA-256, SHA-384 to SHA-384, SHA-512 to SHA-512, and everything else (Ed25519) to none (L109-142). md5WithRSA, ecdsa-with-SHA1 and ecdsa-with-SHA512 are missing, as the review says.
- Dependencies: rustls
^0.23with no default features, sha2^0.11, x509-cert^0.2, tokio-rustls^0.26, tokio-postgres^0.7[runtime] (crates.io/tokio-postgres-rustls/0.14.0/dependencies).
- tokio-postgres 0.7.18 has 18 normal dependencies, including socket2 0.6 [all], tokio-util 0.7 [codec], rand 0.10 and whoami 2. Features: default = runtime; array-impls, with-chrono-0_4, with-uuid-1 and with-serde_json-1 forward to postgres-types (crates.io
/0.7.18/dependencies, docs.rscrate/tokio-postgres/0.7.18/features). Nothing there pulls ring or OpenSSL (inferred from the list).
3. How the driver takes a transport stream instead of a host and port
- Every physical connection is
let stream = target.transport.open().await?(Box<dyn TransportStream>,core/src/transport.rs:21), followed byconfig.connect_raw(stream, tls). This holds for workspace pool members, editor sessions, the standalone probe, stream_read and every CancelRequest.- The stream is TCP from
DirectTcpTransport, already tuned with nodelay, keepalive and TCP_USER_TIMEOUT bynet/src/socket_options.rs:7-15. - Or it is the OpenSSH forward's
UnixStreamfromSshForwardTransport::open(ssh/src/forward.rs:103-122). Box<dyn TransportStream>meetsMakeTlsConnect'sUnpin + Send + 'staticbound, becauseTransportStreamrequires those (confirmed,transport.rs:9).
- The stream is TCP from
- tokio-postgres
Config::host,port,connect_timeout,keepalivesandtcp_user_timeoutare never set. connect_timeout and socket options belong to the transport.login_timeoutwrapsconnect_raw(the transportopen()already hasconnect_timeout). - The TLS server name is
NetworkTarget::tls_server_name()(tls.server_name, elsetransport.service_endpoint().host()). For an SSH Unix-socket forward this is the database host, not the socket path. It is passed tomake_tls_connect(hostname). (inferred from FC-C1 and the tokio-postgres-rustls API) - Cancel must use
CancelToken::cancel_query_raw(transport.open().await?, fresh_tls_connector), nevercancel_query.connect_rawhas no socket config to redial with, socancel_querycannot work. (inferred:socket_configis filled only byConfig::connect,client.rs:723-724.) - On an I/O or closed error, ask
transport.explain_closed_stream()(transport.rs:25; implemented for SSH atforward.rs:128-143) before mapping toConnectionLost, so a dead ssh master is reported as an SSH failure. - The app today still builds v1
ConnectOptions { host, port }through the russh tunnel (app/src/services/connection_service.rs:43,207,russh_tunnelimports). TurningRequestTargetintoNetworkTarget { transport: Arc<dyn Transport>, … }is W1-11, W2-11 and W4-06 work, not this lane. The PostgreSQL driver only readsConnectTarget::Network.
4. W2-12 · PostgreSQL driver on tokio-postgres
Status: not started. The crate is the v1 sqlx driver (section 1). These pieces can be carried over:
- the SQLSTATE table and the diagnostics extraction (
lib.rs:624-656, rewritten againsttokio_postgres::error::DbError); normalize_pg_defaultand the related helpers (525-571), if W3-08's catalog still wants them;- the binary NUMERIC renderer (
856-911, which moves under W3-09).
Every other prerequisite type exists in core (section 1). The CONN v2 trait and ConnectOptions v2 do not.
Amended design (the review wins over the spec)
-
Crates. crates.io tokio-postgres 0.7.18, postgres-types 0.2.14 and postgres-protocol 0.6.12. No
[patch]and no fork (review "Forks"; amendment FC-D1/W2-01..03). Nothing changes indeny.tomlallow-git, which lists only tiberius today (deny.toml:55). -
Pool (amendment FC-D18/W2-12). Use
deadpool_postgres::Manager::from_connect(Config, PgConnect, ManagerConfig { recycling_method: RecyclingMethod::Verified })andPool::builder(m).max_size(WORKSPACE_POOL_MAX_SIZE = 4).wait_timeout(pool_wait).create_timeout(connect + login).recycle_timeout(probe_timeout).runtime(Runtime::Tokio1).PgConnect::connectrunstransport.open(), thenconnect_raw(stream, PgTls), then spawns theConnectionon the connection'sTaskTracker, and returns(Client, JoinHandle).PgPooledandpg_manager.rsare deleted.- A thin
PgCall { object: deadpool_postgres::Object, abandon: Arc<AtomicBool> }still implements coreCallResource, because that trait is core's (engine_stop.rs:50). discardisObject::take(object)followed by drop, which aborts the connection task.PgCall's ownDropmust also take the object whenabandonis set. When the grace runs out,OwnedCallssets the flag (owned_calls.rs:298) and then drops the work future, which still holds the object. Without the take, deadpool would recycle it.Verifiedonly runs""(config.rs:369), which succeeds even inside a half-run write transaction. (inferred from the confirmed code paths)- Flights with no COMMIT or ROLLBACK acknowledgement are taken, not returned.
Timeout(Wait)maps to Busy.Timeout(Create)keeps the phase reached.
-
TLS (amendment FC-D23/W2-10). A small
PgTlswrapper implementstokio_postgres::tls::TlsConnect<Box<dyn TransportStream>>.- It delegates to the
MakeRustlsConnect::new((*arc_config).clone()).make_tls_connect(server_name)connector. - It records
handshake_startedwhenconnectis called, which only happens after the server answers 'S'. - For
TlsMode::DisabletheConfiggetsSslMode::Disable, andconnectis never called. The wrapper type still has to exist (anOptioninner, orNoTlsin an enum stream). - Require and VerifyFull become
SslMode::Require. Verification lives in theClientConfigfromtablepro_net::tls::build_client_config(client_config.rs:8-38). channel_binding(Prefer)is set onConfig.- No x509-cert or
channel_binding.rsof our own; tokio-postgres-rustls brings x509-cert 0.2 transitively. - Placement is an open question (section 9). I recommend the driver crate (
src/tls.rs) so tablepro-net stays engine-agnostic. The W2-10 spec put the connector in net.
- It delegates to the
-
App SQL flights (FC-D21, unchanged).
futures::join!(batch_execute("START TRANSACTION READ ONLY; SET LOCAL statement_timeout = <ms>; SET LOCAL lock_timeout = <ms>"), query_typed_raw/execute_typed, batch_execute("COMMIT")).- The milliseconds come from
deadline.saturating_duration_since(now), becauseCallOptionscarries anInstant(call_options.rs:12). - New rule: never render 0 for a call deadline, because
statement_timeout = 0disables the bound in PostgreSQL. Clamp to at least 1 ms, or returnTimeoutbefore sending. (inferred, PostgreSQL semantics) - No
Client::prepareanywhere. - The 10,000-row enum/citext flight test stays as the read_form guard (FC-D18 amendment). The
prepare_typedmitigation is deleted.
- The milliseconds come from
-
Editor sessions (amendments FC-D1/FC-D19/FC-C24). The psql model stays: split client-side and send one statement per message.
- Splitting. Use core
ScriptPlan::build(text, SqlGrammar::PostgreSql, LexicalSettings { backslash_escapes: standard_conforming_strings == "off" }). Read the setting from the actor-ownedConnection::parameter("standard_conforming_strings")before each statement. The driver's ownscript_split.rsis deleted under amendment W6-05/FC-C23 ("one core ScriptLexer/ScriptPlan"). Core already passes the W2-12 splitter cases (core/tests/script_corpus.rs:31-147). - Exact transaction status, with no fork. A
transaction_tracker.rsderives Idle, InTransaction or Failed from the statement class plus success or failure:- BEGIN or START moves to InTransaction.
- Any error in a block moves to Failed.
- COMMIT, END or ROLLBACK moves to Idle, and COMMIT in a failed block is a rollback.
- ROLLBACK TO SAVEPOINT in a failed block moves back to InTransaction.
- COMMIT AND CHAIN and ROLLBACK AND CHAIN stay in InTransaction.
- PREPARE TRANSACTION moves to Idle.
Undeterminedcovers CALL, DO, multi-statement text and anything unclassified. It triggers a probe:SAVEPOINT tablepro_probethenRELEASE. Success means InTransaction, 25P01 means Idle and 25P02 means Failed.- These transitions are inferred from the review's reading of xact.c and utility.c, and need tests.
- The "Unknown" interim is rejected, because read-only enforcement depends on exact status.
- Row limit. The SQL cursor flight replaces
query_text_portal. It applies only to row sources: SELECT, VALUES, TABLE, WITH and a leading(.- Idle:
BEGIN,DECLARE tablepro_cursor NO SCROLL CURSOR FOR\n<sql>,FETCH FORWARD <limit+1> FROM tablepro_cursor,CLOSE,COMMIT. - InTransaction:
SAVEPOINT, DECLARE, FETCH, CLOSE, thenRELEASE, orROLLBACK TO SAVEPOINTon refusal. - DECLARE and FETCH go as separate messages, so a trailing
--comment cannot swallow the FETCH. - Error positions are corrected by subtracting the prefix length.
- A DECLARE refusal with 0A000 (data-modifying WITH) or 42601 (SELECT INTO) falls back to
simple_query_raw. That is safe because DECLARE has not executed anything. - Failed sessions and transaction-control statements use
simple_query_raw. Rows past the limit are counted but not decoded (ReadPastLimit).
- Idle:
- Read-only per transaction, unchanged from the spec except that it composes with the cursor:
- Idle plus a row source: the read-only wrapper and the cursor are one flight,
START TRANSACTION READ ONLY…COMMIT. - Idle plus TransactionStart: run the statement, then
SET TRANSACTION READ ONLY. - TwoPhase:
ReadOnly(ClientGuard). - No session SET is ever sent. Deadlines go through CancelRequest.
- Idle plus a row source: the read-only wrapper and the cursor are one flight,
- COPY.
COPY … FROM STDINis refused before sending. The upstream stream yieldsErr(unexpected_message)on CopyInResponse (simple_query.rs:112), and the server would wait for CopyData forever. COPY TO STDOUT output cannot be displayed until upstream exposes CopyData. Whether to refuse it or drain it with a notice is an open question. - Column types. Editor result columns are text-typed until rust-postgres Canceling connection does not actually cancel #1358 ships, because
SimpleColumnexposes onlyname()(simple_query.rs:18-31).- The spec's whole type-name path is deleted: the
Weak<PgConnectionInner>pg_type lookup,type_names.rs, and ColumnsResolved for this engine. Core'sResultEvent::ColumnsResolved(result_event.rs:43) stays unused by PostgreSQL. - Values are
Value::TextorValue::NullfromSimpleQueryRow.
- The spec's whole type-name path is deleted: the
- Actor. The actor owns
Client,ConnectionandArc<dyn Transport>. It pollsConnection::poll_messageitself, so notices becomeResultEvent::Notice.
- Splitting. Use core
-
stream_read (XW1, step-30 addition to CONN, details 12254-12264). It runs on a standalone physical connection, never a pool slot.
ReadStatement::GeneratedusesSTART TRANSACTION READ ONLY; SET LOCAL statement_timeout = 0, thenquery_typed_rawwith RawCell decode as rows arrive.ReadStatement::Usermust usesimple_query_raw(text), notquery_typed_raw. Otherwise non-builtin result OIDs make tokio-postgres prepare its named typeinfo statement (prepare.rs:144), which breaks the pooler rule. (inferred)
-
Metadata, write_batch and resolve_commit follow the spec unchanged: pg_namespace and pg_class kinds,
pg_get_indexdefkeys,pg_index_column_has_property, the PK, unique-NOT-NULL, ctid and tableoid ladder,pg_relation_size / block_size,pg_current_xact_id()::text/txid_current(), andpg_xact_status.- The ctid identities need
xmin. CoreEngineRowIdParthas no xmin today (row_identity.rs:33-38), which is the META/DIALECT extension owned by FC and W3. ServerInfomust be filled as{product: "PostgreSQL", version, major, minor}(server_info.rs:7-12).server_version_numis not a ParameterStatus, so it costs oneSHOW server_version_num. (inferred)
- The ctid identities need
Remaining work: files
crates/drivers/postgres/:
| Action | Path | Notes |
|---|---|---|
| change | Cargo.toml |
See the dependency list below |
| rewrite | src/lib.rs |
Module declarations and pub use driver::PgDriver; pub const WORKSPACE_POOL_MAX_SIZE: usize = 4; (W9-07 "part 2 of 2") |
| new | src/driver.rs |
DatabaseDriver v2: capabilities(), connect(options, cancel) |
| new | src/capabilities.rs |
Network 5432, [Password], all TLS modes with CA, identity and server name, SessionDefault, Transactional/Transactional, NativeBatch, ServerTransactionStatus, Statistics, the 8 object kinds, database_required false |
| new | src/physical.rs |
connect_physical(target, liveness, cancel): open, Config, connect_raw under login_timeout raced against cancel, ServerInfo query |
| new | src/tls.rs |
PgTls wrapper over tokio-postgres-rustls with handshake_started (replaces net postgres_connector.rs and channel_binding.rs, which were never created) |
| new | src/pool.rs |
PgConnect: deadpool_postgres::Connect, pool builder, PgCall CallResource with Drop-take on abandon, PoolError mapping. Replaces the spec's pg_pooled.rs and pg_manager.rs |
| new | src/connection.rs |
CONN v2 impl: link state, lost(), probe with timeout_get(wait ZERO) and standalone fallback, close |
| new | src/workspace_call.rs |
FC-D21 flight, millisecond preamble (non-zero clamp) |
| new | src/write_batch.rs |
run_write, ledger, guards, xid, COMMIT outcome mapping |
| new | src/commit_resolution.rs |
pg_xact_status / txid_status |
| new | src/cancel.rs |
PgStop: EngineStop (OutOfBand(Connection), cancel_query_raw over transport.open(), 57014 acknowledgement) |
| new | src/stream_read.rs |
XW1 |
| new | src/editor_session.rs |
Actor, run loop, sink backpressure, deadline and cancel |
| new | src/editor_dispatch.rs |
Pure plan: (class, tracked status, read_only, row-source) to a flight |
| new | src/transaction_tracker.rs |
Status transitions plus the SAVEPOINT probe (amendment; not in the spec) |
| new | src/cursor_flight.rs |
DECLARE/FETCH flight, savepoint wrapper, position correction, 0A000/42601 fallback (amendment; not in the spec) |
| new | src/statement_class.rs |
Leading-keyword class: TransactionStart, TransactionEnd (with AND CHAIN), Savepoint, RollbackToSavepoint, TwoPhase, CopyFromStdin, CopyToStdout, RowSource, Undetermined, Other |
| new | src/errors.rs |
DbError to DriverError (28P01/28000, 25006, 25001 inside the wrapper, 57014 through OwnedCalls, 55P03, 53300); TLS classify; explain_closed_stream |
| new | src/catalog.rs |
fetch_columns: FB-D8 domain CTE, enum labels, typmod, read_form. Shared with W3-08 |
| new | src/metadata/{mod,objects,indexes,identity,estimate}.rs |
|
| delete | n/a | The spec's script_split.rs, type_names.rs, pg_pooled.rs and pg_manager.rs. decode.rs, raw_cell.rs, pg_param.rs, numeric_wire.rs and array_wire.rs are W3-09's files (section 5) |
| delete | tests/integration.rs, tests/tls.rs |
tests/tls.rs imports sqlx at line 5 |
| keep or fold | tests/tls_provider.rs |
|
| new | tests/integration/main.rs and topic modules |
cancellation.rs, editor_session.rs, read_only.rs, write_batch.rs, metadata.rs, pgbouncer.rs, liveness.rs, tls.rs (engine-specific only), decode.rs (W3-09), conformance_engine.rs (W9-07) |
| no edit needed | ../.github/workflows/build-linux.yml |
Already per-package nextest (build-linux.yml:157-200) |
| no new file | tests/fixtures/pgbouncer.ini |
PgBouncerFixture replaces it (details 16484) |
Outside the crate:
crates/test-fixtures/src/pgbouncer_fixture.rs: add adefault_pool_size/max_client_connoption fortransaction_pool_editor_leaves_no_session_state(pool size 1). The current config has no pool size (pgbouncer_fixture.rs:59-70).crates/test-fixtures/src/test_pki.rs: ECDSA P-384 and Ed25519 server-certificate variants for the channel-binding tests (today RSA only at:26,33). This is W9-02's file, so coordinate with that lane.crates/test-support/src/fault_transport.rswithstall_reads_after_write_containing, owned by W4-01/TEST-SUPPORT, forcommit_stall_deadline_commit_outcome_unknown_then_applied.linux/Cargo.toml: add workspace entries for the new crates if they are declared at the workspace level. Removerust_decimaland the sqlxrust_decimalfeature only when the MySQL and MSSQL switches drop them; it is the same commit in step 30.
Dependencies
| Change | Crate and version | Features |
|---|---|---|
| add | tokio-postgres 0.7.18 | default-features = false, ["runtime"]. deadpool-postgres turns default features on anyway |
| add | postgres-types 0.2.14 | ["with-chrono-0_4"], for Date<NaiveDate> / Timestamp<NaiveDateTime>; optionally with-uuid-1 |
| add | postgres-protocol 0.6.12 | |
| add | deadpool-postgres 0.14.2 | default rt_tokio_1; deadpool 0.13.1 comes transitively |
| add | tokio-postgres-rustls 0.14.0 | default-features = false; brings sha2 0.11 and x509-cert 0.2 |
| add | rustls (workspace 0.23) | normal dependency, to clone the ClientConfig |
| add | tokio | rt, time, sync, macros |
| add | tokio-util 0.7.19 | rt, for TaskTracker and CancellationToken |
| add | futures-util 0.3.34 | |
| add | bytes 1.12.1 | |
| add | tracing | |
| keep | tablepro-core, tablepro-net, async-trait, chrono, uuid, secrecy | |
| remove | sqlx, rust_decimal, futures (use futures-util), serde_json | serde_json goes if JSON stays raw text through JsonText::parse, which is likely |
| none | [patch.crates-io], deny allow-git |
|
| check | cargo deny |
Multiple-versions warnings, for example sha2 0.10 and 0.11 if postgres-protocol still uses 0.10. Not verified |
Tests the spec requires (amended)
Unit:
statement_class::leading_keyword_table, extended with AND CHAIN, ROLLBACK TO, COPY FROM STDIN, COPY TO STDOUT and row-source detection.editor_dispatch::read_only_plan_per_class_and_status.workspace_call::preamble_uses_integer_milliseconds, plus a sub-millisecond deadline that never renders 0.errors::sqlstate_table.capabilities_match_driver_contract.- The
script_splitcases are already covered by core's corpus. Add only a driver test thatstandard_conforming_stringsoff maps tobackslash_escapes. - New:
transaction_tracker::transitions_tableandcursor_flight::error_position_is_corrected. numeric_wiretests are W3-09's.
integration/tls.rs, engine-specific only after the W9-08 macros:
channel_binding_require_with_rsa_sha256_and_ecdsa_p384ed25519_cert_require_fails_prefer_connectsserver_without_ssl_reports_server_refused_tls- Removed by details 16478:
verify_full_with_ca_ok,without_ca_unknown_issuer,wrong_server_name_name_mismatch,require_ok.
integration/cancellation.rs:
pg_sleep_cancel_returns_cancelled_within_2s_backend_idledeadline_1s_timeout_server_cancelleddropped_caller_future_backend_idle_within_2sconnection_scoped_stop_never_reaches_next_callcomes from the W9-07 macro.
integration/editor_session.rs:
- From the spec:
begin_update_rollback_persists_state,set_search_path_and_temp_table_persist,create_function_with_dollar_body,create_function_begin_atomic_runs_as_one_statement,vacuum_after_select_in_same_script_succeeds,row_limit_server_stopped_select_list_generate_series,delete_returning_50000_limit_100_deletes_all,begin_insert_big_select_commit_persists_and_state_in_transaction,script_insert_select_insert_both_persist,slow_sink_receives_all_rows_in_order,consumer_gone_cancels_and_session_ready. - Renamed:
select_inside_begin_uses_portal_row_limitbecomesselect_inside_begin_uses_savepoint_cursor_row_limit. - Deleted:
type_created_in_open_transaction_resolved_via_columns_resolved(no editor type OIDs until Canceling connection does not actually cancel #1358). - New from the amendments and the dropped W2-03 fork tests:
cursor_row_limit_reads_under_1mib, the byte-counting stream from the W2-03 test;cursor_trailing_line_comment_still_fetches;data_modifying_with_falls_back_0a000;select_into_inside_block_falls_back_42601_and_block_survives;copy_from_stdin_refused_before_send;parse_error_leaves_session_idle;call_procedure_with_commit_probes_status;do_block_commit_probes_status;commit_and_chain_stays_in_transaction;savepoint_probe_25p01_idle_25p02_failed.
integration/read_only.rs keeps all 7 spec tests: editor_idle_delete_fails_25006, editor_begin_then_delete_fails_25006, editor_set_default_transaction_read_only_off_does_not_escape, editor_vacuum_refused_as_read_only, editor_commit_prepared_refused_client_guard, write_batch_client_guard, query_bound_nextval_is_25006.
integration/write_batch.rs:
- Kept:
lock_timeout_lock_wait,concurrent_delete_row_guard_failed_rolls_back_all,statement_deadline_before_commit_rolls_back,commit_stall_deadline_commit_outcome_unknown_then_applied(needs FaultTransport),deferred_constraint_commit_error_is_rolled_back. - Removed by the W9-07 macros:
row_changes_statement_timeout_1s_times_out,schema_changes_unbounded. - New:
abandoned_write_connection_is_taken_not_recycled.
integration/metadata.rs keeps all 8 spec tests, including stale_ctid_edit_after_vacuum_reuse_row_guard_failed (needs xmin) and ctid_window_explain_shows_tid_range_scan. New from FC-D18: enum_citext_10000_row_flight_never_prepares.
integration/pgbouncer.rs (PgBouncerFixture, v1.25.2-p0):
- the transaction-pooling round trip;
transaction_pool_set_local_timeout_applies;transaction_pool_read_only_editor_delete_fails;transaction_pool_editor_leaves_no_session_state(needs pool size 1);session_pool_editor_state_persists;statement_pool_write_batch_returns_diagnostics;no_named_parse_with_max_prepared_statements_0;- new:
statement_pool_editor_cursor_falls_back.
integration/liveness.rs:
terminate_backend_link_stays_open_after_healthy_probestopped_container_marks_lostprobe_with_saturated_pool_is_okis removed in favour of W9-07 case 5.
Depends on
- W2-11: CONN v2
Connection, DRIVER v2,EditorSessionwiring, XW1stream_read/ReadStatement/StreamSummary. Core still has v1 (connection.rs:64-118,driver.rs:7-48). - W2-10: done for the dialer, TLS config and classify. The PG TLS connector moves to tokio-postgres-rustls under the amendment.
- W2-09: done (types present).
- W3-06: Statement and browse builders, partly present.
- W3-07:
PgDialectandengine_row_idwith xmin. - W3-08: catalog read_form, shared
catalog.rs. - W3-09: codecs (section 5).
- W1-11:
NetworkTargetwith the OpenSSH transport. - W4-01 / TEST-SUPPORT: FaultTransport.
- W9-02: TestPki ECDSA and Ed25519.
- W9-07 and W9-08: the shared macros.
Spec text that is stale or amended
- "tokio-postgres 0.7.18 (fork)",
query_text_portal,SimpleColumn::type_oid,SimpleQueryStream::transaction_status, "using the fork's status from the previous ReadyForQuery" and "transaction_state() is the fork status" (details 7704-7729, 7764). These are replaced by the tracker, the SAVEPOINT probe and the cursor flight (amendment FC-D1). - "PgManager implements deadpool Manager. Type = PgPooled …; recycle runs simple_query_raw("") … rejects unless status is Idle" (7650-7654) and FC-D18 (3093-3094). Replaced by deadpool-postgres
from_connectplusVerified, with take-on-abandon (amendment FC-D18). - "deadpool 0.13.1 [managed, rt_tokio_1]" as a direct dependency (7761). Use deadpool-postgres 0.14.2 instead.
- "Type names … Weak … ColumnsResolved" (7723-7727). Deleted until Canceling connection does not actually cancel #1358.
script_split::splitmirroring psqlscan.l (7695-7700). Superseded by coreScriptPlan(amendment W6-05/FC-C23; commit 43eea7e).- Files
pg_pooled.rs,pg_manager.rs,script_split.rs,type_names.rs,tests/smoke_local.rs (deleted)(already gone, 869df6f),tests/fixtures/pgbouncer.iniand thebuild-linux.ymledit (7637, 7762) are stale. - FC-D21 "Editor sessions … SET default_transaction_read_only = on / SET statement_timeout after connect" (3176) and FC-C4 (3357) contradict W2-12's "No session-level SET is ever sent". W2-12 is the later reconciliation and should win. Fix the FC text.
- PG-DRIVER (5907) and FC-C17 (3808): "fork (FC-C24)", "rust_decimal [db-tokio-postgres]". Stale (amendments FC-D1 and the rust_decimal editorial note).
- CONN "fn dialect()" and the
ServerInfoshape from FC-C8 differ from the current core types (server_info.rs:7-12).CallOptionsdeadline is anInstant(call_options.rs:12), notDurationas in FC-C8 and CONN. - The FC-C17 Cargo list (with-uuid-1, with-serde_json-1, array-impls) is not needed: RawCell plus postgres-protocol do the decoding.
Size
About 3,300 production lines, 700 unit-test lines and 1,600 integration-test lines added, and about 1,400 lines deleted (lib.rs plus the sqlx tests). That is roughly 5,600 changed lines, excluding W3-09's codecs and the shared W9 modules.
5. W3-09 · PostgreSQL part: lossless decode and encode
Status: partial, on the wrong stack.
- Present on sqlx: the binary NUMERIC to exact-text renderer with tests (
lib.rs:856-911,937-1033), JSON and JSONB raw bytes (801-825), bit strings (932-935), Float32 (401), and Undecodable-not-Null (450-460,777-784). - Missing:
- NaN and ±Infinity as
SqlDecimal(today they becomeFloat64,921-928); - date and timestamp infinity (
406-430only produceTemporal::Finite); - time 24:00:00 (
409-411); - timetz and interval through the custom wire (today they use the sqlx types at
412-422and435-441); - arrays with lower bounds;
- inet, cidr and macaddr as Other;
RawCellfor tokio-postgres;PgParam, theToSqlencode byParamType. Today sqlx binds through rust_decimal (466-510,791-794).
- NaN and ±Infinity as
Amended design (W3-09/FB-D5)
- Upstream decoders (all binary in
query_typed_raw):postgres_types::{Date<NaiveDate>, Timestamp<NaiveDateTime>, Timestamp<DateTime<Utc>>}for date, timestamp and timestamptz (infinity variants and checked arithmetic; chrono_04.rs:21,119);- postgres-protocol
bool/int2/int4/int8/float4/float8/text/bytea/uuid/oid_from_sql,array_from_sql,range_from_sql,inet_from_sql,macaddr_from_sql,varbit_from_sql,point/box/path_from_sqlandlsn_from_sql.
- Custom code stays only for:
numeric_wire(movelib.rs:856-911and returnSqlDecimal::nan()/infinity());interval_wire(i64 µs, i32 days, i32 months);time(time_from_sqlthenSqlTime::from_micros, because upstream NaiveTime wraps 24:00:00, chrono_04.rs:142);timetz(i64 µs plus i32 seconds west).
- "Evaluate text-form numeric first":
- Decode: binary results are fixed by tokio-postgres (
query.rsbinds binary result formats; inferred). Keeping the existing tested binary renderer is cheaper than projecting::textfor every numeric column. - Encode:
ToSql::encode_formatexists (confirmed), soPgParamcan send Decimal, Interval, Time (24:00) and TimeTz as text with their real type OID (Type::NUMERIC, …). That avoids writing binary encoders. Recommended. (inferred design on a confirmed API)
- Decode: binary results are fixed by tokio-postgres (
- Unreachable decoders. hstore and ltree have non-builtin OIDs, so DIALECT read_form already projects them
::text(details 5721). Their binary decoders can never run in app SQL. Drop them from the review's list, or keep them only for arrays the server sends as builtin. (inferred) - read_form interplay with W3-08. The ratified refinement ("ServerText … for built-in OIDs without a W3-09 binary codec (tsvector, point, pg_lsn)", details 9981) shrinks to tsvector, because point and pg_lsn now have upstream codecs. Alternatively keep them ServerText for exact server text. Decide this with the W3-08 lane.
- Editor path.
decode_text"through the fork's type_oid" (9380) is stale. Editor cells areValue::TextorNulluntil Canceling connection does not actually cancel #1358. Deletedecode_text.rsfrom the file list, or keep it unused until then.
Remaining work: files
| Action | Path |
|---|---|
| new | crates/drivers/postgres/src/raw_cell.rs: RawCell(Value): FromSql, accepts every type, from_sql_null gives Null |
| new | src/decode.rs: OID dispatch, malformed input gives InvalidEncoding, overflow gives OutOfRange, otherwise UnsupportedType |
| new | src/numeric_wire.rs: moved and fixed |
| new | src/interval_wire.rs |
| new | src/temporal_wire.rs: time and timetz only; date and timestamp go through postgres-types |
| new | src/array_decode.rs: array_from_sql, recursive, lower bound ≠ 1 gives Other [lb:ub]={..} |
| new | src/network_text.rs: inet, cidr and macaddr through postgres-protocol, rendered to canonical text as Other |
| new | src/pg_param.rs: PgParam<'a>(&'a BoundParam): ToSql, accepts true, to_sql_checked!, ParamType to Type map, encode_format text for Decimal, Interval, Time, TimeTz and ServerText |
| change | src/lib.rs |
| change | Cargo.toml (shared with W2-12) |
| new | tests/integration/decode.rs, replacing tests/integration.rs's value_roundtrip_all_types (:75) and binary_only_types_read_back (:238) |
| drop | decode_text.rs from the spec list |
Open point for W3-06: ParamType has no Bits variant (param_type.rs:10-35). Value::Bits is accepted under ParamType::Bytes (:59), which PostgreSQL will refuse for a bit or varbit column. Either add a Bits ParamType or bind bits as ServerText(bit(n)) with text format.
Dependencies
Same as W2-12: postgres-types 0.2.14 [with-chrono-0_4] and postgres-protocol 0.6.12. Remove rust_decimal from this crate.
Tests (spec)
Unit:
numeric_wire_round_tripsnumeric_wire_malformed_is_undecodable_not_panicdate_timestamp_infinity_sentinelstimestamp_overflow_is_out_of_rangetime_24_00_00interval_wire_round_triparray_lower_bound_2_is_other_textpg_param_null_typed_by_oid- Add
pg_param_decimal_nan_sent_as_text_numeric. - Keep the existing numeric tests (
lib.rs:954-1017), adjusted from Float64 NaN toSqlDecimal.
Integration:
round_trip_matrixset_null_saves_typed_nullchar10_key_update_affects_one_row- The regression map cites
round_trip_matrix(details 16365).
Depends on
W2-12 (RawCell use sites), W3-05 and W3-06 (Value and ParamType, present), W3-08 (read_form), W8-12 (supersedes the "keep PG on sqlx 0.8.6" interim, which the review amended).
Stale or amended text
- "Scalars through postgres-protocol codecs" is fine.
- "date and timestamp infinity sentinels with checked_add_signed" is now upstream
postgres_types::{Date, Timestamp}(amendment W3-09). - "decode_text … through the fork's type_oid" (9380) is stale.
- FB-C13 still says "bool … use sqlx Decode" and "interval: PgInterval" (2250, 2254). Stale for PostgreSQL.
- The "Rewrite the rationale that cites sqlx-postgres" (FB-D5, 1272) is an editorial fix.
- Files
crates/drivers/postgres/tests/integration.rs(9365) should becometests/integration/decode.rs.
Size
About 900 production lines (≈130 of them moved), 350 unit-test lines and 350 integration-test lines: roughly 1,600 changed lines.
6. W9-07 · PostgreSQL side: connection conformance
Status: not started.
- No
crates/test-support/src/conformance/exists. The test-support files arecall_counters,close_behaviour,connection_script,fake_connection,fake_driver,fake_prompterandfake_secret_vault. - No
tests/integration/tree exists in the PostgreSQL crate. - No
WORKSPACE_POOL_MAX_SIZEexport exists.
(confirmed)
Remaining work (PostgreSQL side only; the shared module belongs to whichever lane owns W9-07 core)
crates/drivers/postgres/tests/integration/conformance_engine.rs(new):PgEngineimplementingConformanceEngine,NetworkConformanceEngine,PooledConformanceEngine,EditorTransactionEngine,TypedConformanceEngineandBoundedDdlEngine.start()usesPostgresFixture::start(HbaMode::Password).connect_optionsbuildsNetworkTargetwithDirectTcpTransport.faulty_connect_optionswraps it inFaultTransport.unbounded_statement_sqlisSELECT pg_sleep(3600) /*marker*/.blocking_statement_sqlispg_sleep(hold).running_statement_count_sqlcountspg_stat_activity WHERE state='active' AND query LIKE '%marker%' AND pid <> pg_backend_pid().row_source_sqlisgenerate_series.schema_change_exceeding_sqlisCREATE TABLE t AS SELECT 1 AS id FROM pg_sleep(3).unsupported_type_casesare point, int4range, a composite and an enum.WORKSPACE_POOL_MAX_SIZEcomes from the driver.
crates/drivers/postgres/tests/integration/main.rs:connection_conformance_suite! { engine: PgEngine, docker: true, endpoint: network, pool: pooled, editor_transactions: true, unsupported_types: true, bounded_ddl: true }.- No CI change: nextest per package plus
#[ignore = "requires docker"].
Tests
The 14 cases plus suite_flags_match_driver_capabilities run against PostgreSQL. Case 6 (row limit keeps transaction and later statements) exercises the new cursor flight. Case 13 is "unsupported types never decode as Null".
Depends on
W2-11, W2-12, W4-01 (FaultTransport), W9-02 (fixtures, present), W9-03 (the matrix is now nextest per package, already in CI).
Stale or amended text
- "unsupported_types (point, int4range, …)" (15981): after the W3-09 amendment, point and int4range have upstream codecs. The case still holds (non-null reads stay non-null), but the name "unsupported" is now loose.
build-aux/tests/test_integration_matrix.py(15919, 16005) is deleted by amendment W9-03 (nextest).- "deadpool 0.13.1 PoolConfig::default max_size" stands, but the constant now feeds deadpool-postgres's
PoolBuilder::max_size.
Size
About 220 lines for the engine plus 30 for main.rs: roughly 250 changed lines on the PostgreSQL side.
7. W9-08 · PostgreSQL side: TLS conformance
Status: not started.
tests/tls.rscovers prefer, require and plaintext-refused over sqlx and the v1 API (tests/tls.rs:26-72).- The fixtures exist:
HbaMode::ClientCertificatewithhostssl … certfortablepro_client(hba_mode.rs:20-25),certificate_credentials()(postgres_fixture.rs:133) andPkiFile::UnrelatedCa(pki_file.rs:4). The served namedb.tablepro.testis inTestPki::SERVER_NAMES(test_pki.rs:19).
(confirmed)
Remaining work
crates/drivers/postgres/tests/integration/tls.rs(new):PgTlsEngineimplementingTlsConformanceEngineandClientIdentityEngine.startisPostgresFixture::start(HbaMode::ClientCertificate).ca_fileandunrelated_ca_filecome from TestPki, andserved_nameis"db.tablepro.test".plaintext_rejection_categoryis most likelyAuthentication: the server answers 28000 "no pg_hba.conf entry … no encryption" forhostnossl reject(inferred; verify).client_identityistablepro_clientwith the client cert and key.
- Invocation:
tls_conformance_suite! { engine: PgTlsEngine, client_identity: true }. - The engine-specific tests from W2-12 stay beside it: channel binding with RSA-SHA256 and ECDSA-P384 under
channel_binding(Require), Ed25519 failing Require but connecting with Prefer, andserver_without_ssl_reports_server_refused_tlsagainstHbaMode::Password.- These need
TestPkiECDSA and Ed25519 server keys (W9-02 file). - The Ed25519 result follows tokio-postgres-rustls's
_ => None(L125). - md5, ecdsa-with-SHA1 and ecdsa-with-SHA512 cannot be tested until the upstream OID PR ships (amendment FC-D23).
- These need
Tests
Six cases plus tls_suite_flags_match_driver_capabilities for PostgreSQL.
Depends on
W2-10 (TLS config, present), W2-12 (PgTls wrapper), W9-02 (PKI variants), W9-07 (shared ConformanceFailure and macro module).
Stale text
The engine-specific list "channel binding, ed25519" stands, but the implementation is now tokio-postgres-rustls's mapping, not x509-cert 0.3 (amendment FC-D23).
Size
About 120 lines for the engine plus 60 for the engine-specific cases. TestPki variants, if done here, add about 80. Roughly 200-260 changed lines.
8. Implementation order
- Prerequisites, in other lanes or done first on the stacked step-30 branch: W2-11 CONN v2 and ConnectOptions v2 (
NetworkTarget); W1-11 OpenSSH transport into targets; W3-06 and W3-07PgDialectwith xmin inEngineRowIdPart; W4-01 FaultTransport; W9-02 TestPki ECDSA and Ed25519 plus the PgBouncer pool-size option. - W3-09 PostgreSQL codecs as pure modules with unit tests:
raw_cell,decode,numeric_wire(moved and fixed),interval_wire,temporal_wire,array_decode,network_text,pg_param. They depend only on postgres-types and postgres-protocol, so they can compile on the branch before the trait switch. - W2-12 base:
tls.rs(PgTls wrapper),physical.rs,pool.rs(Connect, PgCall, take-on-abandon),errors.rs,capabilities.rs,driver.rs. - W2-12 workspace side:
workspace_call.rs,metadata/*,catalog.rs(with W3-08),query_bound/count_rows,stream_read.rs,cancel.rs(PgStop),write_batch.rs,commit_resolution.rs,connection.rs(probe and link loss). - W2-12 editor side:
statement_class.rs,transaction_tracker.rs(with the SAVEPOINT probe),cursor_flight.rs,editor_dispatch.rs,editor_session.rs(actor over coreScriptPlan). - W9-07 PostgreSQL engine and W9-08 PostgreSQL TLS engine, then the engine-specific integration modules (cancellation, editor_session, read_only, write_batch, metadata, pgbouncer, liveness, decode).
- Delete the sqlx code,
tests/integration.rsandtests/tls.rs. Drop sqlx and rust_decimal from the crate, and runcargo deny(bans plus acargo tree -i rust_decimalcheck after all drivers switch).
9. Open questions and risks
- deadpool-postgres
Connectcannot return typed transport errors.- The trait's error is
tokio_postgres::Error, which has no public constructor (confirmed). - A pool-created connection that fails in
transport.open()(name resolution, refused, SSH master exited) cannot surface asDriverError::Transport. - Options:
- (a) A failing stream adapter whose write yields
io::Error::other(TransportError), so the error comes back throughError::source()/io::Error::get_ref. The connect_raw path wrapping I/O errors that way is inferred. - (b) A per-call side slot in
PgConnect. - (c) Accept a degraded
ConnectionLostfor later pool members, since the first connection is opened outside the pool with full typing.
- (a) A failing stream adapter whose write yields
- This needs a decision. It is the one place where "use deadpool-postgres" costs fidelity.
- The trait's error is
- Recycle cannot see the transaction status. Safety rests on two things: take-on-abandon in
PgCall::drop, and taking every flight without a COMMIT or ROLLBACK acknowledgement. A missed path returns a connection inside an open transaction to the pool. That needs a regression test (abandoned_write_connection_is_taken_not_recycled). - Exact status without a fork. The tracker is inferred from PostgreSQL rules. CALL and DO with transaction control, COMMIT AND CHAIN and procedures that end in a different state must route through the SAVEPOINT probe. Every Idle probe writes a server ERROR log line (25P01). An upstream status PR is the exit, and it goes on the watch list.
- Cursor row limit.
- It changes plans (
cursor_tuple_fraction0.1) and disables parallel plans (as Execute with a row count also did). - It needs a driver-owned BEGIN … COMMIT when Idle.
- It falls back under PgBouncer statement pooling.
- It needs exact position correction.
- COPY TO STDOUT output is lost until upstream exposes CopyData. Refuse it or drain it with a notice?
- It changes plans (
- COPY FROM STDIN detection is client-side. A misclassified COPY would leave the session waiting. The deadline and CancelRequest are the backstop, but whether cancel interrupts COPY IN reads is unverified.
- Editor columns are text-typed until rust-postgres Canceling connection does not actually cancel #1358 (unreviewed since 2026-05-29, per the review). This is user-visible: no typed bool, bytea or numeric formatting in editor results.
- TLS wrapper placement: the PostgreSQL driver or tablepro-net. The W2-10 spec places it in net, while the amendment only says "wrapped". The driver crate keeps tokio-postgres out of net.
- tokio-postgres-rustls is a personal repository with a sole owner. It lacks three signature OIDs, and the upstream PR must be filed. Accepted as a bus-factor-1 exception (amendment FC-D23); it needs an ADR entry.
- The
statement_timeout0 hazard when a call deadline is already near: clamp it, or fail early. - Value and ParamType gaps: no Bits
ParamType, and no xminEngineRowIdPart. Both belong to other lanes but block W2-12 and W3-09 tests. - PgBouncerFixture lacks a pool-size option, and TestPki lacks ECDSA and Ed25519. Both are fixture changes in the W9-02 files.
- The FC-D21 and FC-C4 editor SET text contradicts W2-12. Resolve it as "no session SET", and fix the FC text so the W2-19 docs do not reintroduce it.
Analysis file
|
| Engine | How it reaches the server | TLS name | Notes |
|---|---|---|---|
| MySQL (sqlx-mysql 0.9.0) | Uses transport.route(), never open(). Tcp(ep) becomes .host(ep.host()).port(ep.port()). UnixSocket(p) becomes .socket(p).host(<service host or server-name override>). |
sqlx always uses options.host [C sqlx-mysql-0.9.0/src/connection/tls.rs:62]. It dials connect_uds when socket is set [C establish.rs:18-20]. |
The server-name override only works over SSH (ServerNameOverride::SshForwardOnly). Over TCP the host is both the dial target and the TLS name. Direct TCP has no TCP_NODELAY (0.9.0) and no keepalive, so the health probe is the only liveness check. The KILL QUERY control connection uses the same route. |
| SQLite | FileEndpoint only. No transport. |
n/a | |
| SQL Server (tiberius) | transport.open() gives a boxed tokio stream, then compat_write(), then Client::connect(config, stream). Routing uses transport.reroute(endpoint) once. |
Config::host(service host) with . mapped to localhost. tiberius 0.13 adds hostname_in_certificate for the override [C, fetched main src/client/config.rs]. |
Works the same way over TCP and over the SSH Unix socket. |
| ClickHouse (clickhouse 0.15.2) | Client::with_http_client(hyper_util Client<HttpsConnector<C>>) [C clickhouse-0.15.2/src/lib.rs:152, http_client.rs:23-33]. C is a tower_service::Service<Uri> whose call() runs transport.open(). |
hyper-rustls takes SNI from the URL host, so the URL must name the service host [I]. | hyper-util implements Connection for UnixStream and TokioIo<T> [C hyper-util-0.1.20/src/client/legacy/connect/http.rs:583,598]. Its Connect blanket impl needs S::Future: Unpin + Send [C connect/mod.rs:340-344]. |
0.3 Pools
- The review keeps deadpool managers for sqlx-mysql (MysqlManager) and tiberius (MssqlManager) only.
- SQLite stays on
SqlitePoolwithmax_connections(1). - ClickHouse uses hyper's pool through
pool_idle_timeout. - deadpool is not in Cargo.lock [C grep]. Latest is 0.13.1 (2026-08-26) [C crates.io].
Managerhastype Type,type Error,fn create(&self) -> impl Future<Output=Result<..>> + Send,fn recycle(&self, &mut Type, &Metrics) -> impl Future<Output=RecycleResult<..>> + Sendandfn detach[C https://docs.rs/deadpool/0.13.1/deadpool/managed/trait.Manager.html].- Features:
managed(default) andrt_tokio_1[C docs.rs features page]. - Add:
deadpool = { version = "0.13.1", default-features = false, features = ["managed", "rt_tokio_1"] }to[workspace.dependencies].
0.4 Upstream changes since the review (2026-09-14 to 2026-10-12)
- tiberius 0.13.0 is on crates.io (2026-09-25) [C https://crates.io/api/v1/crates/tiberius/versions].
- Main carries
Client::cancel_query, an additiveConfig::trust_cert_ca,trust_cert_ca_bundle,hostname_in_certificate,handshake_timeout(default 15 s) andcommand_timeout(default 30 s, per round trip; it setscommand_desync, after which the connection must not be reused) [C fetched mainsrc/client.rs,src/client/config.rs,src/client/connection.rs]. - Still missing:
money_as_numeric(MONEY decodes to F64, persrc/tds/codec/column_data/money.rs) and an injectable rustlsClientConfig.Error::Tlsis stillString[C fetched v0.13.0rustls_tls_stream.rs]. - Crypto provider:
CryptoProvider::get_default(), falling back to aws-lc-rs [C same file]. - Default features are
[tds80, winauth, native-tls]. libgssapi is ^0.11 [C https://docs.rs/crate/tiberius/0.13.0/features]. - The review's trigger "drop the patch when crates.io 0.13 ships" has fired.
- Main carries
- sqlx is still 0.9.0 [C crates.io]. #4336 (TCP_NODELAY) was merged to main on 2026-08-17 but is not released [C api.github.com pulls/4336]. #4281 (custom transport) is still open, last updated 2026-05-28 [C]. FC-D3 ("first 0.9.x patch with #4336") cannot be met today.
- clickhouse 0.15.2 is still the latest [C crates.io]. clickhouse-types 0.1.3 and hyper-rustls 0.27.9 / hyper-util 0.1.20 / tower-service 0.3.3 are already locked (Cargo.lock lines 837, 2253, 2282, 5667) [C].
0.5 Workspace items the switch must change (linux/Cargo.toml)
- Line 37:
clickhouseenablesrustls-tls-aws-lcandrustls-tls-webpki-roots. W2-16 turns the crate's TLS features off [C]. - Line 38:
sqlxfeatures includerust_decimalandjson. Droprust_decimalat the switch.crates/app(grid.rs) and the PG driver still use rust_decimal, so it leaves in the same commit [C grep]. - Line 39:
tiberius = "0.12"withrust_decimal. Change to"0.13",default-features = false, features["tds73", "rustls", "chrono"]. - Lines 96-102: delete
[patch.crates-io] tiberius. deny.toml:58:allow-git = [tiberius-rs]becomes empty [C].deny.toml:42-53: the ban onrust_decimalis not there yet (FD-D19 adds it after the switch) [C].deny.toml:52: thetablepro-test-fixtureswrappers list onlytablepro-driver-postgres. Add mysql, mssql, clickhouse and sqlite when they take the fixtures as a dev-dependency [C].
0.6 CI
.github/workflows/build-linux.yml:157-198already has one Docker job per package (mysql, mssql, clickhouse) runningcargo nextest run -p <pkg> --run-ignored only[C].- Following the review amendment for W9-03 (no generated matrix), W2-13, W2-15 and W2-16 need no workflow edit. MariaDB runs inside the mysql package through
MysqlFixture::start(MysqlFlavour::Mariadb)(crates/test-fixtures/src/mysql_flavour.rs:5-17). - SQLite tests are not Docker tests. They run in the fast job, which already installs
libsqlite3-dev,clangandlibclang-dev(build-linux.yml:75-78) [C].
W2-13 · MySQL and MariaDB driver
Status: not started. [C]
crates/drivers/mysql/src/lib.rs(713 lines) is still the v1Connection.- Connect builds a sqlx
Poolwithmax_connections(4), host and port, and Required/Disabled TLS only (lib.rs:37-57). - Workspace reads, metadata and transactions go through the pool (
lib.rs:65-344). - There is no KILL QUERY, no editor session, no session statements and no engine-aware writes.
Pieces that already exist and W2-13 reuses [C]:
tablepro-netis already a dependency (mysql/Cargo.toml:14).tablepro_net::tls::{mysql_ca_bundle_pem, client_identity_pem, classify}(crates/net/src/tls/mysql_bundle.rs:7-64,classify.rs:6-18).- The error-number table is mostly right: 1045, 1290|1792, 1317, 1205, 3024|1969, 1040 (
lib.rs:579-597). - TIME through
MySqlTime(lib.rs:474-482). MysqlFixtureandMysqlFlavour(test-fixtures).- The MySQL column-type parser
crates/core/src/sql_syntax/mysql_column_type.rs.
Design after the amendments (review W2-13, FC-D2, FC-D6):
- No
PrivateSocketRelay.physical::connect_physical(target, route, timezone, liveness, cancel)buildsMySqlConnectOptionsfromtransport.route()as described in 0.2. - Options carried over from the spec [C APIs at
sqlx-mysql-0.9.0/src/options/mod.rs]:ssl_modeDisabled/Required/VerifyIdentity (:174)ssl_ca_from_pem(:204),ssl_client_cert_from_pem/ssl_client_key_from_pem(:244/:284)statement_cache_capacity(0)(:295)pipes_as_concat(false)(:331),no_engine_substitution(false)(:366)timezone(:387),set_names(false)(:413)
- Keep W1-11's
disable_statement_logging()(W1-11 owns it; per the plan's reconcile note at plan.md:1077). - MySqlConnection comes from
MySqlConnection::connect_withinside a deadpoolMysqlManager, not from sqlxPool. - The connection id comes from
SELECT CONNECTION_ID(), because the handshake id ispub(crate)[Cprotocol/connect/handshake.rs:20]. - KILL QUERY runs on a lazily opened control connection over the same route.
- Rows-matched semantics: CLIENT_FOUND_ROWS is set in 0.9.0 [C
connection/stream.rs:38].
Remaining work
- Create under
crates/drivers/mysql/src/:driver.rs,capabilities.rs,flavour.rsphysical.rs(route-based, no relay)session_statements.rs(plus W3-19'sworkspace_session.rssql_mode statement)mysql_manager.rs,connection.rs,cancel.rs,table_engine.rs,write_batch.rs,editor_session.rs,catalog.rs,metadata.rs,errors.rs- W3-09 modules (see below)
- Change
lib.rs: drop the v1 body and re-export the driver. - Delete
crates/drivers/mysql/tests/integration.rs(388 lines). Createtests/integration/{main.rs, flavour_suite.rs, tls.rs, row_limit.rs, editor_session.rs, non_transactional.rs}, plus W3-09's decode cases astests/integration/decode.rs(reconcile plan.md:1078). crates/drivers/mysql/Cargo.toml:- Add
deadpool0.13.1 (managed,rt_tokio_1),tokio(rt, time, sync, macros not needed),tokio-util0.7 (rt) anduuidif session ids are needed. - Remove dev-dependency
rust_decimal. - Add dev-dependencies
tablepro-test-fixturesandtablepro-test-support(conformance). - sqlx features stay
["mysql", "mysql-rsa", "tls-rustls-aws-lc-rs"](mysql/Cargo.toml:21).
- Add
linux/Cargo.toml: drop sqlxrust_decimal(shared with W2-12 and the app). Adddeadpool.deny.toml: add the mysql driver to the test-fixtures wrappers.- No
build-linux.ymledit (0.6).
Dependency changes (exact): add deadpool 0.13.1. Remove rust_decimal (dev). sqlx stays 0.9.0, because there is no 0.9.x release with #4336. tablepro-net is already present.
Tests required (spec plus amendments):
- Unit:
flavour::detects_mariadb_from_version_stringsession_statements::mysql_and_mariadb_texttable_engine::transactions_column_mappingerrors::code_table_per_flavourcapabilities_match_driver_contract
flavour_suite.rson mysql:8.4 and mariadb:11.4: the 14 cases in the spec (details:7954-7968), including the conformance scenarios withMySqlEngine.non_transactional.rs: 5 cases.row_limit.rs: 6 cases.editor_session.rs: 3 cases.tls.rs:verify_identity_through_relay_with_server_namebecomesverify_identity_through_ssh_socket_with_server_name(review: point at the ssh socket, usingOpenSshFixture).unknown_issuer_classifieddisabled_against_require_secure_transport- added by the review:
disabled_mode_uses_rsa_not_cleartextandtls_upgrade_happens_over_socket, both against the OpenSSH forward socket.
- Carried in from other tasks into this crate:
- W1-11
mysql_emits_no_sqlx_query_events - W3-19
workspace_session_strips_no_backslash_escapesandnon_sqlite_drivers_reject_suspension - W3-18
fetch_table_definition_* - W9-16 name
read_only_insert_and_create_1792 - existing coverage
caching_sha2_without_tls_uses_rsa(currenttests/integration.rs:60)
- W1-11
Depends on:
- W2-11 (trait, ConnectOptions v2; missing)
- W2-10 (done in its amended form)
- W3-06 and W3-07 (dialect;
EngineDialectin core) - W3-08 (catalog classification)
- W3-18 (
fetch_table_definition) - W3-19 (sql_mode, ForeignKeyMode)
- W1-11 (statement logging; SSH transport wired into the app)
- W9-07 and W9-08 (conformance)
- W4-06 (app switch)
Spec text amended or stale:
- Every relay line is removed by the review: "One PrivateSocketRelay per MysqlConnection",
socket(relay.socket_path()), "control connection through the relay", and the APItablepro-net PrivateSocketRelay. It becomesroute()-based options. - "replaces mysql/src/lib.rs:15-468": the file is now 713 lines.
- "sqlx sets CLIENT_FOUND_ROWS (verified in 0.8.6 stream.rs:46)": now 0.9.0
stream.rs:38. - FC-D3 (take the 0.9.x patch with #4336) is unmeetable. Record a dated watch-list entry.
- W1-11 lists
crates/drivers/mysql/src/connect_options.rswhile W2-13 namesphysical.rs. Pick one file. - The spec's
tests/integration.rs (deleted)conflicts with W3-09 and W3-11 editing that file. The reconcile (plan.md:1078) moves those cases intotests/integration/.
Size: about 4,300 lines added and 1,100 deleted, about 5,400 changed. That is roughly 2,600 src and 1,700 tests, not counting W3-09's roughly 600.
W2-14 · SQLite driver
Status: not started. [C]
crates/drivers/sqlite/src/lib.rs(785 lines) builds a URL string, maps:memory:(lib.rs:45-49) and setscreate_if_missing(true)(lib.rs:53). It uses a 4-connection pool (lib.rs:54-57).- Indexes come from
index_list/index_infoonly, with no expressions, DESC or predicates (lib.rs:248-307). CANTOPENalways becomesFileAccessDenied(lib.rs:539-541).- There is no interrupt, editor session,
BEGIN IMMEDIATEor atomic create. libsqlite3-sysis a dev-dependency only (sqlite/Cargo.toml:21-22).- A partial W3-09 is in place (storage-class decode,
lib.rs:387-419).
Design after the amendments (review W2-14, FC-D16, FD-D11):
- Opening:
SqliteConnectOptions::new().filename(..).create_if_missing(false).read_only(..).busy_timeout(5 s).foreign_keys(true).pragma("trusted_schema","OFF")plus W1-11'sdisable_statement_logging(). APIs [Csqlx-sqlite-0.9.0/src/options/mod.rs:226,240,294,303,323,358]. - One workspace connection:
SqlitePoolOptions::max_connections(1).after_connectinstallsset_progress_handler(1000, move || !flag.load(Relaxed)); returning false interrupts [Cconnection/mod.rs:386-399].before_acquirerejects abandoned connections [Csqlx-core-0.9.0/src/pool/options.rs:380,435]. The probe usestry_acquire[Cpool/mod.rs:366]. Discard usesclose_on_drop[Cpool/connection.rs:97]. - Cancellation goes through the progress handler only.
lock_handlewaits for the worker to unlock [Cconnection/mod.rs:199-203], so the text "sqlite3_interrupt through lock_handle" is deleted (review). - Transaction state: one interim unsafe call,
sqlite3_get_autocommit(handle.as_raw_handle().as_ptr()).as_raw_handleis [Cconnection/mod.rs:371]. sqlx keepsin_transactionpub(crate)[Cconnection/handle.rs:85]. File the upstream accessor PR. Name rusqlite 0.40 with deadpool and deadpool-sync as the fallback (review). - Index SQL: sqlparser 0.63
CreateIndex.columns/.predicatewithSpannedsource slices, falling back to the raw text when a span is empty.CreateIndexis at [Csqlparser-0.63.0/src/ast/ddl.rs:2819-2847];Spannedatast/mod.rs:152,spans.rs:695,727. Spans are line and column, so they need a line/col to byte conversion [I]. The hand-rolled depth-0 splitter in the spec is replaced.
Remaining work
- Create under
crates/drivers/sqlite/src/:driver.rs,capabilities.rs,open.rs,create_file.rs,connection.rs,interrupt.rs,write_batch.rseditor_session.rs,autocommit.rs,catalog.rs,metadata.rs,index_sql.rs(sqlparser)errors.rs, plus W3-19'sforeign_key_verification.rsand W3-18'scatalog/{table_definition, dependents}.rs
- Change
lib.rs. - Tests: create
tests/integration/{main.rs, open_semantics.rs, sessions.rs, metadata.rs}and W3-09tests/decode.rs. - Port
tests/values.rs(uses":memory:",values.rs:5-11) andtests/csv_import.rs(csv_import.rs:13-19) to tempfileFileEndpoints on the v2 API.:memory:stops working. - Keep
tests/script_plan_matches_sqlite3_complete.rs(no driver API). - Cargo:
- Move
libsqlite3-systo[dependencies]withdefault-features = false, using the workspace range">=0.30.1, <0.38.0"(linux/Cargo.toml:47). It already equals sqlx-sqlite 0.9.0's range [Csqlx-sqlite-0.9.0/Cargo.toml:141-142]. Widen it in lockstep with sqlx, and do not pin~0.9.0. - Add
uuid(workspace 1.x, v4) for temporary names,sqlparser(workspace 0.63) andtokio-util(rt). - The spec's "rust_decimal and serde_json are removed" is already true: neither is in
sqlite/Cargo.toml.
- Move
Dependency changes (exact): libsqlite3-sys >=0.30.1, <0.38.0 (locked 0.37.0, Cargo.lock:2751) becomes a normal dependency. Add sqlparser 0.63.0 (already locked). Add uuid 1 (already locked 1.26.1).
Tests required:
- Unit:
index_sql::key_list_splits_depth_zero_with_quotes_brackets_comments, renamed to fit the parser-based implementation; keep the same fixturesindex_sql::where_tail_is_predicateerrors::cantopen_distinguishes_missing_and_deniedopen::options_never_create_for_open_existing- an empty-span fallback case (review)
open_semantics.rs: 9 cases (details:8075-8084).sessions.rs: 6 cases plus conformance withSqliteEngine.metadata.rs:expression_desc_partial_index_keys,views_listed_and_attached_schema_listed,rowid_identity_for_keyless_table, and the auto-increment cases fromlib.rs:701-775.- Carried in:
- W1-11
sqlite_emits_no_sqlx_query_events - W3-19
write_batch_foreign_keys.rs(4 cases) - W3-18
dependents_and_source_are_read - W9-16 name
read_only_insert_is_read_only
- W1-11
- All of these run without Docker in the fast job.
Depends on:
- W2-11 (missing pieces)
- W3-06 (present)
- W3-18 (dependents, TableDefinition)
- W3-19 (BEGIN IMMEDIATE, FK suspension in the same
write_batch) - W1-11 (statement logging)
- W9-07 (conformance)
- W4-06 (consumes
FileEndpoint::for_reopen) - W2-17 and W2-18 (UI; downstream)
Spec text amended or stale:
- "replaces lib.rs:15-481 and tests 483-650": now 785 lines, with tests at
lib.rs:618-785. - The index_sql depth-0 splitter becomes sqlparser
CreateIndexwith spans (review). - "W2-14 deletes W9-01's tests/integration.rs": that file does not exist in this crate. The crate has
values.rs,csv_import.rsandscript_plan_matches_sqlite3_complete.rs, and the spec does not mention them. - FD-D11: every build of storage and sqlite needs libclang (
sqlx-sqlite-0.9.0/Cargo.toml:83unbundled = buildtime_bindgen). - W1-11 names
sqlite/src/connect_options.rswhile W2-14 namesopen.rs. Pick one.
Size: about 3,000 lines added and 800 deleted, about 3,800 changed. That is about 1,900 src and 1,100 tests, plus about 250 for porting values.rs and csv_import.rs.
W2-15 · SQL Server driver
Status: not started on v2. The dependency prerequisites are partly done:
- W8-10 pinned tiberius to tiberius-rs
c1d2e741(linux/Cargo.toml:96-102, commitc3af59a01) [C]. - W8-11's
kerberosfeature exists (mssql/Cargo.toml[features] kerberos = ["tiberius/integrated-auth-gssapi"]) [C]. W8-11'stests/krb5_profile.rsdoes not exist: thecrates/drivers/mssql/tests/directory holds onlyintegration.rs[C].
The current driver has these defects, and W2-15 removes them [C]:
config.trust_cert()is unconditional (lib.rs:118), so the certificate is never validated, even with TLS on.EncryptionLevel::Offencrypts the login only (lib.rs:113-117).- One
Mutex<Client>(lib.rs:157). - Every login, password included, goes through
spawn_blocking+block_on(lib.rs:65-66). - A fixed 5 s
CONNECT_TIMEOUTmaps to Transport Timeout (lib.rs:28, 73-81). - It dials its own
TcpStream(lib.rs:147-150). - It stops at the second result set (
lib.rs:418-424). - DateTimeOffset decodes through
DateTime<Utc>, which loses the offset (lib.rs:509-513). That breaksdatetimeoffset_plus_0530_round_trip_keeps_offset. - Routing becomes
Protocol(lib.rs:759) and TLS becomesTlsFailure::Other(lib.rs:752-755). - A typed NULL is always nvarchar (
lib.rs:462).
Under the pinned rev c1d2e74 there is no cancel_query [C _build/cargo-home/git/checkouts/tiberius-*/c1d2e74/src/client.rs:72,121,180,232]. trust_cert_ca replaces the roots with exactly one CA [C src/client/tls_stream/rustls_tls_stream.rs:123-130,148-152], despite its doc comment (src/client/config.rs:143-145).
Design after the amendments (review W2-04/05/06/07/08 deleted, W2-15, W8-10, FB-D16), updated for tiberius 0.13.0:
- Depend on crates.io
tiberius = "0.13"withdefault-features = falseand features["tds73", "rustls", "chrono"]. The kerberos feature maps totiberius/integrated-auth-gssapi, which pulls libgssapi ^0.11. Delete[patch.crates-io], and emptyallow-gitindeny.toml:58. This applies the review's "drop the patch when crates.io 0.13 ships". Check withcargo denythat ring, native-tls and openssl-sys stay out [I: the provider fallback is aws-lc-rs, and native-tls is off with no defaults]. - Login:
transport.open(), thencompat_write(), thenClient::connectunder the login timeout, raced with cancel. - Config:
host(service host),port,database,application_name("TablePro")encryptionNotSupported for Disable and Required otherwisecommand_timeout(None): the 30 s default desyncs any statement longer than 30 shandshake_timeout(Some(login_timeout)), or None plus the outer timeoutreadonlyis never called
- Trust (review interim, now native upstream):
- VerifyFull: default system roots plus
trust_cert_ca(path)(additive in 0.13), plushostname_in_certificatefor the server-name override. - Require:
trust_cert(). - Typed TLS failures come from classifying
Error::Tls(String)text ("coarser classification", per the review). - The spec's
ObservedServerVerifierandtls_client_config(...)are not implementable. Keep an upstream PR fortls_client_configon the watch list.
- VerifyFull: default system roots plus
- Kerberos only:
spawn_blocking(move || handle.block_on(Client::connect(..))), selected against cancel and login_timeout. Password logins run on the async workers. - Routing: the first
Error::Routingreruns once withtransport.reroute. A second one givesProtocol. - MONEY: there is no
money_as_numericin 0.13.0, so MONEY columns are projected asCAST(.. AS decimal(19,4))(SMALLMONEY asdecimal(10,4)) by the SQL Server dialect projection. Editor MONEY keeps F64 and carries an "approximate" label. - Cancel: in-band. Drop the request future (which ends the borrow), then call
client.cancel_query()under cancel_grace, thenIF @@TRANCOUNT > 0 ROLLBACK. Ifcancel_queryerrors (the poisoned flag is set during a write) or the connection is desynced, discard the connection (review: "discard poisoned connections"). - MssqlManager on deadpool:
recyclerunsSELECT @@TRANCOUNTunder probe_timeout. The probe usestimeout_getwith wait ZERO, then falls back to a standalone login.
Remaining work
- Create under
crates/drivers/mssql/src/:driver.rs,capabilities.rs(cfg-selected AUTH_MODES)login.rstls_config.rs(trust mapping and text classification instead of the observed verifier)mssql_manager.rs,connection.rsin_band_request.rs(usescancel_query),cancel.rs,read_only_guard.rs,write_batch.rs,editor_session.rscatalog.rs(movesformat_mssql_type/map_referential_actionand their tests fromlib.rs:606-681)metadata.rs,decode.rs,encode.rs,errors.rs
- Change
lib.rs. - Delete
tests/integration.rs(444 lines). Createtests/integration/{main, decode, tls, cancellation, read_only, sessions, write_batch}.rs. - Cargo:
- Add
deadpool0.13.1 andtokio-util(compat, rt). Addtablepro-netonly ifclassifyis reused. - Remove dev-dependency
rust_decimal. Add dev-dependenciestablepro-test-fixtures(MssqlFixture exists atcrates/test-fixtures/src/mssql_fixture.rs:15-83) andtablepro-test-support.
- Add
linux/Cargo.tomllines 39 and 96-102, anddeny.toml:58.- No
build-linux.ymledit. - The review's hand-rolled list also names
normalize_mssql_default(lib.rs:631-669), to be replaced by sqlparserMsSqlDialect::parse_expr. That sits with W3-08's catalog classification, but this rewrite is where it moves.
Dependency changes (exact):
- tiberius goes from git
c1d2e741(0.12.3) to crates.io 0.13.0, withoutrust_decimal, without[patch], withoutallow-git. - libgssapi moves from 0.8.3 (Cargo.lock:2713) to 0.11.x through the feature.
cargo tree -dmust show a single GSSAPI binding. - Add deadpool 0.13.1.
Tests required:
- Unit:
login::routing_followed_once_then_protocol_errorlogin::config_never_sets_readonly_and_uses_routed_hostread_only_guard::wraps_statement_in_begin_rollbackerrors::tds_number_table- the TLS text classification table (replaces the observation-based table)
capabilities::auth_modes_follow_kerberos_feature- the catalog formatting tests moved from
lib.rs:913-955 - new:
login::command_timeout_disabled
decode.rs: 7 cases.money_max_min_exactandsmallmoneynow go through the CAST projection.tls.rs:verify_full_with_ca_okwithout_ca_unknown_issuerdisable_against_forced_encryption_server_requires_tlsname_mismatch_classified
cancellation.rs: 3 cases plus conformance withMssqlEngine.read_only.rs: 2 cases.sessions.rs: 4 cases.write_batch.rs: 3 cases.- Recommended new cases:
waitfor_40s_completes_without_command_desynccancel_after_dropped_row_stream_discards_client
krb5_profile.rs: the spec says it "stays", but it does not exist. W8-11 still owes it.- All of these are CI-only: there is no arm64 SQL Server image.
Depends on:
- W2-11
- W3-06 and W3-07 (MSSQL dialect, including the MONEY CAST projection and
COUNT_BIG) - W3-08 and W3-18 (
default_constraint_name) - W3-10 (static default drop)
- W8-10 (re-scoped: move to crates.io 0.13.0)
- W8-11 (krb5_profile test missing)
- W9-07, W9-08, W4-06
- The review deletes W2-04 through W2-08; their work is upstream.
Spec text amended or stale:
- The title "on the tiberius fork" and the API line "tiberius fork (c1d2e741 + W2-04..08)".
Client::cancel_pendingbecomesClient::cancel_query.Config::money_as_numeric(true)does not exist; use the CAST interim.tls_client_config(tls_config::observed(...)),ObservedServerVerifier, and the "observation Passed/Failed" classification are not implementable.- "The W2-04 matrix now returns that Error::Tls instead of panicking" (now upstream).
- "replaces mssql/src/lib.rs:19-728 including Mutex (lib.rs:141-143) and CONNECT_TIMEOUT (lib.rs:68-72)": now
lib.rs:157and28/73-81. - "krb5_profile.rs stays": the file is absent.
- New requirement not in the spec:
command_timeout(None).
Size: about 4,500 lines added and 1,400 deleted, about 5,900 changed. That is about 2,700 src and 1,800 tests.
W2-16 · ClickHouse driver
Status: not started on v2. [C]
- Connect uses
clickhouse::Client::default().with_url(..), the crate's own HttpConnector with crate TLS, so there is no transport injection and SSH is impossible (lib.rs:53-91). - Editor and workspace reads run
client.query(&escape_bind_markers(sql))(lib.rs:402-406,787-789) through a hand-rolledLineReader(lib.rs:356-396). - Types are parsed with
base_type/type_is_nullable(lib.rs:459-481) and decoded byjson_to_value(lib.rs:483-534). - Literals are inlined by
bind_placeholders/literal(lib.rs:612-764). - Errors are classified by string matching (
network_error,lib.rs:817-828). - There is no query_id, KILL, write ledger or commit resolution.
Reusable pieces [C]:
clickhouse_code(lib.rs:796-800) and the code table (lib.rs:830-846)ClickHouseFixturewith HTTP and HTTPS ports and a PKI (crates/test-fixtures/src/clickhouse_fixture.rs:89-109)CommitToken::ClickHouseQuery- the backslash-escaping identifier quoting from W3-01 (commit
320e65dfb,crates/core/src/sql_dialect.rs:28-40)
Design after the amendments (review W2-16, FC-D5, D12):
client_factory::base_client:hyper_util::client::legacy::Client::builder(TokioExecutor::new()).pool_idle_timeout(2 s).build(HttpsConnectorBuilder::new().with_tls_config(cfg).https_or_http().enable_http1().wrap_connector(connector)). APIs [Chyper-rustls-0.27.9/src/connector/builder.rs:60,208,252,359].- The TLS config comes from
tablepro_net::tls::build_client_config(crates/net/src/tls/client_config.rs:24-52): rustls-platform-verifiernew_with_extra_roots, with Require unverified. - Then
clickhouse::Client::with_http_client(http).with_url(https?://<service host>:<port>), plus user, password and database, andwith_settingformutations_sync=1,readonly=2,allow_ddl=0[Cclickhouse-0.15.2/src/lib.rs:152,185,382]. - Add
with_product_info.
- Connector: one
TransportConnector: Service<Uri>whosecall()runstransport.open()under connect_timeout. Its IO type is either a newtype overTokioIo<Box<dyn TransportStream>>implementingConnection(spec), or, matching the review's wording, an SSH path that returnsTokioIo<UnixStream>(Connectioncomes free, at hyper-utilhttp.rs:583,598).- Recommendation [I]: use the single transport-backed connector. It covers both DirectTcp (socket2 keepalive) and the SSH Unix socket with one code path of about 60 lines, and it is the "Unix-socket connector for SSH forwards" the review asks for. Avoid a second hyper-util HttpConnector path.
- Editor SQL uses
Client::query_raw[Clib.rs:606].escape_bind_markersis deleted. - KILL QUERY keeps
Client::query("KILL QUERY WHERE query_id = ? ASYNC").bind(id.to_string())on a sessionless control client. It is not rendered by the dialect literal, per the review and the fact-check correction at review:553. query_idusesQuery::with_setting("query_id", uuid)[Cquery.rs:336;lib.rs:783settings::QUERY_ID = "query_id"].- Reads use
fetch_bytes("JSONCompactEachRowWithNamesAndTypes")andtokio::io::AsyncBufReadExt::read_until(b'\n')on theBytesCursor(AsyncBufRead, [Ccursors/bytes.rs:193]).LineReaderis deleted. - Header types go through
clickhouse_types::DataTypeNode::new(&str)[Cclickhouse-types-0.1.3/src/data_types.rs:118].base_type,type_is_nullableandunwrap_typeare deleted. - Row identity and sorting keys use the
system.columnskey flags plus sqlparser.split_key_expression(lib.rs:563-589) is deleted. - Network errors are classified by walking
source()andio::Error::get_ref(tablepro_net::tls::classifyalready walks the chain,classify.rs:6-18). That replaces string matching. fetch_nativeis recorded as rejected (no Decimal, Date family, Enum or Int256).- lz4: decide explicitly. The workspace is
default-features = false, so lz4 is off. With lz4 on,fetch_byteswould request compressed responses [Cquery.rs:258,compression/mod.rs:40-45]. Recommendation [I]: keep lz4 off, and record it in the ADR. JSON over HTTP with keep-alive is CPU-cheap, and lz4 adds lz4_flex and cityhash. - App SQL binding:
bind_placeholdersandliteralare deleted, but the core ClickHouse dialect still renders?placeholders (crates/core/src/sql_dialect.rs:41-47,dialect/engine_dialect.rs:96-110). The review amendment for W3-07 and W3-11 moves app-built values to server-side{pN:Type}params, so the driver sendsquery_raw(sql).param("pN", value)[Cquery.rs:357;Nonebecomes\Npersql/ser.rs:539]. That is a cross-lane dependency on W3-07. - Writes,
resolve_commitand the editor session follow the spec unchanged: probe-first, own query_id per statement,wait_end_of_query=1,max_execution_time=0, thewrite_error_classtable, andsystem.query_log/system.mutationsresolution.
Remaining work
- Create under
crates/drivers/clickhouse/src/:driver.rs,capabilities.rstransport_connector.rsandtransport_io.rs(or merged into one file)client_factory.rs,connection.rsrow_seed.rs,cell_seed.rs(FB-D12)cancel.rs,write_batch.rs,write_error_class.rs,commit_resolution.rs,editor_session.rs,catalog.rs,metadata.rs,errors.rsline_reader.rsshrinks to a smallread_untilhelper, or goes away.
- Change
lib.rs. - Delete
tests/integration.rs(380 lines). Createtests/integration/{main, keys_and_decimals, writes, tls, sessions}.rs. - Cargo:
clickhousewithdefault-features = falseand features["uuid"], pluschronoonly if typedRowstructs keep chrono fields. Removerustls-tls-aws-lcandrustls-tls-webpki-rootsfromlinux/Cargo.toml:37.- Add
clickhouse-types = "0.1.3",hyper-rustls(workspace 0.27, aws-lc-rs/http1/tls12),hyper-util(workspace 0.1, client-legacy/http1/tokio),tower-service0.3,http1,tablepro-net,tokio-util, andsqlparser(workspace) for key expressions. - Keep
serde_json(it backs the JSON cell seeds). - Dev-dependencies:
tablepro-test-fixtures,tablepro-test-support.
deny.tomlwrappers.- No
build-linux.ymledit.
Dependency changes (exact):
- Add
clickhouse-types 0.1.3directly, plushyper-rustls 0.27.9,hyper-util 0.1.20,tower-service 0.3.3andhttp 1as normal dependencies. All are already in Cargo.lock. - The clickhouse features drop TLS. That removes the
webpki-rootsedge [I].
Tests required:
- Unit:
errors::code_prefix_parsingcell_seed::invalid_utf8_string_is_bytesrow_seed::dynamic_column_retry_isolates_invalid_cellwrite_batch::affected_exactly_is_guard_unsupported_without_networkwrite_error_class::unknown_versus_refused_codescommit_resolution::mapping_tabletransport_connector::timeout_maps_connect_phasetransport_connector::future_is_unpin_send- new:
network_error_classified_by_source
keys_and_decimals.rs: 4 cases.writes.rs: 10 cases, includingread_only_insert_164.tls.rs:https_verify_full_ok,name_mismatch,http_port_with_https_server_refused.sessions.rs: 4 cases plus conformance withClickHouseEngine.- Recommended new case:
connects_through_ssh_forward_socketusingOpenSshFixture. It proves the review's Unix connector.
Depends on:
- W2-11
- W3-01 (done, commit
320e65dfb) - W3-06 and W3-07 (server-side param placeholders)
- W3-08 and W3-18 (sorting and partition key flags,
SHOW CREATE TABLE) - W9-07, W9-08, W4-06
- Later: W5-14 edits
keys_and_decimals.rs.
Spec text amended or stale:
- "deleting bind_placeholders (598-712), literal (714-741), json_to_value (476-520), map_clickhouse_error (772-800)": now
612-726,728-764,483-534,802-846. - "escape_bind_markers stays" is reversed by
query_raw, so it is deleted. - "Crate TLS features stay off" contradicts the current workspace manifest, which has to change.
- "KILL QUERY ... with the UUID rendered by the ClickHouse dialect's string literal function" becomes client-side
bind(review). - "clickhouse-types 0.1" / "0.1.2 DataTypeNode::new" become 0.1.3.
- "fetch_bytes ... feeds LineReader" becomes
read_until. - The spec's
TransportConnector/TransportIoagainst the review's "~15-line Service returning TokioIo" needs reconciling (recommendation above).
Size: about 4,200 lines added and 1,500 deleted, about 5,700 changed. That is about 2,600 src and 1,600 tests.
W3-09 · Lossless decode and encode (MySQL and SQLite parts only)
Status: partly done. Commit e861c993b ("read every value in the form the server sent it") added the following [C]:
- MySQL (
crates/drivers/mysql/src/lib.rs:374-482):- three-way Null / value / Undecodable (
decode,decode_text,decode_bytes) - DECIMAL from text into
SqlDecimal(:389) - TIME into
SqlTime(:393,:474-482) - BIT into
Bits(:409-412) - JSON into
JsonText(:404-406) - FLOAT into
Float32(:384) - unsigned types into
UInt(:382-383)
- three-way Null / value / Undecodable (
- SQLite: decode by runtime storage class (
crates/drivers/sqlite/src/lib.rs:387-419). - The core
Value,SqlDecimal::from_scaled_i128andParamTypeexist (W3-05) [C].
Gaps against the spec:
- MySQL [C]:
- There is no
decode_cell(MySqlValueRef, MySqlWireFormat, MySqlTimestampMode).MySqlValueRef::format()ispub(crate)[Csqlx-mysql-0.9.0/src/value.rs:36], so the driver passes the format in. - Integer signedness is decided by type-name text, not
<u64 as Type<MySql>>::compatible. That function checks the UNSIGNED flag [Ctype_info.rs:100-101] and covers Year [Ctypes/uint.rs:19-27]. - Narrow unsigned and YEAR decode to
UInt(lib.rs:382-383). The spec saysIntthroughi64::try_from. - BOOLEAN decodes to
Bool(lib.rs:378). The spec saysInt, so a stored 2 stays 2. - The string family is chosen by type name (
lib.rs:413-416), so aVARCHAR ... COLLATE utf8mb4_bincolumn, which sqlx names VARBINARY, decodes as Bytes. The spec uses<str as Type<MySql>>::compatible, which is false only forCollation::BINARY[Ctypes/str.rs:25-43]. - DATE, DATETIME and TIMESTAMP use the chrono decoders. A zero date becomes
Undecodable; the spec saysOther. There is nomysql_temporalbinary-length parser (0/4/7/11). - TIMESTAMP is always UTC (
:397-401). There is noSessionLocalmode for editor sessions. - Encoding binds by
Value, not byParamType(lib.rs:484-511).
- There is no
- SQLite [C]:
- Decode adds declared-type refinements: BOOLEAN 0/1 becomes
Bool, and DATE, TIME and DATETIME text is parsed (lib.rs:397-417). The spec says "decode by runtime storage class throughtype_info().name()" (see open question Q4). - Encoding binds by
Value(lib.rs:476-508). The spec binds byParamType, or by Value for dynamic columns.
- Decode adds declared-type refinements: BOOLEAN 0/1 becomes
Remaining work
- Create:
crates/drivers/mysql/src/{decode.rs, mysql_wire_format.rs, mysql_timestamp_mode.rs, mysql_temporal.rs, encode.rs}crates/drivers/sqlite/src/{decode.rs, encode.rs}crates/drivers/sqlite/tests/decode.rs- MySQL integration cases inside
crates/drivers/mysql/tests/integration/(not the deletedtests/integration.rs; reconcile plan.md:1078)
- Delete
extract_valueandbind_*_paramsin both drivers. - No new dependencies. sqlx 0.9.0 and chrono are already present.
Tests required:
- MySQL unit:
mysql_temporal_binary_lengths_0_4_7_11mysql_zero_date_is_other_in_both_formatsnarrow_unsigned_converts_to_int_checked
- MySQL integration (8.4 and MariaDB 11.4):
unsigned_max_values_decode_in_both_protocolsint_unsigned_max_id_row_edits_through_plan_savebin_collation_text_decodes_as_textdecoded_variants_match_catalog_kinds- DECIMAL(65,30), BIT(10), YEAR, GEOMETRY, TIME -838:59:59,
tinyint(1)=2, zero date gives Other bigint_unsigned_max_id_edit_affects_one_row(the name the W9-16 regression map cites)
- SQLite integration (
sqlite/tests/decode.rs):count_sum_and_expression_columns_are_typeddecimal_declared_column_decodes_storage_classstrict_table_round_trip
- Existing tests to update:
crates/drivers/mysql/tests/integration.rs:280wire_only_types_read_backand:110value_roundtrip_all_types(they userust_decimal)crates/drivers/sqlite/tests/values.rs:40-74(assertsBoolandDate; changes if Q4 resolves to pure storage class)
Depends on:
- W3-05 (done)
- W3-06 (ParamType per column; present in core
engine_dialect.rs) - W3-08 (catalog kinds, for
decoded_variants_match_catalog_kinds) - W8-12 (done, commit
19b8f4e49) - W2-12 (the PG part, outside this lane)
- W2-13 and W2-14 own the files these modules land in.
Spec text amended or stale:
- The file list says
crates/drivers/mysql/tests/integration.rsis edited, but W2-13 deletes it. Usetests/integration/decode.rs. - The PG amendments (FB-D5, postgres-protocol decoders) do not touch this lane.
Size: about 1,300 lines changed (about 700 src and 600 tests). These lines live inside W2-13 and W2-14's files, so do not add them twice to the totals.
Implementation order
Prerequisite from other lanes: W2-11 must put the Connection v2 trait, DatabaseDriver v2, ConnectOptions v2 (target, credentials, workspace settings, environment) and APPLICATION_NAME into core first. Add W3-18's TableDefinition, W3-19's ForeignKeyMode and W9-07's conformance skeleton too, so every driver compiles against the final trait once.
- Dependency groundwork commit (W8-10 re-scoped). Move tiberius to crates.io 0.13.0:
default-features = false,["tds73", "rustls", "chrono"], delete[patch.crates-io], emptyallow-git. Adapt the v1 mssql driver minimally:command_timeout(None), and keeptrust_certbehaviour for now. Add deadpool 0.13.1 to the workspace. Why first: SQL Server can only be tested in CI, and its upstream semantics (cancel, poisoning, timeouts) are the largest unknown, so CI feedback should start early. - W2-14 + W3-09 SQLite. No network and no Docker. It exercises Connection v2, OwnedCalls (statement-scoped stop), WriteLedger (Transactional) and EditorSession end to end, with the fastest test loop. Port
values.rsandcsv_import.rsoff:memory:. - W2-13 + W3-09 MySQL. Docker is available locally (arm64 MySQL and MariaDB). It introduces the deadpool manager pattern (reused by MSSQL), connection-scoped KILL QUERY, and route-based SSH over the OpenSSH socket.
- W2-16 ClickHouse. Docker is available locally. It needs W3-07's server-side param placeholders. It adds the transport connector, the StatementByStatement ledger and commit resolution.
- W2-15 SQL Server, iterated in CI. It reuses the MySQL manager shape and the in-band variant of OwnedCalls.
Optional mitigation [I]: build each v2 driver additively in new modules beside the v1 lib.rs, unexported, and flip the exports in the single switch commit together with W4-06. That keeps the atomic commit reviewable without breaking the build between commits.
Open questions and risks
- Q1 (blocker): core contract missing. None of the five tasks can compile against v2 until W2-11 lands the trait,
DatabaseDriver::capabilities/connect(options, cancel)and ConnectOptions v2. W3-18 and W3-19 add trait methods and fields (fetch_table_definition,WriteBatch.foreign_keys). There is also a conflict: W2-11 hasWriteBatchcarryingtable, which MySQL's engine lookup needs and core has today; W3-19's text omitstable. Keeptable. - Q2 (blocker for W2-16): ClickHouse parameter binding. The core dialect renders
?for ClickHouse (sql_dialect.rs:41-47), and the switch deletes the driver's literal inliner. W3-07 must render{pN:Type}server-side params, or the driver has no safe binding path. - Q3 (risk, W2-15): tiberius 0.13 cancellation semantics. The docs say "a result stream dropped mid-response cannot be recovered", but also that
cancel_querydrains to DONE_ATTN so the connection can be reused. The spec testpooled_query_bound_waitfor_deadline_server_cancelled_client_reusedrequires reuse. It likely holds for WAITFOR, where no rows are in flight; it may not hold mid-row-stream, which forces a discard. Unverified locally (CI only). Also:command_timeoutdefaults to 30 s and poisons the connection, so leaving it set breaks long editor queries. - Q4 (decision, W3-09 SQLite): declared-type refinements. Keep the BOOLEAN-to-Bool and DATE/DATETIME text parsing (current behaviour, asserted by
tests/values.rs:67-71), or decode by storage class only as the spec says? Parsing DATETIME text and re-encoding it can change its spelling (for exampleT/Zforms), so a row-key predicate built from the decoded value may stop matching [I]. Recommend storage class only, and let the column kind present the value. - Q5 (decision, W2-16): connector shape. The spec has one transport-backed
TransportConnectorfor both routes; the review has a Unix-socket connector for SSH. Recommend the single transport-backed connector. Also decide lz4: recommend off, recorded in the ADR. - R1 (W2-13): direct TCP on sqlx 0.9.0 has no TCP_NODELAY (#4336 is unreleased) and no keepalive (#4355). Possible Nagle latency, and dead peers are found only by the health probe. FC-D3 cannot be met. Record a dated watch item. Do not pin sqlx to git:
deny.tomlallows only tiberius. - R2 (W2-13): trust sets differ by engine. MySQL uses sqlx's webpki roots plus PEM. The corporate-CA PEM bundle (
mysql_ca_bundle_pem) already exists, but the review keeps it only if system CAs are required. This needs a decision and documentation. - R3 (W2-14): unsafe interim.
sqlite3_get_autocommitrelies onas_raw_handle, which is semver-exempt in sqlx [Cconnection/mod.rs:360-370]. The direct libsqlite3-sys range must widen together with sqlx (main already allows<0.39). Every sqlite and storage build needs libclang. - R4 (W2-14): sqlparser spans are line/column, so expression keys need offset conversion. Fall back to raw text on empty spans. sqlparser breaks its API every 3 to 4 months.
- R5 (W2-16): resolution heuristics.
system.query_logmay be disabled or inaccessible, which makes results Unresolvable. Matchingsystem.mutationsby time window is heuristic. Whether KILL QUERY accepts server-side params is unverified, so keep client-sidebind. - R6 (W2-15): W8-11 is incomplete.
tests/krb5_profile.rswas never added. libgssapi moves from 0.8 to 0.11 with tiberius 0.13. Checkcargo tree -dandcargo deny(ring and native-tls bans) after the bump. - R7 (all): switch size. The four drivers plus W3-09 add up to about 20,000 changed lines (W2-13 about 5,400, W2-14 about 3,800, W2-15 about 5,900, W2-16 about 5,700; W3-09's about 1,300 is inside those). This is on top of the core and app lanes, in one atomic commit. SQL Server cannot be run on the developer machine (arm64).
- R8 (W2-13 and W2-14): file names overlap with W1-11.
connect_options.rs(W1-11) againstphysical.rs/open.rs(W2-13/W2-14). Settle the names before work starts sodisable_statement_logging()has one home.
Analysis file
|
| File | Lines | Component impl | ComponentSender | FactorySender | relm4::Sender | FactoryVecDeque refs (sites) | relm4::channel | Command | Rewritten by my tasks |
|---|---|---|---|---|---|---|---|---|---|
| lib.rs | 182 | RelmApp::from_app (99-111) |
no (W4-13 at Step 40, or the migration task) | ||||||
| services/change_tracker.rs | 1093 | 2 (159, 187) | W4-07, W4-08 | ||||||
| ui/app/mod.rs | 1772 | App SimpleComponent (628-629) | 3 | 1 (1335) | 7 (2 sites: 971 sidebar, 1165 connections popover) | edited by W4-06/07/09/10/11/12 and W6-17; the root stays Relm4 until W4-13/W4-14 | |||
| ui/app/browse.rs | 294 | 5 | 3 | deleted by W4-10 | |||||
| ui/app/connection.rs | 733 | 19 | 5 | W4-06, W6-13, W6-17 | |||||
| ui/app/import.rs | 231 | 4 | 1 | W4-06 call sites only | |||||
| ui/app/row_ops.rs | 222 | 2 | 1 | W4-06, W4-12, W5-02 | |||||
| ui/app/status_pages.rs | 257 | 6 | 1 | W4-06 | |||||
| ui/app/structure.rs | 720 | 12 | 5 | W4-06, W4-07, W4-10, W4-12 | |||||
| ui/app/workspace_tabs.rs | 1410 | 17 | W4-06/07/09/10/11/12, W6-12, W6-17 | ||||||
| ui/browse_tab.rs | 2985 | BrowseTab SimpleComponent (1342) | 5 | 1 | 2 (1493, 1776) | yes: W4-10 rewrites it (also W4-06/07/08/12, W5-02/03) | |||
| ui/connect_dialog.rs | 1261 | ConnectDialog Component (161-162) | 3 | 2 | yes: W6-13 deletes and rebuilds it | ||||
| ui/editor/mod.rs | 1279 | SqlEditor SimpleComponent (118-119) | 4 | 3 | 1 (439) | 1 | yes: W6-12 rewrites it | ||
| ui/editor/statement_cursor.rs | 122 | imports only | W6-15 (swap imports) | ||||||
| ui/grid.rs | 2414 | 13 | yes: W5-01 moves it, W5-02 replaces the senders | ||||||
| ui/filter_strip.rs | 731 | imports only | W4-09 (swap imports) | ||||||
| ui/ssh_section.rs | 242 | imports only | yes: W6-13 | ||||||
| ui/structure_tab/mod.rs | 1110 | StructureTab SimpleComponent (495) | 7 | yes: W4-10 (also W4-09, W4-12) | |||||
| ui/structure_tab/{columns,fks,indexes}.rs | 418/68/65 | 2/2/2 | converted with StructureTab in W4-10 | ||||||
| ui/structure_tab_dialogs.rs | 404 | 3 | converted with StructureTab in W4-10 | ||||||
| ui/history_dialog.rs | 1086 | HistoryDialog Component (91) | 8 | 3 | W4-06 changes one line (120); conversion is left to the migration task | ||||
| ui/connection_row.rs | 470 | ConnectionRow FactoryComponent (58-59) | 4 | no | |||||
| ui/sidebar_row.rs | 318 | SidebarRow FactoryComponent (55-56) | 4 | 1 | no (W4-10 feeds it, App owns the factory) | ||||
| ui/welcome_view.rs | 280 | WelcomeView SimpleComponent (45) | 2 | 4 (1 site: 61) | no | ||||
| ui/saved_queries_dialog.rs | 646 | SavedQueriesDialog Component (61) | 9 | 3 | no | ||||
| ui/import_dialog.rs | 483 | ImportCsvDialog Component (83) | 4 | no | |||||
| ui/export_dialog.rs, ui/connection_list.rs | 440/251 | imports only | no |
Totals: 30 files reference relm4. There are 11 component impls (9 components, 2 factories), 119 ComponentSender, 8 FactorySender, 21 relm4::Sender, 3 FactoryVecDeque sites, 3 relm4::channel and 25 command sites.
Folding plan (FA-D1 amendment):
- SqlEditor (W6-12), ConnectDialog (W6-13), BrowseTab and StructureTab with its four sub-files (W4-10), and the grid (W5-01/W5-02) become
glib::Objectsubclasses with#[derive(gtk::CompositeTemplate)].uifiles indata/resources/ui/, registered indata/resources/resources.gresource.xml. ui/preferences_dialog/imp.rs:10-11withdata/resources/ui/preferences-dialog.uiis the in-tree precedent.- Output enums become signals. Each converted child emits signals, and App connects them and forwards into its Relm4 input sender until W4-13/W4-14 replace the root.
- Enable
gtk4featurexml_validation(gtk4-0.11.4/Cargo.toml:171). The direct feature floors from the review are already declared (Cargo.toml:62-66). - The migration task still owns: lib.rs, the App root, welcome_view, connection_row, sidebar_row, connection_list, history_dialog, saved_queries_dialog, import_dialog, export_dialog and app/import.rs.
3. App files that consume v1 driver types (all confirmed by grep)
- Deleted by W4-06:
services/database_service.rs(Connection, ConnectOptions, DatabaseDriver, SshTunnel, globalinstance())services/connection_service.rs(DriverRegistry, ConnectOptions, Connection, ReadOnlyConnection, TableInfo, AuthMode, russh types)services/connection_monitor.rs(Connection::ping, SshTunnel)
- Composition root:
lib.rs(DriverRegistry,build_registryat 120-128). - Global
database_service::instance()call sites:ui/app/browse.rs:49,128,159ui/app/connection.rs:38,55,125,192,533ui/app/import.rs:19,23,63,116ui/app/ran_statements.rs:35ui/app/row_ops.rs:29ui/app/status_pages.rs:88,115,160ui/app/structure.rs:125,304,486,574,672ui/app/workspace_tabs.rs:423,971ui/connect_dialog.rs:843-848ui/editor/mod.rs:673,695,713,735ui/history_dialog.rs:120
- v1 methods:
execute_in_transaction/reports_rows_affectedinui/app/row_ops.rsddl_is_transactional,fetch_indexes,fetch_foreign_keys,executeinui/app/structure.rsfetch_rows,query_params,fetch_columnsinui/app/browse.rsqueryinui/editor/mod.rs:846default_port(),is_file_based(),supports_integrated_auth(),display_name()inui/connect_dialog.rs:607-625
- v1 metadata types:
TableInfo:ui/app/{mod,connection,structure,workspace_tabs}.rs,ui/connect_dialog.rs,ui/sidebar_row.rsIndexInfoandForeignKeyInfo:ui/app/{mod,structure}.rs,ui/structure_tab/{mod,indexes,fks}.rs,ui/structure_tab_dialogs.rs
- v1 SQL builders keyed by driver id:
sql_dialect::quote_ident/BuildSqlErrorinservices/change_tracker.rs,ui/app/browse.rs,ui/error_text.rs:2sql_ddlinservices/structure_tracker.rs,ui/app/{mod,structure}.rs,ui/structure_tab/{mod,columns}.rs,ui/structure_tab_dialogs.rs
QueryResultproduced by v1 drivers:ui/app/{browse,mod}.rs,ui/browse_tab.rs,ui/editor/mod.rs,ui/export_dialog.rs,ui/grid.rs.SavedConnection.use_tls/ host-port shape (W1-11's storage change):services/secret_labels.rs,session/session_key.rs,ui/connection_list.rs,ui/connection_row.rs,ui/welcome_view.rs,ui/connect_dialog.rs,tests/secret_save_flow.rs- outside the app:
crates/storage/src/connections.rs:22,crates/storage/tests/isolation.rs
- Test doubles still on v1:
crates/test-support/src/fake_connection.rs:6andfake_driver.rs:29-42.
Analysis file
|
Analysis file
|
In a debug build the tiberius connect future, copied into every caller up to the pool, took 2.09 MB of a 2 MiB thread stack. Each MSSQL docker test aborted, skipped Drop, and left its container running until the CI runner ran out of memory.
tiberius passes @params to sp_executesql even when empty, and SQL Server then rejects CREATE VIEW and CREATE TRIGGER with Msg 156.
chrono has no %.7f, so the formatter failed and the test panicked.
…hops OpenSSH before 9.6 pastes a -J hop into a proxy command run by sh -c, so an imported jump host such as $(cmd) ran on connect.
A ProxyJump hop prompts through the same askpass first, and was handed the destination's password. The owner is the user@host ssh -G resolves; any other password prompt asks the user and names the host asking.
After ALTER TABLE ADD COLUMN, SELECT * in the same tab panicked the sqlx worker: the cached statement still listed the old columns.
On MyISAM or during DDL the server may have run a statement before the connection dropped; the ledger called it refused and told the user nothing applied.
tiberius sends datetime2(7), so a datetime key of .003 compared as .0033333 and the save failed its row guard.
Drivers read XML back as text, but an edit became Value::Other, which a SQL Server text parameter refuses, so no XML cell could be saved.
PostgreSQL has no LIKE for json, jsonb, xml or enums and no = for json or xml; SQL Server has neither for xml. The dialect now names the text form the filter compares.
Opening another connection tore the workspace down without the check Disconnect makes. Both now also count open editor transactions, which the server rolls back when the tab's connection closes.
…s#459 tiberius 0.13 drops a connection whose attention lands while the server is still waiting. The fix is tiberius-rs/tiberius#460.
…nswer A MariaDB max_statement_time stop is a reply, so the write did not apply; only a write whose reply never came is unknown.
Why
The Linux app ran on the first driver contract: host and port connect options, one shared
DatabaseServiceand an interim russh tunnel. SSH ignored~/.ssh/configand agents, nothing asked for a password the keyring lacked, and imported connections could not connect.Change
sessioncrate.Verified
Each crate's unit and docker suites pass, SQL Server's included. A local run reproduced the CI crash: a debug-build stack overflow leaked SQL Server containers until the runner ran out of memory.
Not in this PR