mssql: accept table constraints and indexes in table variables#391
Conversation
DECLARE @t TABLE bodies only accepted column definitions, rejecting table-level constraints at the constraint keyword — a customer function declaring PRIMARY KEY CLUSTERED entries in table variables failed SQL review (BYT-9909 statement 6). The element grammar is the same as CREATE TABLE: reuse parseTableConstraint (CONSTRAINT / PRIMARY KEY / UNIQUE / CHECK) and parseInlineTableIndex (INDEX) inside the table-variable body. Engine-verified on SQL Server 2019: table-level PK CLUSTERED, named constraints, CHECK, and inline INDEX all execute in DECLARE @t TABLE. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 26462424a2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8679db49d4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The review flagged the CONSTRAINT name prefix as invalid inside DECLARE @t TABLE, and the engine confirms (SQL Server 2019: Msg 156, Incorrect syntax near CONSTRAINT) — my original fence asserted acceptance without engine-verifying that specific shape. Remove kwCONSTRAINT from the table-variable dispatch and flip the fence to a reject with the engine citation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c624a86781
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if p.cur.Type == kwPRIMARY || | ||
| p.cur.Type == kwUNIQUE || p.cur.Type == kwCHECK { | ||
| constraint, err := p.parseTableConstraint() |
There was a problem hiding this comment.
Require KEY after PRIMARY in table constraints
When a table-variable element begins with PRIMARY, this dispatch reuses parseTableConstraint, where p.match(kwKEY) makes KEY optional; therefore DECLARE @t TABLE (a INT, PRIMARY (a)) is accepted. The bundled SQL Server grammar requires the Primary Key token pair in uniqueConstraintHeader (mssql/parser/bnf/TSql170.g:30347-30360), so this table-variable path should reject a PRIMARY constraint without KEY.
Useful? React with 👍 / 👎.
| p.cur.Type == kwUNIQUE || p.cur.Type == kwCHECK { | ||
| constraint, err := p.parseTableConstraint() |
There was a problem hiding this comment.
Require a parenthesized predicate for CHECK constraints
For an element such as CHECK, b INT, parseTableConstraint ignores a failed expect('('), returns a CHECK node, and leaves the comma for this loop, so DECLARE @t TABLE (a INT, CHECK, b INT) parses successfully. SQL Server's checkConstraint grammar requires both parentheses and a boolean expression (mssql/parser/bnf/TSql170.g:30679-30699), so the newly accepted CHECK path must reject a missing predicate.
Useful? React with 👍 / 👎.
| } else if p.cur.Type == kwINDEX { | ||
| idx, err := p.parseInlineTableIndex() |
There was a problem hiding this comment.
Require key columns for ordinary inline indexes
When an element is INDEX ix_a without a column list or COLUMNSTORE type, parseInlineTableIndex returns a valid-looking node and this loop accepts DECLARE @t TABLE (a INT, INDEX ix_a). The bundled inlineIndexTableDefinition grammar explicitly raises a syntax error when a non-clustered-columnstore index lacks its column list (mssql/parser/bnf/TSql170.g:29330-29341), so this new table-variable path should enforce the same requirement.
Useful? React with 👍 / 👎.
| } else if p.cur.Type == kwINDEX { | ||
| idx, err := p.parseInlineTableIndex() |
There was a problem hiding this comment.
Validate inline-index option names
For an inline index with WITH, this dispatch reuses parseInlineTableIndex, whose parseOptionList accepts any identifier as an option name; consequently DECLARE @t TABLE (a INT, INDEX ix_a (a) WITH (BOGUS = 1)) succeeds. The bundled inlineIndexTableDefinition grammar routes this clause through indexOptionList and its allowed-option verification (mssql/parser/bnf/TSql170.g:29345-29354), so table-variable indexes should not approve arbitrary option names.
Useful? React with 👍 / 👎.
| } else if p.cur.Type == kwINDEX { | ||
| idx, err := p.parseInlineTableIndex() |
There was a problem hiding this comment.
Require the table-variable closing parenthesis
When the newly supported final element is an index, parseInlineTableIndex consumes the index column list's ) and can return at EOF, after which this body discards the failed expect(')') for the enclosing table definition. As a result, DECLARE @t TABLE (a INT, INDEX ix_a (a) now parses successfully despite missing the table body's closing parenthesis; before this dispatch, INDEX remained unconsumed and caused a syntax error, so the enclosing delimiter error must be propagated.
Useful? React with 👍 / 👎.
Fixes BYT-9909 statement 6's remaining gap (the v3.20.1 lexer error at 45:9 no longer reproduces on main; the surviving failure was
DECLARE @t TABLE (..., PRIMARY KEY CLUSTERED (col))).Problem
Table-variable bodies only accepted column definitions — any table-level constraint rejected at the keyword. Cross-validated minimal repro:
DECLARE @t TABLE (p_year INT, PRIMARY KEY (p_year))rejects while the same constraint passes in CREATE TABLE and the inline column form passes in table variables.Fix
The element grammar is identical to CREATE TABLE's, so the body loop now dispatches to the existing
parseTableConstraint(CONSTRAINT / PRIMARY KEY / UNIQUE / CHECK) andparseInlineTableIndex(INDEX) alongsideparseColumnDef. Engine-verified on SQL Server 2019: table-level PK CLUSTERED, named constraints, CHECK, and inline INDEX all execute inside DECLARE @t TABLE. The customer's full function text now parses.Tests
TestDeclareTableVariableConstraints: 9 shapes — PK bare/CLUSTERED/NONCLUSTERED, named constraint, UNIQUE, CHECK, inline INDEX, the inline-column-PK control, and the customer's exact declarations.🤖 Generated with Claude Code