Skip to content

fix: introspection fails with error 195 on databases below compatibility level 110 - #34

Open
egertaia wants to merge 1 commit into
TabularisDB:mainfrom
egertaia:fix/triggers-xml-value-literal
Open

egertaia wants to merge 1 commit into
TabularisDB:mainfrom
egertaia:fix/triggers-xml-value-literal

Conversation

@egertaia

@egertaia egertaia commented Oct 2, 2026 •

Copy link
Copy Markdown

Reported on Discord: opening a SQL Server connection failed with

SQL Server error 195: 'nvarchar' is not a recognized built-in function name. [statement failure; severity 15; state 10; line 1]

Manual queries still worked.

Root cause

get_tables, get_columns and get_all_columns_batch read extended-property comments with TRY_CONVERT(nvarchar(max), ep.value). TRY_CONVERT only exists at database compatibility level 110 (SQL Server 2012) and above. At level 100 or lower, SQL Server doesn't treat TRY_CONVERT as a function at all. It parses nvarchar(...) as a function call and fails with error 195. That's why the message names nvarchar, not TRY_CONVERT.

So the trigger is the database's compatibility level, not the server version. A database restored or upgraded from SQL Server 2008 keeps level 100 on a 2019 or 2022 server. Opening a connection runs these introspection queries, so the connection fails while ad-hoc queries work.

The first theory on Discord blamed the N'...' literals in the get_triggers XML value() call. That turned out to be wrong. .value(N'.', N'nvarchar(max)') works on SQL Server 2017 and 2022 at every compatibility level I tried.

Reproduction (SQL Server 2022 container)

ALTER DATABASE tabularis_test SET COMPATIBILITY_LEVEL = 100;
SELECT TRY_CONVERT(nvarchar(max), N'x');
-- Msg 195, Level 15, State 10: 'nvarchar' is not a recognized built-in function name.

At level 110 or higher the same statement returns x.

Fix

  • TRY_CONVERT is replaced with CONVERT in the three introspection queries. ep.value is a sql_variant holding the property text, so the conversion can't fail in practice.
  • The get_triggers value() call now uses plain literals ('.', 'nvarchar(max)'), matching the foreign-key queries in introspection.rs. It wasn't the cause, but it keeps the code consistent.

Tests

  • New live test metadata_introspection_works_at_compatibility_level_100. It creates a database at level 100 and calls get_tables, get_columns and get_all_columns_batch. Without the fix it fails with the exact Discord error. With the fix it passes.
  • New live test get_triggers_lists_events_and_timing. live_db had no get_triggers coverage before.
  • Unit-test string assertions updated.

Verified locally against SQL Server 2022 (16.0.4295.3) in podman. The full live_db suite passes (31/31), and so do the unit tests (219), clippy and fmt.

Found while testing, not fixed here

At compatibility level below 130, the REAL case in advertised_types_round_trip_through_query_insert_update_and_null fails. The update returns SQL Server connection failure: IO error: Expected ColumnMetadata in context. There are two separate issues behind that:

  1. Test data. Below level 130, SQL Server converts FLOAT to REAL differently, so the boundary value 3.4028235e38 overflows (Msg 232). CI runs at level 160, so CI isn't affected.
  2. Dependency bug (more important). mssql-tds-preview's drain_stream (tds_client.rs) reads everything after an ERROR token with ParserContext::None and ignores COLMETADATA. So when one statement in a batch fails and a later statement returns rows, the crate can't parse those rows. This happens at every compatibility level. update_record and insert_record always hit it on runtime errors such as arithmetic overflow, because the plugin appends SELECT @@ROWCOUNT. The user sees a misleading "connection failure" instead of the real SQL error. SELECT 1/0; SELECT 2 through execute_query also hangs. The latest mssql-tds-preview (0.1.0-preview.9) has the same drain_stream. This needs an upstream fix or a separate plugin-side workaround, so I'll handle it in its own issue or PR.

@egertaia
egertaia requested a review from debba as a code owner October 2, 2026 08:40
…evel 110

TRY_CONVERT only exists at database compatibility level 110+. On older
databases get_tables, get_columns and get_all_columns_batch failed with
error 195 ('nvarchar' is not a recognized built-in function name), which
broke opening a connection. Also aligns the get_triggers value() call with
the plain-literal form used elsewhere and adds live coverage for both.
@egertaia
egertaia force-pushed the fix/triggers-xml-value-literal branch from b4c940d to d7e40cd Compare October 2, 2026 08:58
@egertaia egertaia changed the title fix: get_triggers fails with SQL Server error 195 fix: introspection fails with error 195 on databases below compatibility level 110 Oct 2, 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