Add mssql-python connection-string parity attributes to ODBC parser#147
Add mssql-python connection-string parity attributes to ODBC parser#147Vahid-b wants to merge 2 commits into
Conversation
Recognize and act on the canonical ODBC Driver 18 keywords that mssql-python normalizes to before handing the string to the driver: ServerSPN, ApplicationIntent, MultiSubnetFailover, ConnectRetryCount, ConnectRetryInterval, KeepAlive, KeepAliveInterval, IpAddressPreference, PacketSize, HostnameInCertificate, and ServerCertificate. Map the msodbcsql-native Addr/Address synonyms to Server (previously silently dropped). Wire the new fields into the mssql-tds ClientContext and EncryptionOptions, converting KeepAlive seconds to milliseconds and ServerCertificate to a path. Enumerated keys (ApplicationIntent, MultiSubnetFailover, IpAddressPreference) validate their values and hard-reject invalid input (E_FAIL); integer keys parse leniently, rejecting only non-numeric or negative input and leaving range clamping to mssql-tds. Collapse the KEY_* consts and the classify_key match into a single MAPPED_KEYS descriptor table. Closes AB#46418. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
📊 Code Coverage Report
Diff CoverageDiff: main...HEAD, staged and unstaged changes
Summary
mssql-odbc/src/api/driver_connect.rs🔗 Quick Links |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
Extends ODBC connection-string parsing and TDS context mapping for mssql-python compatibility.
Changes:
- Adds and validates canonical connection attributes and synonyms.
- Maps parsed values into
ClientContextandEncryptionOptions. - Adds parser documentation and tests.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
mssql-odbc/src/connection/connection_string_parser.rs |
Parses, validates, and formats new attributes. |
mssql-odbc/src/api/driver_connect.rs |
Applies attributes to TDS connection settings. |
mssql-odbc/docs/connection_string_parser.md |
Documents supported attributes and validation. |
mssql-odbc/tests/e2e/tests/driver_connect_test.cpp |
Adds live connection coverage. |
Comments suppressed due to low confidence (1)
mssql-odbc/src/api/driver_connect.rs:327
mssql-tdsdoes not clamp this value:ClientContext::validaterejects packet sizes outside 512–32768 before connecting. Values such asPacketSize=40000therefore fail with 08001, while the PR promises lenient range clamping. Clamp to the actual TDS range before conversion.
context.packet_size = u16::try_from(size).unwrap_or(u16::MAX);
| if let Some(count) = params.connect_retry_count { | ||
| context.connect_retry_count = count; |
There was a problem hiding this comment.
Good catch. Fixed in 68f25f7: the param->ClientContext mapping now lives in apply_connection_params, which clamps ConnectRetryCount to 0-255 (and ConnectRetryInterval to 1-60) at the boundary, so the downstream connect_retry_count + 1 in mssql-tds can no longer overflow. Clamping rather than rejecting mirrors msodbcsql. Added a unit test that feeds u32::MAX and asserts the result stays addable.
|
|
||
| /// Parse a non-negative integer attribute value. Rejects non-numeric or negative | ||
| /// input with `InvalidAttrValue` (msodbcsql `E_FAIL`). Documented per-key ranges | ||
| /// (e.g. PacketSize 512–32767) are intentionally not enforced here; range handling |
There was a problem hiding this comment.
Fixed. The doc comment now reads 512-32768 (matching DefaultClientContextValidator, MAX = 32768) and no longer claims range handling is deferred to mssql-tds.
| `KeepAliveInterval`, `PacketSize`): a non-negative integer. Documented ranges are | ||
| **not** enforced at parse time — out-of-range clamping is left to mssql-tds so the | ||
| parser cannot diverge from the native driver. A non-numeric or negative value is a |
There was a problem hiding this comment.
Updated. The design doc now describes the real behavior: the parser accepts any u32 and the driver clamps out-of-range values when mapping onto the ClientContext (PacketSize 512-32768, ConnectRetryCount 0-255, ConnectRetryInterval 1-60), rather than delegating range handling to mssql-tds.
| if let Some(server_certificate) = &self.server_certificate { | ||
| parts.push(format!("ServerCertificate={server_certificate}")); |
There was a problem hiding this comment.
Addressed in 68f25f7. This formatter only feeds the redacted diagnostic log line (never re-parsed), but the ambiguity was real. Added a quote_odbc_value helper that brace-quotes values containing ;{}= and doubles any inner }, applied to the free-form fields including ServerCertificate, with tests.
| host_name_in_cert: params.host_name_in_certificate.clone(), | ||
| server_certificate: params.server_certificate.as_deref().map(PathBuf::from), |
There was a problem hiding this comment.
Done. Factored the mapping into apply_connection_params(&mut ClientContext, &ConnectionParams) and added direct unit tests asserting the HostnameInCertificate/ServerCertificate -> EncryptionOptions mappings, plus the enum and clamping paths, so they are no longer only reachable via a live-server e2e test.
Address Copilot review on PR #147: - Extract apply_connection_params() so the param->ClientContext mapping is unit-testable, and clamp ConnectRetryCount (0-255), ConnectRetryInterval (1-60), and PacketSize (512-32768) at the TDS boundary. Clamping mirrors msodbcsql and prevents the downstream connect_retry_count + 1 overflow in mssql-tds. - Brace-quote free-form values containing ; { } = in the redacted diagnostic connection-string log so delimiters stay unambiguous. - Fix parse_uint doc (32767 -> 32768) and the design doc's stale 'clamping left to mssql-tds' claim to describe the actual clamping behavior. - Add unit tests for the mapping, clamping, and value quoting. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
| )); | ||
| } | ||
| if let Some(connect_retry_count) = self.connect_retry_count { | ||
| parts.push(format!("ConnectRetryCount={connect_retry_count}")); |
There was a problem hiding this comment.
in msodbcsql ConnectRetryCount and ConnectRetryInterval are range-validated during parse and rejected when out of range, but here we accept and clamp later. Is this clamping intentional here?
https://sqlclientdrivers.visualstudio.com/msodbcsql/_git/msodbcsql?path=/Sql/Ntdbms/sqlncli/odbc/sqlcconn.cpp&version=GBmaster&_a=contents&line=4136&lineStyle=plain&lineEnd=4136&lineStartColumn=9&lineEndColumn=36
https://sqlclientdrivers.visualstudio.com/msodbcsql/_git/msodbcsql?path=/Sql/Ntdbms/sqlncli/odbc/sqlcconn.cpp&version=GBmaster&_a=contents&line=4009&lineStyle=plain&lineEnd=4009&lineStartColumn=1&lineEndColumn=30
| params.keep_alive_interval = Some(parse_uint(lower, value)?); | ||
| } | ||
| ConnAttrKey::IpAddressPreference => { | ||
| validate_attr(lower, value, IP_ADDRESS_PREFERENCE_VALUES)?; |
There was a problem hiding this comment.
msodbcsql falls back unknown values to IPv4First, while this parser currently rejects unknown values with HY024. Is this strict rejection intentional?
I didnt find ODBC spec defined behavior for unknown IpAddressPreference values, so this is a product-parity decision
Description
Finishes connection-string compatibility parity with mssql-python (feature AB#46372) by extending the
SQLDriverConnectparser to recognize and act on the full canonical ODBC Driver 18 keyword set that mssql-python normalizes to before handing the string to the driver.Newly acted-on keywords, each wired into the mssql-tds
ClientContext/EncryptionOptions:ServerSPNserver_spnApplicationIntentapplication_intentReadOnly/ReadWrite(validated)MultiSubnetFailovermulti_subnet_failoverYes/No(validated)ConnectRetryCount/ConnectRetryIntervalKeepAlive/KeepAliveIntervalkeep_alive_in_ms/keep_alive_interval_in_msIpAddressPreferenceipaddress_preferenceIPv4First/IPv6First/UsePlatformDefault(validated)PacketSizepacket_sizeHostnameInCertificateEncryptionOptions::host_name_in_certServerCertificateEncryptionOptions::server_certificatePathBufAlso:
Addr/Addresssynonyms toServer(they were previously in the ignored set and silently dropped).E_FAIL→HY024), matching howEncrypt/Yes-Noalready behave. Integer keys parse leniently — rejecting only non-numeric/negative input and leaving documented range clamping to mssql-tds so the parser can't diverge from the native driver.KEY_*consts and theclassify_keymatch into a singleMAPPED_KEYSdescriptor table (the code's own TODO sanctioned this once the mapped set grew).Value validation ownership note: mssql-python value-validates nothing itself (it only maps
Authenticationfor client-side token acquisition and passes everything else through), so mssql-odbc owns all connection-string value validation — which this PR implements.Related Issues
Closes AB#46418
Feature: AB#46372
Work item: https://dev.azure.com/SqlClientDrivers/mssql-rs/_workitems/edit/46418
Checklist
cargo bfmtpassescargo bclippypassescargo btestpasses — mssql-odbc suite is green (337/337 via nextest). The remaining workspace failures are mssql-tds live-DB integration tests that require a.env/SQL Server (untouched crate, environmental).NewConnectionAttributesParitytestdocs/connection_string_parser.mdupdated (mapped-attributes table + value validation)