Skip to content

T-SQL Conformance

Lanexio™ Parser implements T-SQL (Microsoft SQL Server) as a lexer-level dialect of the SQL grammar (ADR 0035 curated intersection), completed in ADR 0045. There is deliberately no separate parser-grammar-tsql package: a standalone pack would duplicate the SQL lexer and contradict the ADR 0035 decision.

The T-SQL surface covered by this conformance gate includes:

  • #-identifiers (temporary-table #name / ##name, plus #/## followed by $/@), bracket [identifiers], and @-variable parameters.
  • DECLARE @var [TYPE], SET @var =, BEGIN...END and TRY...CATCH blocks, CREATE PROCEDURE/FUNCTION/VIEW bodies, EXEC/EXECUTE, OUTPUT, MERGE, GO batch separators, and sqlcmd client directives.
  • Money literals ($1, $.01) and the !< / !> / ! = and ^ operators.

Statement-level structural validation is tsql-gated, so the ANSI / postgres / mysql / sqlite corpus behavior is unchanged.

The statement-head typo hardening (which flags SELCT * FROM t under ansi/postgres) is disabled for the tsql dialect. A bare identifier at statement-head is a valid implicit EXEC (a stored-procedure name as the first statement in a batch runs without EXEC), and dialect verbs outside the shared head set (RECONFIGURE, GOTO, SHUTDOWN, RENAME TABLE, DISABLE/ENABLE TRIGGER, SEND, GET CONVERSATION) begin real statements. SELCT * FROM t therefore parses clean under tsql while ansi/postgres flag it. This is ADR 0041 decision 4 over-acceptance: structural defects are still caught by the statement-linter rules at ;/EOF.

parseSql(bytes, { dialect: "tsql" }) selects the T-SQL surface directly:

import { parseSql } from '@lanexio/parser-grammar-sql';
const tree = parseSql(encoder.encode('CREATE TABLE #tmp (id int,);'), { dialect: 'tsql' });
console.log(tree.root.hasError); // false

The unified parse() accepts the tsql, mssql, and sqlserver spellings and routes them to the SQL grammar with the tsql dialect forced (ADR 0045):

import { parse } from '@lanexio/parser';
const tree = parse('DECLARE @x int; SET @x = 1;', { language: 'tsql' });
console.log(tree.metadata.language); // "sql"
console.log(tree.metadata.dialect); // "tsql"

pnpm tsql:conformance runs scripts/tsql-conformance.mjs over the 468-file test_files/tsql corpus (155 manifest-ledgered cases plus 313 root-level reference files):

GateAssertion
manifest acceptroot clean (no SqlKind.Error)
manifest reject / reject-coreroot flagged
manifest recoverflagged but a later statement node is produced
manifest accept-clientrecorded but excluded (SQLCMD client preprocessing is not implemented)
root-level filesroot clean + never-throw + lossless (flat AST leaf partition)
manifest conform %>= 95 over the classified cases (denominator 155 - 3 accept-client = 152)
root-level clean %100%

Two ledger expectations are documented over-acceptance: the reject-core GO and sqlcmd cases (270_go_is_not_tsql.sql, 271_sqlcmd_is_not_tsql.sql) are leniently accepted because GO batch separators and sqlcmd directives are client preprocessing, not core statement grammar (ADR 0041 decision 4). They are the bounded toleration that keeps the aggregate at 150/152 = 98.7% while the remaining 150 classified cases are exact.

The same manifest and root-file assertions run against the source in the package test suite (tsql-conformance.test.ts), so the gate is enforced in CI both on the built dist (harness script) and under test (vitest).

Measured on this tree with pnpm tsql:conformance:

  • Manifest: 150/152 classified conform = 98.7% (>= 95 gate; only the two documented GO/sqlcmd over-acceptance cases fail).
  • Root-level files: 313/313 clean.

A trailing comma before the closing paren of a T-SQL CREATE TABLE column list is valid and parses clean, regardless of the table-name shape: a bare name (CREATE TABLE t (a int,)), a schema-qualified name (CREATE TABLE dbo.t (id int,)), a bracket-delimited name (CREATE TABLE [t] (a int,)), and a temporary-table name (CREATE TABLE #t (a int,)) all parse clean. An empty column list (CREATE TABLE t ()) is rejected, because T-SQL requires at least one column definition. The leniency does not extend to table variables, function argument lists, INSERT column lists, or ORDER BY: a function-argument trailing comma such as SELECT COALESCE(a, b,) is still flagged.