Skip to content

Feat/mssql support#4200

Closed
pabl-o-ce wants to merge 33 commits intolaunchbadge:mainfrom
pabl-o-ce:feat/mssql-support
Closed

Feat/mssql support#4200
pabl-o-ce wants to merge 33 commits intolaunchbadge:mainfrom
pabl-o-ce:feat/mssql-support

Conversation

@pabl-o-ce
Copy link

Does your PR solve an issue?

Delete this text and add "fixes #(issue number)".

Do not just list issue numbers here as they will not be automatically closed on merging this pull request unless prefixed with "fixes" or "closes".

Is this a breaking change?

Delete this text and answer yes/no and explain.

If yes, this pull request will need to wait for the next major release (0.{x + 1}.0)

Behavior changes can be breaking if significant enough.
Consider Hyrum's Law:

With a sufficient number of users of an API,
it does not matter what you promise in the contract:
all observable behaviors of your system
will be depended on by somebody.

Introduces a new `sqlx-mssql` crate that wraps the tiberius TDS protocol
driver (v0.12) behind sqlx's trait system, enabling SQL Server connectivity
with the same ergonomic API as the existing MySQL, PostgreSQL, and SQLite
drivers.

Key implementation details:
- Full Database/Connection/Executor/Row/Value trait implementations
- Connection via tiberius::Client with runtime-agnostic socket bridging
- Eager result collection (tiberius QueryStream borrows &mut Client)
- Transaction support with MSSQL-specific savepoint syntax
- Type mappings: bool, u8, i8, i16, i32, i64, f32, f64, String, Vec<u8>
- Any driver integration and migration support
- Test infrastructure with TestSupport for #[sqlx::test]
- sp_describe_first_result_set for statement metadata and nullability
- Fixes to existing MSSQL test files (imports, placeholders, lifetimes)

Author: Pablo Carrera <pabloce@poscye.com>
Enable native chrono type handling for MSSQL datetime columns,
eliminating the need for CONVERT(VARCHAR) workarounds. Supports
NaiveDateTime, NaiveDate, NaiveTime, and DateTime<Utc> by converting
between tiberius internal datetime structs and chrono types.

Author: Pablo Carrera <pabloce@poscye.com>
Enable UNIQUEIDENTIFIER columns via uuid::Uuid and DECIMAL/NUMERIC/MONEY
columns via rust_decimal::Decimal, both feature-gated behind their
respective cargo features.

Author: Pablo Carrera <pabloce@poscye.com>
Wire up tiberius/time and tiberius/bigdecimal feature flags and implement
Type/Encode/Decode for time::{Date, Time, PrimitiveDateTime, OffsetDateTime},
bigdecimal::BigDecimal, and Json<T>. Uses a ColumnDataWrapper newtype to
bridge tiberius's IntoSql gap for time types and BigDecimal (version mismatch).
JSON is stored as NVARCHAR since SQL Server has no native JSON column type.

Author: Pablo Carrera <pabloce@poscye.com>
…erives, and test attributes

Add tests for all implemented Encode/Decode types (uuid, chrono, time,
rust_decimal, bigdecimal, json, bytes), error kind mapping, derive macros
(weak enums, transparent types), and #[sqlx::test] attribute with
migrations and fixtures.

Author: Pablo Carrera <pabloce@poscye.com>
…lx-mssql

Add MssqlSslMode with 4 variants (Disabled, LoginOnly, Preferred, Required)
mapping to all tiberius EncryptionLevel options. Add MssqlAdvisoryLock API
using sp_getapplock/sp_releaseapplock for application-level distributed
locking. Enable f64/Decimal/BigDecimal decoding from MONEY/SMALLMONEY columns.

Author: Pablo Carrera <pabloce@poscye.com>
…lx-mssql

Add MssqlIsolationLevel enum with begin_with_isolation() for typed
transaction isolation control. Surface tiberius application_intent
(read-only routing) and trust_cert_ca options in MssqlConnectOptions.
Wrap tiberius BulkLoadRequest as MssqlBulkInsert for high-performance
data loading via the TDS INSERT BULK protocol.

Author: Pablo Carrera <pabloce@poscye.com>
… sqlx-mssql

- Add Windows, Integrated, and AAD token authentication support with
  cfg-gated features matching tiberius (winauth, integrated-auth-gssapi)
- Override format_placeholder to produce @p1, @p2, ... instead of ? so
  QueryBuilder works correctly with MSSQL parameterized queries
- Add MssqlXml newtype wrapper for SQL Server XML columns with Type,
  Encode, and Decode implementations
- Add URL parsing for auth and token query parameters with roundtrip tests

Author: Pablo Carrera <pabloce@poscye.com>
Register MSSQL in the macro system (cfg gate, impl_database_ext,
FOSS_DRIVERS) so query!()/query_as!() work with MSSQL databases.
Add missing Any driver type mappings for NULL, BIT, MONEY, SMALLMONEY,
and DECIMAL/NUMERIC to prevent AnyDriverError at runtime.

Author: Pablo Carrera <pabloce@poscye.com>
…ECIMAL precision for MSSQL

Wire MSSQL into docker-compose and CI workflow, support no_tx migrations
following the Postgres pattern, extract column origin from
sp_describe_first_result_set, map date/time/UUID types to Text in the Any
driver, and preserve DECIMAL precision/scale by using a base_name() helper
for type matching.

Author: Pablo Carrera <pabloce@poscye.com>
…d MSSQL examples

Add MssqlAdvisoryLockGuard with Deref/DerefMut, release_now(), and leak()
methods, mirroring the Postgres advisory lock guard pattern. The guard
logs a warning on drop if not explicitly released, since MSSQL connections
cannot queue deferred commands.

Add chrono::DateTime<FixedOffset> Type/Encode/Decode for DATETIMEOFFSET
columns, preserving timezone offset information instead of converting to
UTC. Existing NaiveDateTime and DateTime<Utc> decoders remain backward
compatible by handling the new internal variant.

Add examples/mssql/todos/ with a CLI app demonstrating basic CRUD
operations using MSSQL parameter syntax and OUTPUT INSERTED.

Author: Pablo Carrera <pabloce@poscye.com>
… _persistent flag for MSSQL

Fix Money4 (SMALLMONEY) being incorrectly mapped to "MONEY" instead of
"SMALLMONEY", enabling the Any driver's existing SMALLMONEY handling.
Add comprehensive type tests (integer edge cases, Unicode, large strings,
binary, NULL, XML, DateTime<FixedOffset>, SMALLMONEY) and integration
tests (multiple result sets, column metadata, error recovery, many
parameters, isolation levels, advisory lock guard).

Author: Pablo Carrera <pabloce@poscye.com>
…ion lock

- Escape `]` and `'` in database/schema names interpolated into DDL
  in migrate.rs and testing/mod.rs to prevent SQL injection
- Add SMALLMONEY to compatible() for rust_decimal and bigdecimal types
- Wrap sp_getapplock in DECLARE/THROW to surface lock failures as SQL errors
- Add compatible() overrides for DATE, TIME, and UNIQUEIDENTIFIER types
- Fix swapped Real/Double null type mappings in Any arguments
- Replace panicking expects with proper Error returns in executor
- Add migration test harness and sample migrations for MSSQL

Author: Pablo Carrera <pabloce@poscye.com>
…alculation

Closes #1

Author: Pablo Carrera <pabloce@poscye.com>
Author: Pablo Carrera <pabloce@poscye.com>
…conversion

Dates before the TDS epoch (0001-01-01) produced negative day counts that
silently wrapped via `as u32`, causing corrupt data or a panic inside
`tiberius::time::Date::new()`. Add `days_since_epoch_to_u32()` helper that
returns `Error::Encode` for negative or out-of-range values, and replace
all four unsafe cast sites.

Closes #2

Author: Pablo Carrera <pabloce@poscye.com>
…validated conversion

Adds offset_minutes_to_i16 helper that validates the offset fits within
SQL Server's -840..=840 minute range, returning Error::Encode instead of
silently truncating. Follows the same pattern as days_since_epoch_to_u32.

Author: Pablo Carrera <pabloce@poscye.com>
Apply bracket-quoting for identifier contexts and single-quote escaping
for string literal contexts across all 6 interpolation sites, consistent
with existing escaping in create_database, drop_database, and
create_schema_if_not_exists.

Closes #4

Author: Pablo Carrera <pabloce@poscye.com>
…_if_not_exists

Split the single `escaped` variable into separate variables for each SQL
quoting context, preventing cross-contamination between bracket-quoted
identifiers ([...]) and single-quoted string literals ('...').

Closes #5

Author: Pablo Carrera <pabloce@poscye.com>
…ta with Result propagation

Return Result<MssqlData, Error> instead of panicking on invalid data from
tiberius. Fixes and_local_timezone().unwrap() panic on ambiguous/invalid
DateTimeOffset, and guards against silent u8 truncation in
time_from_sec_fragments with an upfront bounds check. All helper functions
(chrono_date_from_days, time_date_from_days, time_from_sec_fragments) now
return Result and use checked arithmetic.

Author: Pablo Carrera <pabloce@poscye.com>
…terized queries + QUOTENAME()

Replace fragile client-side .replace() escaping in create_database,
drop_database, and create_schema_if_not_exists with parameterized
queries (@p1) and SQL Server's built-in QUOTENAME() function for
server-side identifier escaping via sp_executesql.

Author: Pablo Carrera <pabloce@poscye.com>
…column_data_to_mssql_data

The wildcard `_ => Ok(MssqlData::Null)` silently coerced unhandled
`Some(...)` variants (e.g. Xml, Guid without uuid, Numeric without
rust_decimal) to NULL, causing silent data loss. Enumerate all 18
`None` variants explicitly and error on unhandled `Some(...)` values.

Author: Pablo Carrera <pabloce@poscye.com>
…ation

Closes #9

Author: Pablo Carrera <pabloce@poscye.com>
…check

Extract bigdecimal_to_numeric() helper that normalizes negative exponents
via with_scale(0) (matching tiberius's own to_sql! pattern) and tightens
the scale limit from >38 to >37 to match tiberius's assert!(scale < 38).

Add 10 unit tests covering negative exponents, boundary values, silent
truncation at scale=256, and the off-by-one at scale=38.

Author: Pablo Carrera <pabloce@poscye.com>
Replace string-interpolated SQL with tiberius::Query parameterized
bindings in prepare_with/describe to eliminate Unicode homoglyph
injection edge case. Also log sp_describe_undeclared_parameters errors
instead of silently swallowing them.

Closes #10

Author: Pablo Carrera <pabloce@poscye.com>
…SQL escaping in test harness

- Map Datetime4 to SMALLDATETIME instead of DATETIMEOFFSET
- Validate rust_decimal scale <= 37 before u8 cast to prevent silent truncation
- Replace client-side .replace(']',"]]") escaping with parameterized QUOTENAME(@p1) + sp_executesql in testing/mod.rs

Author: Pablo Carrera <pabloce@poscye.com>
…arnings

- Add #[allow] annotations with SAFETY comments for all guarded integer
  casts that violated crate-level deny lints (cast_possible_truncation,
  cast_possible_wrap, cast_sign_loss)
- Parameterize advisory lock mode via @p2 instead of string interpolation
- Parameterize migrate table existence check via @p1 while preserving
  atomic single-statement DDL
- Change time_date_from_days to accept i64 with i64::from() at call
  sites to eliminate cast_sign_loss under --features time
- Remove unnecessary as u64 casts on Time::increments() calls
- Remove useless .into() on Error::Protocol(format!(...)) calls
- Extract build_columns_from_describe_rows helper to deduplicate
  prepare_with/describe column-building logic
- Fix minor issues: needless_borrow, needless_lifetimes, doc-test
  reborrows, todo!() -> error, uuid encode simplification, unused atoi
  dep, incorrect doc comment, redundant .into_iter()

Author: Pablo Carrera <pabloce@poscye.com>
… lossless casts

- Fix panic on multi-byte UTF-8 in catch-all error truncation by using
  is_char_boundary to retreat to a valid boundary
- Handle ColumnData::Xml(Some(...)) explicitly, converting to MssqlData::String
  via XmlData::into_string()
- Take ColumnData by value instead of by reference, eliminating allocations
  for String/Binary/Xml variants (into_owned + move instead of to_string/to_vec)
- Replace `as u64` with u64::from() for lossless widening casts in time encoding
- Extract repeated .time() calls into local bindings for clarity

Author: Pablo Carrera <pabloce@poscye.com>
Transform the feature overview into a self-contained guide covering
connection pooling, query patterns, FromRow/derive macros, error handling,
OUTPUT INSERTED, stored procedures, nested transactions, and pool callbacks.
Restructure sections to follow a developer's learning path, add recommended
feature flag sets, and correct begin_with_isolation usage and advisory lock
drop behavior.

Author: Pablo Carrera <pabloce@poscye.com>
Return Result from build_url(), document expect() invariants for UTC
offsets and epoch dates, safe UTF-8 truncation, lossless numeric casts,
parameterized sp_describe calls, and resolve all clippy deny violations.

Author: Pablo Carrera <pabloce@poscye.com>
@pabl-o-ce pabl-o-ce closed this Mar 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant