Skip to content

Implement SQLGetTypeInfo - #152

Merged
gargsaumya merged 6 commits into
mainfrom
dev/saumya/odbc-sqlgettypeinfo
Jul 29, 2026
Merged

Implement SQLGetTypeInfo#152
gargsaumya merged 6 commits into
mainfrom
dev/saumya/odbc-sqlgettypeinfo

Conversation

@gargsaumya

@gargsaumya gargsaumya commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Description

Implements SQLGetTypeInfoW for the mssql-odbc driver. It executes the sp_datatype_info_100
catalog procedure as an RPC (matching the classic msodbcsql driver) and leaves the result
set open for SQLFetch/SQLGetData, reusing the existing execute/fetch machinery.

Behaviors, verified for parity against the msodbcsql source so the crate is a drop-in
replacement behind the same Driver Manager (mssql-python swap):

  • Client-side validation of the DataType argument before any I/O: unrecognized type →
    HY004, user-defined type → HYC00, active exec / open cursor → 24000.
  • @data_type sent positionally (SQLINT2); @ODBCVer = 3 sent as a named parameter for
    ODBC 3.x apps; 2.x apps omit it and receive the 2.x date/time id remap.
  • Post-success column renames (ordinals 3 / 11 / 12 → COLUMN_SIZE / FIXED_PREC_SCALE /
    AUTO_UNIQUE_VALUE) so SQLDescribeCol reports the ODBC 3.x names identically.

Supporting additions: DbcHandle::parent_env() accessor, SQL_ALL_TYPES / SQL_TIME
constants, and an HYC00 (ERR_OPTIONAL_FEATURE_NOT_IMPLEMENTED) diagnostic.

Deferred (gated on infrastructure not yet in this driver), matching msodbcsql but scoped out:

  • Server-version / vector-based proc selection (_90 / _170) — needs the negotiated server
    version plumbed to the ODBC layer; _100 is valid for all supported servers (2016+).
  • ClearNullable descriptor tweak — affects SQLColAttribute(NULLABLE), not yet wired.
  • The sp_ddopen scrollable-cursor wrapper — no cursors yet (forward-only firehose).

Note: retrieving type-info columns as native C types (e.g. SQL_C_SSHORT) depends on the
Phase-5 SQLGetData conversion work; today the columns read back as SQL_C_CHAR/SQL_C_WCHAR.

Tests: 5 unit tests (null handle, HY004, HYC00, disconnected, 24000) plus a live e2e
suite (get_type_info_test.cpp, registered in the e2e CMakeLists).

Related Issues

https://sqlclientdrivers.visualstudio.com/DefaultCollection/mssql-rs/_workitems/edit/46406

Checklist

  • cargo bfmt passes
  • cargo bclippy passes
  • cargo btest passes
  • New/changed functionality has tests
  • Public API changes are documented

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

📊 Code Coverage Report

🔥 Diff Coverage

87%

🎯 Overall Coverage

90.1%

📦 Project: mssql-tds + mssql-odbc + mssql-py-core
ℹ️ Note: diff coverage is reported, not enforced.


Diff Coverage

Diff: main...HEAD, staged and unstaged changes

  • mssql-odbc/src/api/exports.rs (100%)
  • mssql-odbc/src/api/get_type_info.rs (86.9%): Missing lines 106-107,118-119,179,184,186-196,198-204,301-302,307,317-318,323
  • mssql-odbc/src/handles/dbc.rs (100%)

Summary

  • Total: 239 lines
  • Missing: 30 lines
  • Coverage: 87%

mssql-odbc/src/api/get_type_info.rs

  102     // lock ordering.
  103     let odbc_version = {
  104         let env = dbc.parent_env();
  105         let Ok(env_state) = env.inner.lock() else {
! 106             error!("SQLGetTypeInfoW: env mutex poisoned");
! 107             return SQL_ERROR;
  108         };
  109         env_state.odbc_version
  110     };
  111     let is_2x_app = odbc_version == OdbcVersion::Odbc2;

  114     // Validation runs before any state mutation so an invalid type leaves the
  115     // statement unchanged, matching msodbcsql.
  116     {
  117         let Ok(mut stmt_state) = stmt.inner.lock() else {
! 118             error!("SQLGetTypeInfoW: stmt mutex poisoned");
! 119             return SQL_ERROR;
  120         };
  121         free_errors(&mut stmt_state);
  122 
  123         // The cursor/exec state is checked before the data type, matching

  175         )])
  176     };
  177 
  178     let mut client = match claim_connection(dbc, stmt, statement_handle, "SQLGetTypeInfoW") {
! 179         Ok(client) => client,
  180         Err(rc) => return rc,
  181     };
  182 
  183     // Release any handle orphaned by the reset above before running the RPC.
! 184     flush_pending_unprepare(dbc, stmt, &mut client, "SQLGetTypeInfoW");
  185 
! 186     let exec_result = dbc.runtime.block_on(client.execute_stored_procedure(
! 187         DATATYPE_INFO_PROC.to_string(),
! 188         Some(positional),
! 189         named,
! 190         None,
! 191         None,
! 192     ));
! 193     if let Err(e) = exec_result {
! 194         error!(%e, "SQLGetTypeInfoW: execution failed");
! 195         return fail_with_tds(dbc, stmt, statement_handle, client, &e);
! 196     }
  197 
! 198     let rc = finish_execute(dbc, stmt, statement_handle, client, "SQLGetTypeInfoW");
! 199     if rc == SQL_ERROR {
! 200         return rc;
! 201     }
! 202     rename_type_info_columns(stmt, is_2x_app);
! 203     clear_type_info_nullable(stmt);
! 204     rc
  205 }
  206 
  207 /// The `@data_type` argument sent to the catalog proc. ODBC 2.x applications
  208 /// receive the legacy 2.x date/time id (msodbcsql remaps the concise 3.x forms

  297 /// `SetColNames(COL(3)|COL(11)|COL(12), ...)` post-processing so `SQLDescribeCol`
  298 /// reports identical column names.
  299 fn rename_type_info_columns(stmt: &StmtHandle, is_2x_app: bool) {
  300     let Ok(mut stmt_state) = stmt.inner.lock() else {
! 301         error!("SQLGetTypeInfoW: stmt mutex poisoned renaming columns");
! 302         return;
  303     };
  304     let cols = &mut stmt_state.column_metadata;
  305     for (idx, name) in type_info_column_renames(is_2x_app) {
  306         if let Some(col) = cols.get_mut(idx) {
! 307             col.column_name = name.to_string();
  308         }
  309     }
  310 }

  313 /// are NOT NULL, matching msodbcsql's `ClearNullable` post-processing so
  314 /// `SQLDescribeCol` reports `SQL_NO_NULLS` for them.
  315 fn clear_type_info_nullable(stmt: &StmtHandle) {
  316     let Ok(mut stmt_state) = stmt.inner.lock() else {
! 317         error!("SQLGetTypeInfoW: stmt mutex poisoned clearing nullable");
! 318         return;
  319     };
  320     let cols = &mut stmt_state.column_metadata;
  321     for ordinal in TYPE_INFO_NOT_NULL_COLUMNS {
  322         if let Some(col) = cols.get_mut(ordinal - 1) {
! 323             col.flags &= !COLMETA_NULLABLE_FLAG;
  324         }
  325     }
  326 }


🔗 Quick Links

View Azure DevOps Build · Coverage Report

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements RPC-backed SQLGetTypeInfoW with validation, diagnostics, metadata renaming, and end-to-end coverage.

Changes:

  • Executes sp_datatype_info_100 and exposes its fetchable result set.
  • Adds ODBC type constants, diagnostics, and parent ENV access.
  • Adds unit and live integration tests.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
mssql-odbc/src/api/get_type_info.rs Implements type-info retrieval and validation.
mssql-odbc/src/api/exports.rs Exports SQLGetTypeInfoW.
mssql-odbc/src/api/mod.rs Registers the new module.
mssql-odbc/src/api/odbc_types.rs Adds required type constants.
mssql-odbc/src/api/sqlstate.rs Adds the HYC00 diagnostic.
mssql-odbc/src/handles/dbc.rs Adds parent ENV access.
mssql-odbc/tests/e2e/tests/get_type_info_test.cpp Tests observable type-info behavior.
mssql-odbc/tests/e2e/CMakeLists.txt Registers the new test suite.

Comment thread mssql-odbc/src/api/get_type_info.rs Outdated
Comment thread mssql-odbc/src/api/get_type_info.rs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

mssql-odbc/src/api/get_type_info.rs:190

  • finish_execute publishes CURSOR_OPEN, clears EXEC_STARTED, and releases the statement lock before these renames run. A concurrent call on the same STMT can therefore observe the catalog's unrenamed metadata, or close/reuse the statement and have this loop rename columns in the next result set. Commit the renamed metadata in the same locked state transition that publishes the result set (for example, let finish_execute accept a metadata transform) so the cursor never becomes visible before renaming.
    rename_type_info_columns(stmt, is_2x_app);

mssql-odbc/src/api/get_type_info.rs:159

  • The positional/named RPC shape is a stated parity requirement, but no test inspects it. odbc2_app_omits_odbc_ver_and_remaps_date only reaches this branch and then asserts a disconnected SQL_ERROR; it would still pass if @ODBCVer were sent or @data_type were named/wrongly typed. Extract the parameter construction into a pure helper and assert the ODBC 2.x and 3.x parameter names, types, and values. The live outcome-only test should also carry the required Benefits-from-mock-tds: note for the wire-level assertion it cannot make.
    // `@ODBCVer` is named and sent only for 3.x applications (matching
    // msodbcsql's `!IS2xAPP` guard).
    let named = if is_2x_app {
        None
    } else {
        Some(vec![RpcParameter::new(

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

mssql-odbc/src/api/get_type_info.rs:201

  • These post-processing helpers swallow mutex-poison failures, so SQLGetTypeInfoW can return the successful finish_execute status even though the required names/nullability were not applied. This also takes the statement lock twice and exposes partially post-processed metadata between the calls. Apply both mutations under one lock and propagate lock failure as SQL_ERROR rather than returning rc.
    rename_type_info_columns(stmt, is_2x_app);
    clear_type_info_nullable(stmt);

mssql-odbc/src/api/get_type_info.rs:172

  • The disconnected odbc2_app_omits_odbc_ver_and_remaps_date test stops in claim_connection, so it never observes this named = None branch or the RPC payload. The repository's ODBC testing guidance requires byte-level behavior hidden by live outcome tests to be pinned in Rust; extract RPC parameter construction into a testable helper (or add a mock-TDS seam) and assert that ODBC 2 omits @ODBCVer while ODBC 3 sends the named tinyint value.
    let named = if is_2x_app {
        None
    } else {
        Some(vec![RpcParameter::new(
            Some("@ODBCVer".to_string()),
            StatusFlags::NONE,
            SqlType::TinyInt(Some(ODBC_VER_KATMAI)),

mssql-odbc/tests/e2e/tests/get_type_info_test.cpp:96

  • The PR description explicitly lists the ClearNullable descriptor tweak as deferred, but this test and clear_type_info_nullable implement and require that behavior. Either update the description/scope to include the nullable metadata change or remove this behavior if it is still meant to be deferred.
// The columns the ODBC spec defines as NOT NULL report SQL_NO_NULLS, matching
// msodbcsql's ClearNullable post-processing.
TEST_F(GetTypeInfoLiveTest, NotNullColumnsReportNoNulls) {

@gargsaumya
gargsaumya marked this pull request as ready for review July 28, 2026 07:26
@gargsaumya
gargsaumya requested a review from a team as a code owner July 28, 2026 07:26
Comment thread mssql-odbc/src/api/get_type_info.rs Outdated
Comment thread mssql-odbc/tests/e2e/tests/get_type_info_test.cpp
@gargsaumya
gargsaumya merged commit 8473174 into main Jul 29, 2026
18 checks passed
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.

3 participants