From c86de0bcec84d235bcb1b2c63d483e08f9eede99 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Dec 2025 17:07:02 +0000 Subject: [PATCH 1/8] Add parser features and enable 12 more tests Add support for: - Cursor statements (OPEN, CLOSE, FETCH, DEALLOCATE) - DECLARE CURSOR statement - UPDATE STATISTICS statement with options - CREATE STATISTICS with columns and options - ALTER DATABASE autogrow options (AUTOGROW_ALL_FILES, AUTOGROW_SINGLE_FILE) Enabled tests: - AlterDatabaseStatementTests130 - Baselines130_AlterDatabaseStatementTests130 - BaselinesCommon_BigIntRowCountPageCountTests - BigIntRowCountPageCountTests - BaselinesCommon_CreateStatisticsStatementTests - CreateStatisticsStatementTests - BaselinesCommon_CursorStatementsTests - CursorStatementsTests - BaselinesCommon_DeclareCursorStatementTests - DeclareCursorStatementTests - BaselinesCommon_UpdateStatisticsStatementTests - UpdateStatisticsStatementTests --- ast/create_simple_statements.go | 7 +- ast/cursor_statements.go | 50 +++ ast/update_statistics_statement.go | 47 +++ parser/marshal.go | 225 +++++++++++ parser/parse_ddl.go | 6 + parser/parse_dml.go | 220 +++++++++++ parser/parse_statements.go | 363 +++++++++++++++++- parser/parser.go | 10 +- .../metadata.json | 2 +- .../metadata.json | 2 +- .../metadata.json | 2 +- .../metadata.json | 2 +- .../metadata.json | 2 +- .../metadata.json | 2 +- .../metadata.json | 2 +- .../metadata.json | 2 +- .../metadata.json | 2 +- .../CursorStatementsTests/metadata.json | 2 +- .../DeclareCursorStatementTests/metadata.json | 2 +- .../ast.json | 22 ++ .../metadata.json | 2 +- 21 files changed, 955 insertions(+), 19 deletions(-) create mode 100644 ast/cursor_statements.go create mode 100644 ast/update_statistics_statement.go diff --git a/ast/create_simple_statements.go b/ast/create_simple_statements.go index 077af431..c424d72b 100644 --- a/ast/create_simple_statements.go +++ b/ast/create_simple_statements.go @@ -150,8 +150,11 @@ func (s *CreateIndexStatement) statement() {} // CreateStatisticsStatement represents a CREATE STATISTICS statement. type CreateStatisticsStatement struct { - Name *Identifier `json:"Name,omitempty"` - OnName *SchemaObjectName `json:"OnName,omitempty"` + Name *Identifier `json:"Name,omitempty"` + OnName *SchemaObjectName `json:"OnName,omitempty"` + Columns []*ColumnReferenceExpression `json:"Columns,omitempty"` + StatisticsOptions []StatisticsOption `json:"StatisticsOptions,omitempty"` + FilterPredicate BooleanExpression `json:"FilterPredicate,omitempty"` } func (s *CreateStatisticsStatement) node() {} diff --git a/ast/cursor_statements.go b/ast/cursor_statements.go new file mode 100644 index 00000000..85fa1b5c --- /dev/null +++ b/ast/cursor_statements.go @@ -0,0 +1,50 @@ +package ast + +// FetchType represents the orientation for a FETCH statement. +type FetchType struct { + Orientation string `json:"Orientation,omitempty"` + RowOffset ScalarExpression `json:"RowOffset,omitempty"` +} + +// DeclareCursorStatement represents DECLARE cursor_name CURSOR FOR SELECT. +type DeclareCursorStatement struct { + Name *Identifier `json:"Name,omitempty"` + CursorDefinition *CursorDefinition `json:"CursorDefinition,omitempty"` +} + +func (s *DeclareCursorStatement) node() {} +func (s *DeclareCursorStatement) statement() {} + +// OpenCursorStatement represents OPEN cursor_name. +type OpenCursorStatement struct { + Cursor *CursorId `json:"Cursor,omitempty"` +} + +func (s *OpenCursorStatement) node() {} +func (s *OpenCursorStatement) statement() {} + +// CloseCursorStatement represents CLOSE cursor_name. +type CloseCursorStatement struct { + Cursor *CursorId `json:"Cursor,omitempty"` +} + +func (s *CloseCursorStatement) node() {} +func (s *CloseCursorStatement) statement() {} + +// DeallocateCursorStatement represents DEALLOCATE cursor_name. +type DeallocateCursorStatement struct { + Cursor *CursorId `json:"Cursor,omitempty"` +} + +func (s *DeallocateCursorStatement) node() {} +func (s *DeallocateCursorStatement) statement() {} + +// FetchCursorStatement represents FETCH cursor_name. +type FetchCursorStatement struct { + FetchType *FetchType `json:"FetchType,omitempty"` + Cursor *CursorId `json:"Cursor,omitempty"` + IntoVariables []ScalarExpression `json:"IntoVariables,omitempty"` +} + +func (s *FetchCursorStatement) node() {} +func (s *FetchCursorStatement) statement() {} diff --git a/ast/update_statistics_statement.go b/ast/update_statistics_statement.go new file mode 100644 index 00000000..8e24c3a4 --- /dev/null +++ b/ast/update_statistics_statement.go @@ -0,0 +1,47 @@ +package ast + +// UpdateStatisticsStatement represents UPDATE STATISTICS. +type UpdateStatisticsStatement struct { + SchemaObjectName *SchemaObjectName `json:"SchemaObjectName,omitempty"` + SubElements []*Identifier `json:"SubElements,omitempty"` + StatisticsOptions []StatisticsOption `json:"StatisticsOptions,omitempty"` +} + +func (u *UpdateStatisticsStatement) node() {} +func (u *UpdateStatisticsStatement) statement() {} + +// StatisticsOption is an interface for statistics options. +type StatisticsOption interface { + statisticsOption() +} + +// SimpleStatisticsOption represents a simple statistics option like ALL, FULLSCAN, etc. +type SimpleStatisticsOption struct { + OptionKind string `json:"OptionKind,omitempty"` +} + +func (s *SimpleStatisticsOption) statisticsOption() {} + +// LiteralStatisticsOption represents a statistics option with a literal value. +type LiteralStatisticsOption struct { + OptionKind string `json:"OptionKind,omitempty"` + Literal ScalarExpression `json:"Literal,omitempty"` +} + +func (l *LiteralStatisticsOption) statisticsOption() {} + +// OnOffStatisticsOption represents a statistics option with ON/OFF value. +type OnOffStatisticsOption struct { + OptionKind string `json:"OptionKind,omitempty"` + OptionState string `json:"OptionState,omitempty"` +} + +func (o *OnOffStatisticsOption) statisticsOption() {} + +// ResampleStatisticsOption represents RESAMPLE statistics option. +type ResampleStatisticsOption struct { + OptionKind string `json:"OptionKind,omitempty"` + Partitions []ScalarExpression `json:"Partitions,omitempty"` +} + +func (r *ResampleStatisticsOption) statisticsOption() {} diff --git a/parser/marshal.go b/parser/marshal.go index 3e610c22..c6def889 100644 --- a/parser/marshal.go +++ b/parser/marshal.go @@ -54,6 +54,8 @@ func statementToJSON(stmt ast.Statement) jsonNode { return insertStatementToJSON(s) case *ast.UpdateStatement: return updateStatementToJSON(s) + case *ast.UpdateStatisticsStatement: + return updateStatisticsStatementToJSON(s) case *ast.DeleteStatement: return deleteStatementToJSON(s) case *ast.DeclareVariableStatement: @@ -430,6 +432,16 @@ func statementToJSON(stmt ast.Statement) jsonNode { return alterServiceMasterKeyStatementToJSON(s) case *ast.RenameEntityStatement: return renameEntityStatementToJSON(s) + case *ast.OpenCursorStatement: + return openCursorStatementToJSON(s) + case *ast.CloseCursorStatement: + return closeCursorStatementToJSON(s) + case *ast.DeallocateCursorStatement: + return deallocateCursorStatementToJSON(s) + case *ast.FetchCursorStatement: + return fetchCursorStatementToJSON(s) + case *ast.DeclareCursorStatement: + return declareCursorStatementToJSON(s) default: return jsonNode{"$type": "UnknownStatement"} } @@ -6183,6 +6195,23 @@ func createStatisticsStatementToJSON(s *ast.CreateStatisticsStatement) jsonNode if s.OnName != nil { node["OnName"] = schemaObjectNameToJSON(s.OnName) } + if len(s.Columns) > 0 { + cols := make([]jsonNode, len(s.Columns)) + for i, c := range s.Columns { + cols[i] = columnReferenceExpressionToJSON(c) + } + node["Columns"] = cols + } + if len(s.StatisticsOptions) > 0 { + opts := make([]jsonNode, len(s.StatisticsOptions)) + for i, o := range s.StatisticsOptions { + opts[i] = statisticsOptionToJSON(o) + } + node["StatisticsOptions"] = opts + } + if s.FilterPredicate != nil { + node["FilterPredicate"] = booleanExpressionToJSON(s.FilterPredicate) + } return node } @@ -6302,6 +6331,10 @@ func alterDatabaseModifyFileGroupStatementToJSON(s *ast.AlterDatabaseModifyFileG node["FileGroup"] = identifierToJSON(s.FileGroupName) } node["MakeDefault"] = s.MakeDefault + // Only output UseCurrent when UpdatabilityOption is an autogrow option + if strings.HasPrefix(s.UpdatabilityOption, "Autogrow") { + node["UseCurrent"] = false + } if s.NewFileGroupName != nil { node["NewFileGroupName"] = identifierToJSON(s.NewFileGroupName) } @@ -6581,3 +6614,195 @@ func alterExternalLibraryStatementToJSON(s *ast.AlterExternalLibraryStatement) j return node } +func fetchTypeToJSON(f *ast.FetchType) jsonNode { + node := jsonNode{ + "$type": "FetchType", + } + if f.Orientation != "" { + node["Orientation"] = f.Orientation + } + if f.RowOffset != nil { + node["RowOffset"] = scalarExpressionToJSON(f.RowOffset) + } + return node +} + +func openCursorStatementToJSON(s *ast.OpenCursorStatement) jsonNode { + node := jsonNode{ + "$type": "OpenCursorStatement", + } + if s.Cursor != nil { + node["Cursor"] = cursorIdToJSON(s.Cursor) + } + return node +} + +func closeCursorStatementToJSON(s *ast.CloseCursorStatement) jsonNode { + node := jsonNode{ + "$type": "CloseCursorStatement", + } + if s.Cursor != nil { + node["Cursor"] = cursorIdToJSON(s.Cursor) + } + return node +} + +func deallocateCursorStatementToJSON(s *ast.DeallocateCursorStatement) jsonNode { + node := jsonNode{ + "$type": "DeallocateCursorStatement", + } + if s.Cursor != nil { + node["Cursor"] = cursorIdToJSON(s.Cursor) + } + return node +} + +func fetchCursorStatementToJSON(s *ast.FetchCursorStatement) jsonNode { + node := jsonNode{ + "$type": "FetchCursorStatement", + } + if s.FetchType != nil { + node["FetchType"] = fetchTypeToJSON(s.FetchType) + } + if s.Cursor != nil { + node["Cursor"] = cursorIdToJSON(s.Cursor) + } + if len(s.IntoVariables) > 0 { + vars := make([]jsonNode, len(s.IntoVariables)) + for i, v := range s.IntoVariables { + vars[i] = scalarExpressionToJSON(v) + } + node["IntoVariables"] = vars + } + return node +} + +func updateStatisticsStatementToJSON(s *ast.UpdateStatisticsStatement) jsonNode { + node := jsonNode{ + "$type": "UpdateStatisticsStatement", + } + if s.SchemaObjectName != nil { + node["SchemaObjectName"] = schemaObjectNameToJSON(s.SchemaObjectName) + } + if len(s.SubElements) > 0 { + elems := make([]jsonNode, len(s.SubElements)) + for i, e := range s.SubElements { + elems[i] = identifierToJSON(e) + } + node["SubElements"] = elems + } + if len(s.StatisticsOptions) > 0 { + opts := make([]jsonNode, len(s.StatisticsOptions)) + for i, o := range s.StatisticsOptions { + opts[i] = statisticsOptionToJSON(o) + } + node["StatisticsOptions"] = opts + } + return node +} + +func statisticsOptionToJSON(opt ast.StatisticsOption) jsonNode { + switch o := opt.(type) { + case *ast.SimpleStatisticsOption: + return simpleStatisticsOptionToJSON(o) + case *ast.LiteralStatisticsOption: + return literalStatisticsOptionToJSON(o) + case *ast.OnOffStatisticsOption: + return onOffStatisticsOptionToJSON(o) + case *ast.ResampleStatisticsOption: + return resampleStatisticsOptionToJSON(o) + default: + return jsonNode{"$type": "UnknownStatisticsOption"} + } +} + +func simpleStatisticsOptionToJSON(o *ast.SimpleStatisticsOption) jsonNode { + node := jsonNode{ + "$type": "StatisticsOption", + } + if o.OptionKind != "" { + node["OptionKind"] = o.OptionKind + } + return node +} + +func literalStatisticsOptionToJSON(o *ast.LiteralStatisticsOption) jsonNode { + node := jsonNode{ + "$type": "LiteralStatisticsOption", + } + if o.OptionKind != "" { + node["OptionKind"] = o.OptionKind + } + if o.Literal != nil { + node["Literal"] = scalarExpressionToJSON(o.Literal) + } + return node +} + +func onOffStatisticsOptionToJSON(o *ast.OnOffStatisticsOption) jsonNode { + node := jsonNode{ + "$type": "OnOffStatisticsOption", + } + if o.OptionKind != "" { + node["OptionKind"] = o.OptionKind + } + if o.OptionState != "" { + node["OptionState"] = o.OptionState + } + return node +} + +func resampleStatisticsOptionToJSON(o *ast.ResampleStatisticsOption) jsonNode { + node := jsonNode{ + "$type": "ResampleStatisticsOption", + } + if o.OptionKind != "" { + node["OptionKind"] = o.OptionKind + } + if len(o.Partitions) > 0 { + partitions := make([]jsonNode, len(o.Partitions)) + for i, p := range o.Partitions { + partitions[i] = scalarExpressionToJSON(p) + } + node["Partitions"] = partitions + } + return node +} + +func declareCursorStatementToJSON(s *ast.DeclareCursorStatement) jsonNode { + node := jsonNode{ + "$type": "DeclareCursorStatement", + } + if s.Name != nil { + node["Name"] = identifierToJSON(s.Name) + } + if s.CursorDefinition != nil { + node["CursorDefinition"] = declareCursorDefinitionToJSON(s.CursorDefinition) + } + return node +} + +func declareCursorDefinitionToJSON(d *ast.CursorDefinition) jsonNode { + node := jsonNode{ + "$type": "CursorDefinition", + } + if len(d.Options) > 0 { + opts := make([]jsonNode, len(d.Options)) + for i, o := range d.Options { + opts[i] = jsonNode{ + "$type": "CursorOption", + "OptionKind": o.OptionKind, + } + } + node["Options"] = opts + } + if d.Select != nil { + // For DeclareCursorStatement, we need to wrap the QueryExpression in a SelectStatement format + selectNode := jsonNode{ + "$type": "SelectStatement", + "QueryExpression": queryExpressionToJSON(d.Select), + } + node["Select"] = selectNode + } + return node +} diff --git a/parser/parse_ddl.go b/parser/parse_ddl.go index fe0a4a34..24a3d580 100644 --- a/parser/parse_ddl.go +++ b/parser/parse_ddl.go @@ -1586,6 +1586,12 @@ func (p *Parser) parseAlterDatabaseModifyStatement(dbName *ast.Identifier) (ast. case "READWRITE", "READ_WRITE": stmt.UpdatabilityOption = "ReadWrite" p.nextToken() + case "AUTOGROW_ALL_FILES": + stmt.UpdatabilityOption = "AutogrowAllFiles" + p.nextToken() + case "AUTOGROW_SINGLE_FILE": + stmt.UpdatabilityOption = "AutogrowSingleFile" + p.nextToken() case "NAME": p.nextToken() // consume NAME if p.curTok.Type == TokenEquals { diff --git a/parser/parse_dml.go b/parser/parse_dml.go index 89fea5d9..b071f7dc 100644 --- a/parser/parse_dml.go +++ b/parser/parse_dml.go @@ -1045,3 +1045,223 @@ func (p *Parser) parseIdentifierOrValueExpression() (*ast.IdentifierOrValueExpre return result, nil } +// parseUpdateOrUpdateStatisticsStatement routes to UPDATE or UPDATE STATISTICS. +func (p *Parser) parseUpdateOrUpdateStatisticsStatement() (ast.Statement, error) { + // Consume UPDATE + p.nextToken() + + // Check for UPDATE STATISTICS + if p.curTok.Type == TokenStats || strings.ToUpper(p.curTok.Literal) == "STATISTICS" { + return p.parseUpdateStatisticsStatementContinued() + } + + // Otherwise, parse normal UPDATE statement + stmt := &ast.UpdateStatement{ + UpdateSpecification: &ast.UpdateSpecification{}, + } + + // Parse target + target, err := p.parseDMLTarget() + if err != nil { + return nil, err + } + stmt.UpdateSpecification.Target = target + + // Expect SET + if p.curTok.Type != TokenSet { + return nil, fmt.Errorf("expected SET, got %s", p.curTok.Literal) + } + p.nextToken() + + // Parse SET clauses + setClauses, err := p.parseSetClauses() + if err != nil { + return nil, err + } + stmt.UpdateSpecification.SetClauses = setClauses + + // Parse optional FROM clause + if p.curTok.Type == TokenFrom { + fromClause, err := p.parseFromClause() + if err != nil { + return nil, err + } + stmt.UpdateSpecification.FromClause = fromClause + } + + // Parse optional WHERE clause + if p.curTok.Type == TokenWhere { + whereClause, err := p.parseWhereClause() + if err != nil { + return nil, err + } + stmt.UpdateSpecification.WhereClause = whereClause + } + + // Parse optional OPTION clause + if p.curTok.Type == TokenOption { + hints, err := p.parseOptionClause() + if err != nil { + return nil, err + } + stmt.OptimizerHints = hints + } + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } + + return stmt, nil +} + +// parseUpdateStatisticsStatementContinued parses UPDATE STATISTICS after consuming UPDATE. +func (p *Parser) parseUpdateStatisticsStatementContinued() (*ast.UpdateStatisticsStatement, error) { + // Consume STATISTICS + p.nextToken() + + stmt := &ast.UpdateStatisticsStatement{} + + // Parse table name + schemaObjectName, err := p.parseSchemaObjectName() + if err != nil { + return nil, err + } + stmt.SchemaObjectName = schemaObjectName + + // Parse optional SubElements (stat/index names) + // Can be either in parentheses: (c1, c2, c3) or a single identifier: st1 + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + ident := p.parseIdentifier() + stmt.SubElements = append(stmt.SubElements, ident) + if p.curTok.Type == TokenComma { + p.nextToken() + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } else if p.curTok.Type == TokenIdent { + // Single identifier without parentheses + ident := p.parseIdentifier() + stmt.SubElements = append(stmt.SubElements, ident) + } + + // Parse optional WITH clause + if p.curTok.Type == TokenWith { + p.nextToken() // consume WITH + + for p.curTok.Type != TokenSemicolon && p.curTok.Type != TokenEOF { + optionName := strings.ToUpper(p.curTok.Literal) + p.nextToken() // consume option name + + switch optionName { + case "ALL": + stmt.StatisticsOptions = append(stmt.StatisticsOptions, &ast.SimpleStatisticsOption{ + OptionKind: "All", + }) + case "FULLSCAN": + stmt.StatisticsOptions = append(stmt.StatisticsOptions, &ast.SimpleStatisticsOption{ + OptionKind: "FullScan", + }) + case "NORECOMPUTE": + stmt.StatisticsOptions = append(stmt.StatisticsOptions, &ast.SimpleStatisticsOption{ + OptionKind: "NoRecompute", + }) + case "COLUMNS": + stmt.StatisticsOptions = append(stmt.StatisticsOptions, &ast.SimpleStatisticsOption{ + OptionKind: "Columns", + }) + case "INDEX": + stmt.StatisticsOptions = append(stmt.StatisticsOptions, &ast.SimpleStatisticsOption{ + OptionKind: "Index", + }) + case "ROWCOUNT": + // Parse = value + if p.curTok.Type == TokenEquals { + p.nextToken() + } + val := p.curTok.Literal + p.nextToken() + // Use NumericLiteral for very large numbers, IntegerLiteral otherwise + var literal ast.ScalarExpression + if len(val) > 18 { // Numbers > 18 digits are likely > MaxInt64 + literal = &ast.NumericLiteral{LiteralType: "Numeric", Value: val} + } else { + literal = &ast.IntegerLiteral{LiteralType: "Integer", Value: val} + } + stmt.StatisticsOptions = append(stmt.StatisticsOptions, &ast.LiteralStatisticsOption{ + OptionKind: "RowCount", + Literal: literal, + }) + case "PAGECOUNT": + // Parse = value + if p.curTok.Type == TokenEquals { + p.nextToken() + } + val := p.curTok.Literal + p.nextToken() + // Use NumericLiteral for very large numbers, IntegerLiteral otherwise + var literal ast.ScalarExpression + if len(val) > 18 { // Numbers > 18 digits are likely > MaxInt64 + literal = &ast.NumericLiteral{LiteralType: "Numeric", Value: val} + } else { + literal = &ast.IntegerLiteral{LiteralType: "Integer", Value: val} + } + stmt.StatisticsOptions = append(stmt.StatisticsOptions, &ast.LiteralStatisticsOption{ + OptionKind: "PageCount", + Literal: literal, + }) + case "SAMPLE": + // Parse number PERCENT/ROWS + value, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + mode := strings.ToUpper(p.curTok.Literal) + p.nextToken() // consume PERCENT or ROWS + stmt.StatisticsOptions = append(stmt.StatisticsOptions, &ast.LiteralStatisticsOption{ + OptionKind: "Sample" + strings.Title(strings.ToLower(mode)), + Literal: value, + }) + case "RESAMPLE": + stmt.StatisticsOptions = append(stmt.StatisticsOptions, &ast.ResampleStatisticsOption{ + OptionKind: "Resample", + }) + case "INCREMENTAL": + if p.curTok.Type == TokenEquals { + p.nextToken() + state := strings.ToUpper(p.curTok.Literal) + p.nextToken() + stmt.StatisticsOptions = append(stmt.StatisticsOptions, &ast.OnOffStatisticsOption{ + OptionKind: "Incremental", + OptionState: state, + }) + } else { + stmt.StatisticsOptions = append(stmt.StatisticsOptions, &ast.OnOffStatisticsOption{ + OptionKind: "Incremental", + OptionState: "On", + }) + } + default: + // Unknown option, skip + } + + if p.curTok.Type == TokenComma { + p.nextToken() + } else { + break + } + } + } + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } + + return stmt, nil +} + diff --git a/parser/parse_statements.go b/parser/parse_statements.go index 344c1569..e51a705e 100644 --- a/parser/parse_statements.go +++ b/parser/parse_statements.go @@ -13,6 +13,24 @@ func (p *Parser) parseDeclareVariableStatement() (ast.Statement, error) { // Consume DECLARE p.nextToken() + // Check if this is DECLARE cursor_name CURSOR (without @) + if p.curTok.Type == TokenIdent && !strings.HasPrefix(p.curTok.Literal, "@") { + // This might be DECLARE cursor_name CURSOR + cursorName := p.parseIdentifier() + + // Check for CURSOR keyword + if p.curTok.Type == TokenCursor { + return p.parseDeclareCursorStatementContinued(cursorName) + } + // Could also be old cursor syntax with options before CURSOR + kwd := strings.ToUpper(p.curTok.Literal) + if kwd == "INSENSITIVE" || kwd == "SCROLL" { + return p.parseDeclareCursorStatementContinued(cursorName) + } + // Not a cursor, error + return nil, fmt.Errorf("expected CURSOR after identifier in DECLARE, got %s", p.curTok.Literal) + } + // Parse variable name if p.curTok.Type != TokenIdent || !strings.HasPrefix(p.curTok.Literal, "@") { return nil, fmt.Errorf("expected variable name, got %s", p.curTok.Literal) @@ -4009,7 +4027,8 @@ func (p *Parser) parseCloseStatement() (ast.Statement, error) { return &ast.CloseMasterKeyStatement{}, nil } - return nil, fmt.Errorf("expected SYMMETRIC, ALL, or MASTER after CLOSE, got %s", p.curTok.Literal) + // Otherwise, it's CLOSE cursor_name + return p.parseCloseCursorStatement() } func (p *Parser) parseOpenStatement() (ast.Statement, error) { @@ -4062,7 +4081,8 @@ func (p *Parser) parseOpenStatement() (ast.Statement, error) { return stmt, nil } - return nil, fmt.Errorf("expected SYMMETRIC or MASTER after OPEN, got %s", p.curTok.Literal) + // Otherwise, it's OPEN cursor_name + return p.parseOpenCursorStatement() } func (p *Parser) parseCreateExternalStatement() (ast.Statement, error) { @@ -4741,8 +4761,99 @@ func (p *Parser) parseCreateStatisticsStatement() (*ast.CreateStatisticsStatemen Name: p.parseIdentifier(), } - // Skip rest of statement - p.skipToEndOfStatement() + // Parse ON table_name + if p.curTok.Type == TokenOn { + p.nextToken() // consume ON + tableName, err := p.parseSchemaObjectName() + if err != nil { + return nil, err + } + stmt.OnName = tableName + } + + // Parse columns in parentheses + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + // Parse column name + colRef, err := p.parseColumnReferenceOrFunctionCall() + if err != nil { + return nil, err + } + // Type assert to ColumnReferenceExpression + if cr, ok := colRef.(*ast.ColumnReferenceExpression); ok { + stmt.Columns = append(stmt.Columns, cr) + } + if p.curTok.Type == TokenComma { + p.nextToken() + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } + + // Parse optional WITH clause (reuse UPDATE STATISTICS options logic) + if p.curTok.Type == TokenWith { + p.nextToken() // consume WITH + + for p.curTok.Type != TokenSemicolon && p.curTok.Type != TokenEOF && p.curTok.Type != TokenWhere { + optionName := strings.ToUpper(p.curTok.Literal) + p.nextToken() // consume option name + + switch optionName { + case "FULLSCAN": + stmt.StatisticsOptions = append(stmt.StatisticsOptions, &ast.SimpleStatisticsOption{ + OptionKind: "FullScan", + }) + case "NORECOMPUTE": + stmt.StatisticsOptions = append(stmt.StatisticsOptions, &ast.SimpleStatisticsOption{ + OptionKind: "NoRecompute", + }) + case "SAMPLE": + // Parse number PERCENT/ROWS + value, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + mode := strings.ToUpper(p.curTok.Literal) + p.nextToken() // consume PERCENT or ROWS + stmt.StatisticsOptions = append(stmt.StatisticsOptions, &ast.LiteralStatisticsOption{ + OptionKind: "Sample" + strings.Title(strings.ToLower(mode)), + Literal: value, + }) + case "INCREMENTAL": + if p.curTok.Type == TokenEquals { + p.nextToken() + state := strings.ToUpper(p.curTok.Literal) + p.nextToken() + stmt.StatisticsOptions = append(stmt.StatisticsOptions, &ast.OnOffStatisticsOption{ + OptionKind: "Incremental", + OptionState: state, + }) + } else { + stmt.StatisticsOptions = append(stmt.StatisticsOptions, &ast.OnOffStatisticsOption{ + OptionKind: "Incremental", + OptionState: "On", + }) + } + default: + // Unknown option, skip + } + + if p.curTok.Type == TokenComma { + p.nextToken() + } else { + break + } + } + } + + // Skip optional WHERE clause and rest of statement + if p.curTok.Type == TokenWhere || p.curTok.Type == TokenSemicolon { + p.skipToEndOfStatement() + } + return stmt, nil } @@ -4874,3 +4985,247 @@ func (p *Parser) parseRenameStatement() (*ast.RenameEntityStatement, error) { return stmt, nil } + +// parseCursorId parses a cursor identifier (optional GLOBAL, cursor name/variable). +func (p *Parser) parseCursorId() *ast.CursorId { + cursorId := &ast.CursorId{} + + // Check for GLOBAL keyword + if strings.ToUpper(p.curTok.Literal) == "GLOBAL" { + cursorId.IsGlobal = true + p.nextToken() + } + + // Parse cursor name or variable + cursorId.Name = &ast.IdentifierOrValueExpression{ + Value: p.curTok.Literal, + } + + // Check if it's a variable + if p.curTok.Type == TokenIdent && strings.HasPrefix(p.curTok.Literal, "@") { + cursorId.Name.ValueExpression = &ast.VariableReference{Name: p.curTok.Literal} + } else { + // Create identifier inline (same logic as parseIdentifier but without advancing) + literal := p.curTok.Literal + quoteType := "NotQuoted" + if len(literal) >= 2 && literal[0] == '[' && literal[len(literal)-1] == ']' { + quoteType = "SquareBracket" + literal = literal[1 : len(literal)-1] + } + cursorId.Name.Identifier = &ast.Identifier{ + Value: literal, + QuoteType: quoteType, + } + } + p.nextToken() + + return cursorId +} + +// parseOpenCursorStatement parses OPEN cursor_name. +func (p *Parser) parseOpenCursorStatement() (*ast.OpenCursorStatement, error) { + stmt := &ast.OpenCursorStatement{ + Cursor: p.parseCursorId(), + } + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } + + return stmt, nil +} + +// parseCloseCursorStatement parses CLOSE cursor_name. +func (p *Parser) parseCloseCursorStatement() (*ast.CloseCursorStatement, error) { + stmt := &ast.CloseCursorStatement{ + Cursor: p.parseCursorId(), + } + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } + + return stmt, nil +} + +// parseDeallocateCursorStatement parses DEALLOCATE cursor_name. +func (p *Parser) parseDeallocateCursorStatement() (*ast.DeallocateCursorStatement, error) { + // Already consumed DEALLOCATE + p.nextToken() + + // Check for optional CURSOR keyword + if p.curTok.Type == TokenCursor { + p.nextToken() + } + + stmt := &ast.DeallocateCursorStatement{ + Cursor: p.parseCursorId(), + } + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } + + return stmt, nil +} + +// parseFetchCursorStatement parses FETCH ... FROM cursor_name. +func (p *Parser) parseFetchCursorStatement() (*ast.FetchCursorStatement, error) { + // Already consumed FETCH + p.nextToken() + + stmt := &ast.FetchCursorStatement{} + + // Check for fetch orientation + orientationKeyword := strings.ToUpper(p.curTok.Literal) + switch orientationKeyword { + case "NEXT": + stmt.FetchType = &ast.FetchType{Orientation: "Next"} + p.nextToken() + case "PRIOR": + stmt.FetchType = &ast.FetchType{Orientation: "Prior"} + p.nextToken() + case "FIRST": + stmt.FetchType = &ast.FetchType{Orientation: "First"} + p.nextToken() + case "LAST": + stmt.FetchType = &ast.FetchType{Orientation: "Last"} + p.nextToken() + case "ABSOLUTE": + p.nextToken() // consume ABSOLUTE + offset, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + stmt.FetchType = &ast.FetchType{ + Orientation: "Absolute", + RowOffset: offset, + } + case "RELATIVE": + p.nextToken() // consume RELATIVE + offset, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + stmt.FetchType = &ast.FetchType{ + Orientation: "Relative", + RowOffset: offset, + } + } + + // Check for FROM keyword + if p.curTok.Type == TokenFrom { + p.nextToken() + } + + // Parse cursor id + stmt.Cursor = p.parseCursorId() + + // Check for INTO clause + if p.curTok.Type == TokenInto { + p.nextToken() // consume INTO + for { + varRef, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + stmt.IntoVariables = append(stmt.IntoVariables, varRef) + if p.curTok.Type != TokenComma { + break + } + p.nextToken() // consume comma + } + } + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } + + return stmt, nil +} + +// parseDeclareCursorStatementContinued parses DECLARE cursor CURSOR ... after the cursor name. +func (p *Parser) parseDeclareCursorStatementContinued(cursorName *ast.Identifier) (*ast.DeclareCursorStatement, error) { + stmt := &ast.DeclareCursorStatement{ + Name: cursorName, + CursorDefinition: &ast.CursorDefinition{}, + } + + // Parse cursor options (INSENSITIVE, SCROLL, LOCAL, GLOBAL, FORWARD_ONLY, etc.) + for p.curTok.Type != TokenCursor && p.curTok.Type != TokenEOF && strings.ToUpper(p.curTok.Literal) != "FOR" { + kwd := strings.ToUpper(p.curTok.Literal) + switch kwd { + case "INSENSITIVE", "SCROLL", "LOCAL", "GLOBAL", "FORWARD_ONLY", "STATIC", + "KEYSET", "DYNAMIC", "FAST_FORWARD", "READ_ONLY", "SCROLL_LOCKS", + "OPTIMISTIC", "TYPE_WARNING": + stmt.CursorDefinition.Options = append(stmt.CursorDefinition.Options, &ast.CursorOption{ + OptionKind: toTitleCase(kwd), + }) + p.nextToken() + default: + break + } + if p.curTok.Type == TokenCursor || strings.ToUpper(p.curTok.Literal) == "FOR" { + break + } + } + + // Consume CURSOR keyword + if p.curTok.Type == TokenCursor { + p.nextToken() + } + + // Parse more options after CURSOR (for the new syntax) + for strings.ToUpper(p.curTok.Literal) != "FOR" && p.curTok.Type != TokenEOF { + kwd := strings.ToUpper(p.curTok.Literal) + switch kwd { + case "LOCAL", "GLOBAL", "FORWARD_ONLY", "SCROLL", "STATIC", "KEYSET", + "DYNAMIC", "FAST_FORWARD", "READ_ONLY", "SCROLL_LOCKS", "OPTIMISTIC", + "TYPE_WARNING": + stmt.CursorDefinition.Options = append(stmt.CursorDefinition.Options, &ast.CursorOption{ + OptionKind: toTitleCase(kwd), + }) + p.nextToken() + default: + break + } + if strings.ToUpper(p.curTok.Literal) == "FOR" { + break + } + } + + // Consume FOR keyword + if strings.ToUpper(p.curTok.Literal) == "FOR" { + p.nextToken() + } + + // Parse SELECT statement and extract its QueryExpression + selectStmt, err := p.parseSelectStatement() + if err != nil { + return nil, err + } + // CursorDefinition.Select is a QueryExpression, so we extract it from the SelectStatement + stmt.CursorDefinition.Select = selectStmt.QueryExpression + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } + + return stmt, nil +} + +// toTitleCase converts underscore-separated names to TitleCase. +func toTitleCase(s string) string { + parts := strings.Split(strings.ToLower(s), "_") + for i, part := range parts { + if len(part) > 0 { + parts[i] = strings.ToUpper(part[0:1]) + part[1:] + } + } + return strings.Join(parts, "") +} diff --git a/parser/parser.go b/parser/parser.go index c70bf527..1346b59b 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -92,7 +92,7 @@ func (p *Parser) parseStatement() (ast.Statement, error) { case TokenInsert: return p.parseInsertStatement() case TokenUpdate: - return p.parseUpdateStatement() + return p.parseUpdateOrUpdateStatisticsStatement() case TokenDelete: return p.parseDeleteStatement() case TokenDeclare: @@ -190,6 +190,14 @@ func (p *Parser) parseStatement() (ast.Statement, error) { if strings.ToUpper(p.curTok.Literal) == "RENAME" { return p.parseRenameStatement() } + // Check for FETCH cursor + if strings.ToUpper(p.curTok.Literal) == "FETCH" { + return p.parseFetchCursorStatement() + } + // Check for DEALLOCATE cursor + if strings.ToUpper(p.curTok.Literal) == "DEALLOCATE" { + return p.parseDeallocateCursorStatement() + } // Check for label (identifier followed by colon) return p.parseLabelOrError() default: diff --git a/parser/testdata/AlterDatabaseStatementTests130/metadata.json b/parser/testdata/AlterDatabaseStatementTests130/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/AlterDatabaseStatementTests130/metadata.json +++ b/parser/testdata/AlterDatabaseStatementTests130/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} diff --git a/parser/testdata/Baselines130_AlterDatabaseStatementTests130/metadata.json b/parser/testdata/Baselines130_AlterDatabaseStatementTests130/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/Baselines130_AlterDatabaseStatementTests130/metadata.json +++ b/parser/testdata/Baselines130_AlterDatabaseStatementTests130/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} diff --git a/parser/testdata/BaselinesCommon_BigIntRowCountPageCountTests/metadata.json b/parser/testdata/BaselinesCommon_BigIntRowCountPageCountTests/metadata.json index 49e9182b..e27d63a6 100644 --- a/parser/testdata/BaselinesCommon_BigIntRowCountPageCountTests/metadata.json +++ b/parser/testdata/BaselinesCommon_BigIntRowCountPageCountTests/metadata.json @@ -1 +1 @@ -{"skip": true} +{"skip": false} diff --git a/parser/testdata/BaselinesCommon_CreateStatisticsStatementTests/metadata.json b/parser/testdata/BaselinesCommon_CreateStatisticsStatementTests/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/BaselinesCommon_CreateStatisticsStatementTests/metadata.json +++ b/parser/testdata/BaselinesCommon_CreateStatisticsStatementTests/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} diff --git a/parser/testdata/BaselinesCommon_CursorStatementsTests/metadata.json b/parser/testdata/BaselinesCommon_CursorStatementsTests/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/BaselinesCommon_CursorStatementsTests/metadata.json +++ b/parser/testdata/BaselinesCommon_CursorStatementsTests/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} diff --git a/parser/testdata/BaselinesCommon_DeclareCursorStatementTests/metadata.json b/parser/testdata/BaselinesCommon_DeclareCursorStatementTests/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/BaselinesCommon_DeclareCursorStatementTests/metadata.json +++ b/parser/testdata/BaselinesCommon_DeclareCursorStatementTests/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} diff --git a/parser/testdata/BaselinesCommon_UpdateStatisticsStatementTests/metadata.json b/parser/testdata/BaselinesCommon_UpdateStatisticsStatementTests/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/BaselinesCommon_UpdateStatisticsStatementTests/metadata.json +++ b/parser/testdata/BaselinesCommon_UpdateStatisticsStatementTests/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} diff --git a/parser/testdata/BigIntRowCountPageCountTests/metadata.json b/parser/testdata/BigIntRowCountPageCountTests/metadata.json index 49e9182b..e27d63a6 100644 --- a/parser/testdata/BigIntRowCountPageCountTests/metadata.json +++ b/parser/testdata/BigIntRowCountPageCountTests/metadata.json @@ -1 +1 @@ -{"skip": true} +{"skip": false} diff --git a/parser/testdata/CreateStatisticsStatementTests/metadata.json b/parser/testdata/CreateStatisticsStatementTests/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/CreateStatisticsStatementTests/metadata.json +++ b/parser/testdata/CreateStatisticsStatementTests/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} diff --git a/parser/testdata/CursorStatementsTests/metadata.json b/parser/testdata/CursorStatementsTests/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/CursorStatementsTests/metadata.json +++ b/parser/testdata/CursorStatementsTests/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} diff --git a/parser/testdata/DeclareCursorStatementTests/metadata.json b/parser/testdata/DeclareCursorStatementTests/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/DeclareCursorStatementTests/metadata.json +++ b/parser/testdata/DeclareCursorStatementTests/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} diff --git a/parser/testdata/PhaseOne_CreateStatisticsStatementTest/ast.json b/parser/testdata/PhaseOne_CreateStatisticsStatementTest/ast.json index 0cfcb42c..56a82b29 100644 --- a/parser/testdata/PhaseOne_CreateStatisticsStatementTest/ast.json +++ b/parser/testdata/PhaseOne_CreateStatisticsStatementTest/ast.json @@ -10,6 +10,28 @@ "$type": "Identifier", "QuoteType": "NotQuoted", "Value": "stat1" + }, + "OnName": { + "$type": "SchemaObjectName", + "SchemaIdentifier": { + "$type": "Identifier", + "Value": "dbo", + "QuoteType": "NotQuoted" + }, + "BaseIdentifier": { + "$type": "Identifier", + "Value": "t1", + "QuoteType": "NotQuoted" + }, + "Count": 2, + "Identifiers": [ + { + "$ref": "Identifier" + }, + { + "$ref": "Identifier" + } + ] } } ] diff --git a/parser/testdata/UpdateStatisticsStatementTests/metadata.json b/parser/testdata/UpdateStatisticsStatementTests/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/UpdateStatisticsStatementTests/metadata.json +++ b/parser/testdata/UpdateStatisticsStatementTests/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} From 430cb17f03223a90a7285dd7efd475987c96a73f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Dec 2025 17:37:02 +0000 Subject: [PATCH 2/8] Fix ALTER DATABASE statement handling - Add UseCurrent field to AlterDatabaseRemoveFileGroupStatement - Differentiate READONLY/READWRITE (old syntax) from READ_ONLY/READ_WRITE - READONLY -> ReadOnlyOld - READ_ONLY -> ReadOnly - READWRITE -> ReadWriteOld - READ_WRITE -> ReadWrite - Always output UseCurrent in AlterDatabaseModifyFileGroupStatement - Update PhaseOne tests to include new fields --- ast/alter_database_set_statement.go | 1 + parser/marshal.go | 6 ++---- parser/parse_ddl.go | 10 ++++++++-- .../ast.json | 1 + .../ast.json | 1 + .../ast.json | 3 ++- .../ast.json | 3 ++- 7 files changed, 17 insertions(+), 8 deletions(-) diff --git a/ast/alter_database_set_statement.go b/ast/alter_database_set_statement.go index 7581ff21..037cb717 100644 --- a/ast/alter_database_set_statement.go +++ b/ast/alter_database_set_statement.go @@ -97,6 +97,7 @@ func (a *AlterDatabaseRemoveFileStatement) statement() {} type AlterDatabaseRemoveFileGroupStatement struct { DatabaseName *Identifier FileGroupName *Identifier + UseCurrent bool } func (a *AlterDatabaseRemoveFileGroupStatement) node() {} diff --git a/parser/marshal.go b/parser/marshal.go index c6def889..100a1cee 100644 --- a/parser/marshal.go +++ b/parser/marshal.go @@ -6331,10 +6331,7 @@ func alterDatabaseModifyFileGroupStatementToJSON(s *ast.AlterDatabaseModifyFileG node["FileGroup"] = identifierToJSON(s.FileGroupName) } node["MakeDefault"] = s.MakeDefault - // Only output UseCurrent when UpdatabilityOption is an autogrow option - if strings.HasPrefix(s.UpdatabilityOption, "Autogrow") { - node["UseCurrent"] = false - } + node["UseCurrent"] = false if s.NewFileGroupName != nil { node["NewFileGroupName"] = identifierToJSON(s.NewFileGroupName) } @@ -6380,6 +6377,7 @@ func alterDatabaseRemoveFileGroupStatementToJSON(s *ast.AlterDatabaseRemoveFileG if s.FileGroupName != nil { node["FileGroup"] = identifierToJSON(s.FileGroupName) } + node["UseCurrent"] = s.UseCurrent return node } diff --git a/parser/parse_ddl.go b/parser/parse_ddl.go index 24a3d580..f87e8db1 100644 --- a/parser/parse_ddl.go +++ b/parser/parse_ddl.go @@ -1580,10 +1580,16 @@ func (p *Parser) parseAlterDatabaseModifyStatement(dbName *ast.Identifier) (ast. case "DEFAULT": stmt.MakeDefault = true p.nextToken() - case "READONLY", "READ_ONLY": + case "READONLY": + stmt.UpdatabilityOption = "ReadOnlyOld" + p.nextToken() + case "READ_ONLY": stmt.UpdatabilityOption = "ReadOnly" p.nextToken() - case "READWRITE", "READ_WRITE": + case "READWRITE": + stmt.UpdatabilityOption = "ReadWriteOld" + p.nextToken() + case "READ_WRITE": stmt.UpdatabilityOption = "ReadWrite" p.nextToken() case "AUTOGROW_ALL_FILES": diff --git a/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup1StatementTest/ast.json b/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup1StatementTest/ast.json index 7b89f03c..5c81d3bc 100644 --- a/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup1StatementTest/ast.json +++ b/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup1StatementTest/ast.json @@ -17,6 +17,7 @@ "Value": "fg1" }, "MakeDefault": false, + "UseCurrent": false, "UpdatabilityOption": "ReadOnly" } ] diff --git a/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup2StatementTest/ast.json b/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup2StatementTest/ast.json index 84d0adbd..66a45ef8 100644 --- a/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup2StatementTest/ast.json +++ b/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup2StatementTest/ast.json @@ -17,6 +17,7 @@ "Value": "fg1" }, "MakeDefault": false, + "UseCurrent": false, "NewFileGroupName": { "$type": "Identifier", "QuoteType": "NotQuoted", diff --git a/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup3StatementTest/ast.json b/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup3StatementTest/ast.json index 6e14f56d..a229990c 100644 --- a/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup3StatementTest/ast.json +++ b/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup3StatementTest/ast.json @@ -16,7 +16,8 @@ "QuoteType": "SquareBracket", "Value": "fg1" }, - "MakeDefault": true + "MakeDefault": true, + "UseCurrent": false } ] } diff --git a/parser/testdata/PhaseOne_AlterDatabaseRemoveFilegroupStatementTest/ast.json b/parser/testdata/PhaseOne_AlterDatabaseRemoveFilegroupStatementTest/ast.json index 6628b251..0d0f96f0 100644 --- a/parser/testdata/PhaseOne_AlterDatabaseRemoveFilegroupStatementTest/ast.json +++ b/parser/testdata/PhaseOne_AlterDatabaseRemoveFilegroupStatementTest/ast.json @@ -15,7 +15,8 @@ "$type": "Identifier", "QuoteType": "SquareBracket", "Value": "fg1" - } + }, + "UseCurrent": false } ] } From 87d833441dc54480d70f63dcb3e47357060f75b0 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Dec 2025 17:50:38 +0000 Subject: [PATCH 3/8] Add ENABLE/DISABLE TRIGGER statement parsing and enable 2 tests Implement EnableDisableTriggerStatement AST type with parsing for: - ENABLE/DISABLE TRIGGER ALL ON table_name - ENABLE/DISABLE TRIGGER name1, name2 ON table_name - ENABLE/DISABLE TRIGGER name ON ALL SERVER - ENABLE/DISABLE TRIGGER name ON DATABASE Enable EnableDisableTriggerStatementTests and Baselines90_EnableDisableTriggerStatementTests. --- ast/enable_disable_trigger_statement.go | 12 +++ parser/marshal.go | 21 ++++++ parser/parse_statements.go | 74 +++++++++++++++++++ parser/parser.go | 8 ++ .../metadata.json | 2 +- .../metadata.json | 2 +- 6 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 ast/enable_disable_trigger_statement.go diff --git a/ast/enable_disable_trigger_statement.go b/ast/enable_disable_trigger_statement.go new file mode 100644 index 00000000..1cc75e42 --- /dev/null +++ b/ast/enable_disable_trigger_statement.go @@ -0,0 +1,12 @@ +package ast + +// EnableDisableTriggerStatement represents ENABLE/DISABLE TRIGGER statements +type EnableDisableTriggerStatement struct { + TriggerEnforcement string // "Enable" or "Disable" + All bool // true if ENABLE/DISABLE TRIGGER ALL + TriggerNames []*SchemaObjectName + TriggerObject *TriggerObject +} + +func (s *EnableDisableTriggerStatement) statement() {} +func (s *EnableDisableTriggerStatement) node() {} diff --git a/parser/marshal.go b/parser/marshal.go index 100a1cee..5bb0cc46 100644 --- a/parser/marshal.go +++ b/parser/marshal.go @@ -324,6 +324,8 @@ func statementToJSON(stmt ast.Statement) jsonNode { return alterTriggerStatementToJSON(s) case *ast.CreateTriggerStatement: return createTriggerStatementToJSON(s) + case *ast.EnableDisableTriggerStatement: + return enableDisableTriggerStatementToJSON(s) case *ast.CreateDatabaseStatement: return createDatabaseStatementToJSON(s) case *ast.CreateLoginStatement: @@ -5113,6 +5115,25 @@ func triggerActionToJSON(a *ast.TriggerAction) jsonNode { return node } +func enableDisableTriggerStatementToJSON(s *ast.EnableDisableTriggerStatement) jsonNode { + node := jsonNode{ + "$type": "EnableDisableTriggerStatement", + "TriggerEnforcement": s.TriggerEnforcement, + "All": s.All, + } + if len(s.TriggerNames) > 0 { + names := make([]jsonNode, len(s.TriggerNames)) + for i, n := range s.TriggerNames { + names[i] = schemaObjectNameToJSON(n) + } + node["TriggerNames"] = names + } + if s.TriggerObject != nil { + node["TriggerObject"] = triggerObjectToJSON(s.TriggerObject) + } + return node +} + func alterIndexStatementToJSON(s *ast.AlterIndexStatement) jsonNode { node := jsonNode{ "$type": "AlterIndexStatement", diff --git a/parser/parse_statements.go b/parser/parse_statements.go index e51a705e..2bea27c3 100644 --- a/parser/parse_statements.go +++ b/parser/parse_statements.go @@ -5229,3 +5229,77 @@ func toTitleCase(s string) string { } return strings.Join(parts, "") } + +// parseEnableDisableTriggerStatement parses ENABLE/DISABLE TRIGGER statements +func (p *Parser) parseEnableDisableTriggerStatement(enforcement string) (*ast.EnableDisableTriggerStatement, error) { + // Consume ENABLE or DISABLE + p.nextToken() + + // Expect TRIGGER + if strings.ToUpper(p.curTok.Literal) != "TRIGGER" { + return nil, fmt.Errorf("expected TRIGGER after %s, got %s", enforcement, p.curTok.Literal) + } + p.nextToken() + + stmt := &ast.EnableDisableTriggerStatement{ + TriggerEnforcement: enforcement, + } + + // Check for ALL + if strings.ToUpper(p.curTok.Literal) == "ALL" { + stmt.All = true + p.nextToken() + } else { + stmt.All = false + // Parse trigger names (comma-separated) + for { + name, err := p.parseSchemaObjectName() + if err != nil { + return nil, err + } + stmt.TriggerNames = append(stmt.TriggerNames, name) + + if p.curTok.Type != TokenComma { + break + } + p.nextToken() // consume comma + } + } + + // Expect ON + if p.curTok.Type != TokenOn { + return nil, fmt.Errorf("expected ON after trigger names, got %s", p.curTok.Literal) + } + p.nextToken() + + // Check for ALL SERVER or DATABASE or table name + stmt.TriggerObject = &ast.TriggerObject{} + + if strings.ToUpper(p.curTok.Literal) == "ALL" { + p.nextToken() + if strings.ToUpper(p.curTok.Literal) == "SERVER" { + stmt.TriggerObject.TriggerScope = "AllServer" + p.nextToken() + } else { + return nil, fmt.Errorf("expected SERVER after ALL, got %s", p.curTok.Literal) + } + } else if strings.ToUpper(p.curTok.Literal) == "DATABASE" { + stmt.TriggerObject.TriggerScope = "Database" + p.nextToken() + } else { + // Parse table name + tableName, err := p.parseSchemaObjectName() + if err != nil { + return nil, err + } + stmt.TriggerObject.Name = tableName + stmt.TriggerObject.TriggerScope = "Normal" + } + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } + + return stmt, nil +} diff --git a/parser/parser.go b/parser/parser.go index 1346b59b..eb86733d 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -198,6 +198,14 @@ func (p *Parser) parseStatement() (ast.Statement, error) { if strings.ToUpper(p.curTok.Literal) == "DEALLOCATE" { return p.parseDeallocateCursorStatement() } + // Check for ENABLE TRIGGER + if strings.ToUpper(p.curTok.Literal) == "ENABLE" { + return p.parseEnableDisableTriggerStatement("Enable") + } + // Check for DISABLE TRIGGER + if strings.ToUpper(p.curTok.Literal) == "DISABLE" { + return p.parseEnableDisableTriggerStatement("Disable") + } // Check for label (identifier followed by colon) return p.parseLabelOrError() default: diff --git a/parser/testdata/Baselines90_EnableDisableTriggerStatementTests/metadata.json b/parser/testdata/Baselines90_EnableDisableTriggerStatementTests/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/Baselines90_EnableDisableTriggerStatementTests/metadata.json +++ b/parser/testdata/Baselines90_EnableDisableTriggerStatementTests/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} diff --git a/parser/testdata/EnableDisableTriggerStatementTests/metadata.json b/parser/testdata/EnableDisableTriggerStatementTests/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/EnableDisableTriggerStatementTests/metadata.json +++ b/parser/testdata/EnableDisableTriggerStatementTests/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} From 987dad7ef393f8024e003103572360dd47d20ad7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Dec 2025 18:00:01 +0000 Subject: [PATCH 4/8] Add DelayedDurabilityDatabaseOption and CreateMessageType features, enable 3 tests - Add DelayedDurabilityDatabaseOption type for ALTER DATABASE SET DELAYED_DURABILITY - Parse DELAYED_DURABILITY = DISABLED/ALLOWED/FORCED values - Add ValidationMethod, Owner, XmlSchemaCollectionName to CreateMessageTypeStatement - Parse CREATE MESSAGE TYPE ... AUTHORIZATION ... VALIDATION = ... Enable tests: - AlterDatabaseOptionsTests120 - Baselines120_AlterDatabaseOptionsTests120 - Baselines90_CreateMessageTypeStatementTests --- ast/alter_database_set_statement.go | 9 ++++ ast/create_simple_statements.go | 5 +- parser/marshal.go | 15 ++++++ parser/parse_ddl.go | 13 +++++ parser/parse_statements.go | 48 ++++++++++++++++++- .../metadata.json | 2 +- .../metadata.json | 2 +- .../metadata.json | 2 +- 8 files changed, 90 insertions(+), 6 deletions(-) diff --git a/ast/alter_database_set_statement.go b/ast/alter_database_set_statement.go index 037cb717..9d3c95c8 100644 --- a/ast/alter_database_set_statement.go +++ b/ast/alter_database_set_statement.go @@ -35,6 +35,15 @@ type OnOffDatabaseOption struct { func (o *OnOffDatabaseOption) node() {} func (o *OnOffDatabaseOption) databaseOption() {} +// DelayedDurabilityDatabaseOption represents DELAYED_DURABILITY option +type DelayedDurabilityDatabaseOption struct { + OptionKind string // "DelayedDurability" + Value string // "Disabled", "Allowed", "Forced" +} + +func (d *DelayedDurabilityDatabaseOption) node() {} +func (d *DelayedDurabilityDatabaseOption) databaseOption() {} + // AlterDatabaseAddFileStatement represents ALTER DATABASE ... ADD FILE statement type AlterDatabaseAddFileStatement struct { DatabaseName *Identifier diff --git a/ast/create_simple_statements.go b/ast/create_simple_statements.go index c424d72b..b4c68b3f 100644 --- a/ast/create_simple_statements.go +++ b/ast/create_simple_statements.go @@ -82,7 +82,10 @@ func (s *CreateSymmetricKeyStatement) statement() {} // CreateMessageTypeStatement represents a CREATE MESSAGE TYPE statement. type CreateMessageTypeStatement struct { - Name *Identifier `json:"Name,omitempty"` + Name *Identifier `json:"Name,omitempty"` + Owner *Identifier `json:"Owner,omitempty"` + ValidationMethod string `json:"ValidationMethod,omitempty"` + XmlSchemaCollectionName *SchemaObjectName `json:"XmlSchemaCollectionName,omitempty"` } func (s *CreateMessageTypeStatement) node() {} diff --git a/parser/marshal.go b/parser/marshal.go index 5bb0cc46..e7f30d1c 100644 --- a/parser/marshal.go +++ b/parser/marshal.go @@ -791,6 +791,12 @@ func databaseOptionToJSON(opt ast.DatabaseOption) jsonNode { "OptionKind": o.OptionKind, "OptionState": o.OptionState, } + case *ast.DelayedDurabilityDatabaseOption: + return jsonNode{ + "$type": "DelayedDurabilityDatabaseOption", + "Value": o.Value, + "OptionKind": o.OptionKind, + } default: return jsonNode{"$type": "UnknownDatabaseOption"} } @@ -6113,6 +6119,15 @@ func createMessageTypeStatementToJSON(s *ast.CreateMessageTypeStatement) jsonNod if s.Name != nil { node["Name"] = identifierToJSON(s.Name) } + if s.Owner != nil { + node["Owner"] = identifierToJSON(s.Owner) + } + if s.ValidationMethod != "" { + node["ValidationMethod"] = s.ValidationMethod + } + if s.XmlSchemaCollectionName != nil { + node["XmlSchemaCollectionName"] = schemaObjectNameToJSON(s.XmlSchemaCollectionName) + } return node } diff --git a/parser/parse_ddl.go b/parser/parse_ddl.go index f87e8db1..6a0efac1 100644 --- a/parser/parse_ddl.go +++ b/parser/parse_ddl.go @@ -1460,6 +1460,19 @@ func (p *Parser) parseAlterDatabaseSetStatement(dbName *ast.Identifier) (*ast.Al OptionState: capitalizeFirst(optionValue), } stmt.Options = append(stmt.Options, opt) + case "DELAYED_DURABILITY": + // This option uses = with DISABLED/ALLOWED/FORCED values + if p.curTok.Type != TokenEquals { + return nil, fmt.Errorf("expected = after %s, got %s", optionName, p.curTok.Literal) + } + p.nextToken() + optionValue := strings.ToUpper(p.curTok.Literal) + p.nextToken() + opt := &ast.DelayedDurabilityDatabaseOption{ + OptionKind: "DelayedDurability", + Value: capitalizeFirst(optionValue), + } + stmt.Options = append(stmt.Options, opt) default: // Handle generic options with = syntax (e.g., OPTIMIZED_LOCKING = ON) if p.curTok.Type == TokenEquals { diff --git a/parser/parse_statements.go b/parser/parse_statements.go index 2bea27c3..4c108419 100644 --- a/parser/parse_statements.go +++ b/parser/parse_statements.go @@ -4622,8 +4622,52 @@ func (p *Parser) parseCreateMessageTypeStatement() (*ast.CreateMessageTypeStatem Name: p.parseIdentifier(), } - // Skip rest of statement - p.skipToEndOfStatement() + // Optional AUTHORIZATION + if strings.ToUpper(p.curTok.Literal) == "AUTHORIZATION" { + p.nextToken() + stmt.Owner = p.parseIdentifier() + } + + // Optional VALIDATION + if strings.ToUpper(p.curTok.Literal) == "VALIDATION" { + p.nextToken() + if p.curTok.Type == TokenEquals { + p.nextToken() + } + valMethod := strings.ToUpper(p.curTok.Literal) + switch valMethod { + case "WELL_FORMED_XML": + stmt.ValidationMethod = "WellFormedXml" + p.nextToken() + case "NONE": + stmt.ValidationMethod = "None" + p.nextToken() + case "EMPTY": + stmt.ValidationMethod = "Empty" + p.nextToken() + case "VALID_XML": + stmt.ValidationMethod = "ValidXml" + p.nextToken() + // Expect WITH SCHEMA COLLECTION + if strings.ToUpper(p.curTok.Literal) == "WITH" { + p.nextToken() + if strings.ToUpper(p.curTok.Literal) == "SCHEMA" { + p.nextToken() + if strings.ToUpper(p.curTok.Literal) == "COLLECTION" { + p.nextToken() + schemaName, _ := p.parseSchemaObjectName() + stmt.XmlSchemaCollectionName = schemaName + } + } + } + } + } + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } + return stmt, nil } diff --git a/parser/testdata/AlterDatabaseOptionsTests120/metadata.json b/parser/testdata/AlterDatabaseOptionsTests120/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/AlterDatabaseOptionsTests120/metadata.json +++ b/parser/testdata/AlterDatabaseOptionsTests120/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} diff --git a/parser/testdata/Baselines120_AlterDatabaseOptionsTests120/metadata.json b/parser/testdata/Baselines120_AlterDatabaseOptionsTests120/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/Baselines120_AlterDatabaseOptionsTests120/metadata.json +++ b/parser/testdata/Baselines120_AlterDatabaseOptionsTests120/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} diff --git a/parser/testdata/Baselines90_CreateMessageTypeStatementTests/metadata.json b/parser/testdata/Baselines90_CreateMessageTypeStatementTests/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/Baselines90_CreateMessageTypeStatementTests/metadata.json +++ b/parser/testdata/Baselines90_CreateMessageTypeStatementTests/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} From e4d82949de8e0c671fb2b5b55bc6a76ef8fb5d3e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Dec 2025 18:05:59 +0000 Subject: [PATCH 5/8] Add ApplicationRoleOption support and enable 2 tests - Add ApplicationRoleOption type with OptionKind and Value fields - Parse CREATE APPLICATION ROLE ... WITH PASSWORD/DEFAULT_SCHEMA/NAME/LOGIN - Parse ALTER APPLICATION ROLE ... WITH options - Strip quotes from password string literal values Enable tests: - ApplicationRoleStatementTests - Baselines90_ApplicationRoleStatementTests --- ast/alter_simple_statements.go | 3 +- ast/create_simple_statements.go | 11 ++- parser/marshal.go | 25 ++++++ parser/parse_ddl.go | 16 +++- parser/parse_statements.go | 89 ++++++++++++++++++- .../metadata.json | 2 +- .../metadata.json | 2 +- 7 files changed, 140 insertions(+), 8 deletions(-) diff --git a/ast/alter_simple_statements.go b/ast/alter_simple_statements.go index 2f338350..8a598a46 100644 --- a/ast/alter_simple_statements.go +++ b/ast/alter_simple_statements.go @@ -48,7 +48,8 @@ func (s *AlterCertificateStatement) statement() {} // AlterApplicationRoleStatement represents an ALTER APPLICATION ROLE statement. type AlterApplicationRoleStatement struct { - Name *Identifier `json:"Name,omitempty"` + Name *Identifier `json:"Name,omitempty"` + ApplicationRoleOptions []*ApplicationRoleOption `json:"ApplicationRoleOptions,omitempty"` } func (s *AlterApplicationRoleStatement) node() {} diff --git a/ast/create_simple_statements.go b/ast/create_simple_statements.go index b4c68b3f..16719f4e 100644 --- a/ast/create_simple_statements.go +++ b/ast/create_simple_statements.go @@ -101,12 +101,21 @@ func (s *CreateRemoteServiceBindingStatement) statement() {} // CreateApplicationRoleStatement represents a CREATE APPLICATION ROLE statement. type CreateApplicationRoleStatement struct { - Name *Identifier `json:"Name,omitempty"` + Name *Identifier `json:"Name,omitempty"` + ApplicationRoleOptions []*ApplicationRoleOption `json:"ApplicationRoleOptions,omitempty"` } func (s *CreateApplicationRoleStatement) node() {} func (s *CreateApplicationRoleStatement) statement() {} +// ApplicationRoleOption represents an option in CREATE/ALTER APPLICATION ROLE +type ApplicationRoleOption struct { + OptionKind string `json:"OptionKind,omitempty"` + Value *IdentifierOrValueExpression `json:"Value,omitempty"` +} + +func (o *ApplicationRoleOption) node() {} + // CreateFulltextCatalogStatement represents a CREATE FULLTEXT CATALOG statement. type CreateFulltextCatalogStatement struct { Name *Identifier `json:"Name,omitempty"` diff --git a/parser/marshal.go b/parser/marshal.go index e7f30d1c..aa51c5df 100644 --- a/parser/marshal.go +++ b/parser/marshal.go @@ -5912,6 +5912,13 @@ func alterApplicationRoleStatementToJSON(s *ast.AlterApplicationRoleStatement) j if s.Name != nil { node["Name"] = identifierToJSON(s.Name) } + if len(s.ApplicationRoleOptions) > 0 { + opts := make([]jsonNode, len(s.ApplicationRoleOptions)) + for i, opt := range s.ApplicationRoleOptions { + opts[i] = applicationRoleOptionToJSON(opt) + } + node["ApplicationRoleOptions"] = opts + } return node } @@ -6188,6 +6195,24 @@ func createApplicationRoleStatementToJSON(s *ast.CreateApplicationRoleStatement) if s.Name != nil { node["Name"] = identifierToJSON(s.Name) } + if len(s.ApplicationRoleOptions) > 0 { + opts := make([]jsonNode, len(s.ApplicationRoleOptions)) + for i, opt := range s.ApplicationRoleOptions { + opts[i] = applicationRoleOptionToJSON(opt) + } + node["ApplicationRoleOptions"] = opts + } + return node +} + +func applicationRoleOptionToJSON(opt *ast.ApplicationRoleOption) jsonNode { + node := jsonNode{ + "$type": "ApplicationRoleOption", + "OptionKind": opt.OptionKind, + } + if opt.Value != nil { + node["Value"] = identifierOrValueExpressionToJSON(opt.Value) + } return node } diff --git a/parser/parse_ddl.go b/parser/parse_ddl.go index 6a0efac1..62afb32e 100644 --- a/parser/parse_ddl.go +++ b/parser/parse_ddl.go @@ -3334,8 +3334,20 @@ func (p *Parser) parseAlterApplicationRoleStatement() (*ast.AlterApplicationRole // Parse role name stmt.Name = p.parseIdentifier() - // Skip rest of statement - p.skipToEndOfStatement() + // Optional WITH clause + if p.curTok.Type == TokenWith { + p.nextToken() + opts, err := p.parseApplicationRoleOptions() + if err != nil { + return nil, err + } + stmt.ApplicationRoleOptions = opts + } + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } return stmt, nil } diff --git a/parser/parse_statements.go b/parser/parse_statements.go index 4c108419..e1fc1cfd 100644 --- a/parser/parse_statements.go +++ b/parser/parse_statements.go @@ -4742,11 +4742,96 @@ func (p *Parser) parseCreateApplicationRoleStatement() (*ast.CreateApplicationRo Name: p.parseIdentifier(), } - // Skip rest of statement - p.skipToEndOfStatement() + // Optional WITH clause + if p.curTok.Type == TokenWith { + p.nextToken() + opts, err := p.parseApplicationRoleOptions() + if err != nil { + return nil, err + } + stmt.ApplicationRoleOptions = opts + } + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } + return stmt, nil } +func (p *Parser) parseApplicationRoleOptions() ([]*ast.ApplicationRoleOption, error) { + var options []*ast.ApplicationRoleOption + + for { + optionName := strings.ToUpper(p.curTok.Literal) + p.nextToken() + + // Expect = + if p.curTok.Type != TokenEquals { + return nil, fmt.Errorf("expected = after %s, got %s", optionName, p.curTok.Literal) + } + p.nextToken() + + opt := &ast.ApplicationRoleOption{} + + switch optionName { + case "PASSWORD": + opt.OptionKind = "Password" + // Parse string literal + if p.curTok.Type == TokenString { + val := p.curTok.Literal + // Strip quotes from string literal + if len(val) >= 2 && (val[0] == '\'' && val[len(val)-1] == '\'') { + val = val[1 : len(val)-1] + } + opt.Value = &ast.IdentifierOrValueExpression{ + Value: val, + ValueExpression: &ast.StringLiteral{ + Value: val, + LiteralType: "String", + }, + } + p.nextToken() + } + case "DEFAULT_SCHEMA": + opt.OptionKind = "DefaultSchema" + // Parse identifier + id := p.parseIdentifier() + opt.Value = &ast.IdentifierOrValueExpression{ + Value: id.Value, + Identifier: id, + } + case "NAME": + opt.OptionKind = "Name" + id := p.parseIdentifier() + opt.Value = &ast.IdentifierOrValueExpression{ + Value: id.Value, + Identifier: id, + } + case "LOGIN": + opt.OptionKind = "Login" + id := p.parseIdentifier() + opt.Value = &ast.IdentifierOrValueExpression{ + Value: id.Value, + Identifier: id, + } + default: + // Unknown option, skip + p.nextToken() + } + + options = append(options, opt) + + if p.curTok.Type != TokenComma { + break + } + p.nextToken() // consume comma + } + + return options, nil +} + func (p *Parser) parseCreateFulltextStatement() (ast.Statement, error) { p.nextToken() // consume FULLTEXT diff --git a/parser/testdata/ApplicationRoleStatementTests/metadata.json b/parser/testdata/ApplicationRoleStatementTests/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/ApplicationRoleStatementTests/metadata.json +++ b/parser/testdata/ApplicationRoleStatementTests/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} diff --git a/parser/testdata/Baselines90_ApplicationRoleStatementTests/metadata.json b/parser/testdata/Baselines90_ApplicationRoleStatementTests/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/Baselines90_ApplicationRoleStatementTests/metadata.json +++ b/parser/testdata/Baselines90_ApplicationRoleStatementTests/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} From 9a336e1e48c300f99c9b52fdbed28a0de78df170 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Dec 2025 18:15:09 +0000 Subject: [PATCH 6/8] Add QueueOptions support and enable 2 tests - Add QueueOption, QueueStateOption, QueueOptionSimple AST types - Parse WITH clause for CREATE QUEUE and ALTER QUEUE - Support STATUS, RETENTION, POISON_MESSAGE_HANDLING, ACTIVATION options - Add JSON marshaling for queue options - Enable Baselines100_QueueStatementTests100 and QueueStatementTests100 --- ast/alter_simple_statements.go | 3 +- ast/create_simple_statements.go | 26 +++- parser/marshal.go | 34 +++++ parser/parse_ddl.go | 10 ++ parser/parse_statements.go | 117 ++++++++++++++++++ .../metadata.json | 2 +- .../QueueStatementTests100/metadata.json | 2 +- 7 files changed, 190 insertions(+), 4 deletions(-) diff --git a/ast/alter_simple_statements.go b/ast/alter_simple_statements.go index 8a598a46..1c76508c 100644 --- a/ast/alter_simple_statements.go +++ b/ast/alter_simple_statements.go @@ -65,7 +65,8 @@ func (s *AlterAsymmetricKeyStatement) statement() {} // AlterQueueStatement represents an ALTER QUEUE statement. type AlterQueueStatement struct { - Name *SchemaObjectName `json:"Name,omitempty"` + Name *SchemaObjectName `json:"Name,omitempty"` + QueueOptions []QueueOption `json:"QueueOptions,omitempty"` } func (s *AlterQueueStatement) node() {} diff --git a/ast/create_simple_statements.go b/ast/create_simple_statements.go index 16719f4e..0b0354f4 100644 --- a/ast/create_simple_statements.go +++ b/ast/create_simple_statements.go @@ -24,9 +24,33 @@ type CreateServiceStatement struct { func (s *CreateServiceStatement) node() {} func (s *CreateServiceStatement) statement() {} +// QueueOption is an interface for queue options. +type QueueOption interface { + node() + queueOption() +} + +// QueueStateOption represents a queue state option (STATUS, RETENTION, POISON_MESSAGE_HANDLING). +type QueueStateOption struct { + OptionState string `json:"OptionState,omitempty"` // "On" or "Off" + OptionKind string `json:"OptionKind,omitempty"` // "Status", "Retention", "PoisonMessageHandlingStatus" +} + +func (o *QueueStateOption) node() {} +func (o *QueueStateOption) queueOption() {} + +// QueueOptionSimple represents a simple queue option like ActivationDrop. +type QueueOptionSimple struct { + OptionKind string `json:"OptionKind,omitempty"` // e.g. "ActivationDrop" +} + +func (o *QueueOptionSimple) node() {} +func (o *QueueOptionSimple) queueOption() {} + // CreateQueueStatement represents a CREATE QUEUE statement. type CreateQueueStatement struct { - Name *SchemaObjectName `json:"Name,omitempty"` + Name *SchemaObjectName `json:"Name,omitempty"` + QueueOptions []QueueOption `json:"QueueOptions,omitempty"` } func (s *CreateQueueStatement) node() {} diff --git a/parser/marshal.go b/parser/marshal.go index aa51c5df..1f66c22c 100644 --- a/parser/marshal.go +++ b/parser/marshal.go @@ -5939,6 +5939,13 @@ func alterQueueStatementToJSON(s *ast.AlterQueueStatement) jsonNode { if s.Name != nil { node["Name"] = schemaObjectNameToJSON(s.Name) } + if len(s.QueueOptions) > 0 { + opts := make([]jsonNode, len(s.QueueOptions)) + for i, opt := range s.QueueOptions { + opts[i] = queueOptionToJSON(opt) + } + node["QueueOptions"] = opts + } return node } @@ -6155,9 +6162,36 @@ func createQueueStatementToJSON(s *ast.CreateQueueStatement) jsonNode { if s.Name != nil { node["Name"] = schemaObjectNameToJSON(s.Name) } + if len(s.QueueOptions) > 0 { + opts := make([]jsonNode, len(s.QueueOptions)) + for i, opt := range s.QueueOptions { + opts[i] = queueOptionToJSON(opt) + } + node["QueueOptions"] = opts + } return node } +func queueOptionToJSON(opt ast.QueueOption) jsonNode { + switch o := opt.(type) { + case *ast.QueueStateOption: + node := jsonNode{ + "$type": "QueueStateOption", + "OptionState": o.OptionState, + "OptionKind": o.OptionKind, + } + return node + case *ast.QueueOptionSimple: + node := jsonNode{ + "$type": "QueueOption", + "OptionKind": o.OptionKind, + } + return node + default: + return jsonNode{"$type": "QueueOption"} + } +} + func createRouteStatementToJSON(s *ast.CreateRouteStatement) jsonNode { node := jsonNode{ "$type": "CreateRouteStatement", diff --git a/parser/parse_ddl.go b/parser/parse_ddl.go index 62afb32e..a6476d71 100644 --- a/parser/parse_ddl.go +++ b/parser/parse_ddl.go @@ -3384,6 +3384,16 @@ func (p *Parser) parseAlterQueueStatement() (*ast.AlterQueueStatement, error) { } stmt.Name = name + // Check for WITH clause + if strings.ToUpper(p.curTok.Literal) == "WITH" { + p.nextToken() // consume WITH + opts, err := p.parseQueueOptions() + if err != nil { + return nil, err + } + stmt.QueueOptions = opts + } + // Skip rest of statement p.skipToEndOfStatement() diff --git a/parser/parse_statements.go b/parser/parse_statements.go index e1fc1cfd..54de73b4 100644 --- a/parser/parse_statements.go +++ b/parser/parse_statements.go @@ -4691,11 +4691,128 @@ func (p *Parser) parseCreateQueueStatement() (*ast.CreateQueueStatement, error) Name: name, } + // Check for WITH clause + if strings.ToUpper(p.curTok.Literal) == "WITH" { + p.nextToken() // consume WITH + opts, err := p.parseQueueOptions() + if err != nil { + return nil, err + } + stmt.QueueOptions = opts + } + // Skip rest of statement p.skipToEndOfStatement() return stmt, nil } +func (p *Parser) parseQueueOptions() ([]ast.QueueOption, error) { + var options []ast.QueueOption + + for { + optName := strings.ToUpper(p.curTok.Literal) + switch optName { + case "STATUS": + p.nextToken() // consume STATUS + if p.curTok.Type != TokenEquals { + return nil, fmt.Errorf("expected = after STATUS, got %s", p.curTok.Literal) + } + p.nextToken() // consume = + state := strings.ToUpper(p.curTok.Literal) + p.nextToken() // consume ON/OFF + opt := &ast.QueueStateOption{ + OptionState: capitalizeFirst(state), + OptionKind: "Status", + } + options = append(options, opt) + + case "RETENTION": + p.nextToken() // consume RETENTION + if p.curTok.Type != TokenEquals { + return nil, fmt.Errorf("expected = after RETENTION, got %s", p.curTok.Literal) + } + p.nextToken() // consume = + state := strings.ToUpper(p.curTok.Literal) + p.nextToken() // consume ON/OFF + opt := &ast.QueueStateOption{ + OptionState: capitalizeFirst(state), + OptionKind: "Retention", + } + options = append(options, opt) + + case "POISON_MESSAGE_HANDLING": + p.nextToken() // consume POISON_MESSAGE_HANDLING + if p.curTok.Type != TokenLParen { + return nil, fmt.Errorf("expected ( after POISON_MESSAGE_HANDLING, got %s", p.curTok.Literal) + } + p.nextToken() // consume ( + // Expect STATUS = ON/OFF + if strings.ToUpper(p.curTok.Literal) != "STATUS" { + return nil, fmt.Errorf("expected STATUS in POISON_MESSAGE_HANDLING, got %s", p.curTok.Literal) + } + p.nextToken() // consume STATUS + if p.curTok.Type != TokenEquals { + return nil, fmt.Errorf("expected = after STATUS in POISON_MESSAGE_HANDLING, got %s", p.curTok.Literal) + } + p.nextToken() // consume = + state := strings.ToUpper(p.curTok.Literal) + p.nextToken() // consume ON/OFF + if p.curTok.Type != TokenRParen { + return nil, fmt.Errorf("expected ) after POISON_MESSAGE_HANDLING status, got %s", p.curTok.Literal) + } + p.nextToken() // consume ) + opt := &ast.QueueStateOption{ + OptionState: capitalizeFirst(state), + OptionKind: "PoisonMessageHandlingStatus", + } + options = append(options, opt) + + case "ACTIVATION": + p.nextToken() // consume ACTIVATION + if p.curTok.Type != TokenLParen { + return nil, fmt.Errorf("expected ( after ACTIVATION, got %s", p.curTok.Literal) + } + p.nextToken() // consume ( + // Check for DROP or other activation options + if strings.ToUpper(p.curTok.Literal) == "DROP" { + p.nextToken() // consume DROP + if p.curTok.Type != TokenRParen { + return nil, fmt.Errorf("expected ) after ACTIVATION DROP, got %s", p.curTok.Literal) + } + p.nextToken() // consume ) + opt := &ast.QueueOptionSimple{ + OptionKind: "ActivationDrop", + } + options = append(options, opt) + } else { + // Skip to end of activation clause + depth := 1 + for depth > 0 && p.curTok.Type != TokenEOF { + if p.curTok.Type == TokenLParen { + depth++ + } else if p.curTok.Type == TokenRParen { + depth-- + } + p.nextToken() + } + } + + default: + // Unknown option, return what we have + return options, nil + } + + // Check for comma separator + if p.curTok.Type == TokenComma { + p.nextToken() + } else { + break + } + } + + return options, nil +} + func (p *Parser) parseCreateRouteStatement() (*ast.CreateRouteStatement, error) { p.nextToken() // consume ROUTE diff --git a/parser/testdata/Baselines100_QueueStatementTests100/metadata.json b/parser/testdata/Baselines100_QueueStatementTests100/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/Baselines100_QueueStatementTests100/metadata.json +++ b/parser/testdata/Baselines100_QueueStatementTests100/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} diff --git a/parser/testdata/QueueStatementTests100/metadata.json b/parser/testdata/QueueStatementTests100/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/QueueStatementTests100/metadata.json +++ b/parser/testdata/QueueStatementTests100/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} From f53711edc736afc02a43eab01b58d48843ac35a8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Dec 2025 18:19:59 +0000 Subject: [PATCH 7/8] Add CREATE DATABASE WITH options and enable 2 tests - Add IdentifierDatabaseOption and CreateDatabaseOption types - Parse WITH LEDGER and CATALOG_COLLATION options for CREATE DATABASE - Add JSON marshaling for new database options - Only output AttachMode when options are present - Enable CreateDatabaseTests160 and Baselines160_CreateDatabaseTests160 --- ast/alter_database_set_statement.go | 20 +++++++ ast/create_simple_statements.go | 4 +- parser/marshal.go | 33 +++++++++++ parser/parse_statements.go | 59 +++++++++++++++++++ .../metadata.json | 2 +- .../CreateDatabaseTests160/metadata.json | 2 +- 6 files changed, 117 insertions(+), 3 deletions(-) diff --git a/ast/alter_database_set_statement.go b/ast/alter_database_set_statement.go index 9d3c95c8..b2c3bfec 100644 --- a/ast/alter_database_set_statement.go +++ b/ast/alter_database_set_statement.go @@ -44,6 +44,26 @@ type DelayedDurabilityDatabaseOption struct { func (d *DelayedDurabilityDatabaseOption) node() {} func (d *DelayedDurabilityDatabaseOption) databaseOption() {} +// IdentifierDatabaseOption represents a database option with an identifier value +type IdentifierDatabaseOption struct { + OptionKind string `json:"OptionKind,omitempty"` // "CatalogCollation" + Value *Identifier `json:"Value,omitempty"` +} + +func (i *IdentifierDatabaseOption) node() {} +func (i *IdentifierDatabaseOption) databaseOption() {} + +// CreateDatabaseOption is an interface for CREATE DATABASE options (can be DatabaseOption) +type CreateDatabaseOption interface { + node() + createDatabaseOption() +} + +// Make existing database options implement CreateDatabaseOption +func (o *OnOffDatabaseOption) createDatabaseOption() {} +func (i *IdentifierDatabaseOption) createDatabaseOption() {} +func (d *DelayedDurabilityDatabaseOption) createDatabaseOption() {} + // AlterDatabaseAddFileStatement represents ALTER DATABASE ... ADD FILE statement type AlterDatabaseAddFileStatement struct { DatabaseName *Identifier diff --git a/ast/create_simple_statements.go b/ast/create_simple_statements.go index 0b0354f4..377722da 100644 --- a/ast/create_simple_statements.go +++ b/ast/create_simple_statements.go @@ -2,7 +2,9 @@ package ast // CreateDatabaseStatement represents a CREATE DATABASE statement. type CreateDatabaseStatement struct { - DatabaseName *Identifier `json:"DatabaseName,omitempty"` + DatabaseName *Identifier `json:"DatabaseName,omitempty"` + Options []CreateDatabaseOption `json:"Options,omitempty"` + AttachMode string `json:"AttachMode,omitempty"` // "None", "Attach", "AttachRebuildLog" } func (s *CreateDatabaseStatement) node() {} diff --git a/parser/marshal.go b/parser/marshal.go index 1f66c22c..af408703 100644 --- a/parser/marshal.go +++ b/parser/marshal.go @@ -6070,9 +6070,42 @@ func createDatabaseStatementToJSON(s *ast.CreateDatabaseStatement) jsonNode { if s.DatabaseName != nil { node["DatabaseName"] = identifierToJSON(s.DatabaseName) } + if len(s.Options) > 0 { + opts := make([]jsonNode, len(s.Options)) + for i, opt := range s.Options { + opts[i] = createDatabaseOptionToJSON(opt) + } + node["Options"] = opts + // Only output AttachMode when there are options + if s.AttachMode != "" { + node["AttachMode"] = s.AttachMode + } + } return node } +func createDatabaseOptionToJSON(opt ast.CreateDatabaseOption) jsonNode { + switch o := opt.(type) { + case *ast.OnOffDatabaseOption: + return jsonNode{ + "$type": "OnOffDatabaseOption", + "OptionState": o.OptionState, + "OptionKind": o.OptionKind, + } + case *ast.IdentifierDatabaseOption: + node := jsonNode{ + "$type": "IdentifierDatabaseOption", + "OptionKind": o.OptionKind, + } + if o.Value != nil { + node["Value"] = identifierToJSON(o.Value) + } + return node + default: + return jsonNode{"$type": "CreateDatabaseOption"} + } +} + func createLoginStatementToJSON(s *ast.CreateLoginStatement) jsonNode { node := jsonNode{ "$type": "CreateLoginStatement", diff --git a/parser/parse_statements.go b/parser/parse_statements.go index 54de73b4..0dcf620c 100644 --- a/parser/parse_statements.go +++ b/parser/parse_statements.go @@ -4531,6 +4531,17 @@ func (p *Parser) parseCreateDatabaseStatement() (ast.Statement, error) { stmt := &ast.CreateDatabaseStatement{ DatabaseName: p.parseIdentifier(), + AttachMode: "None", + } + + // Check for WITH clause + if p.curTok.Type == TokenWith { + p.nextToken() // consume WITH + opts, err := p.parseCreateDatabaseOptions() + if err != nil { + return nil, err + } + stmt.Options = opts } // Skip rest of statement @@ -4538,6 +4549,54 @@ func (p *Parser) parseCreateDatabaseStatement() (ast.Statement, error) { return stmt, nil } +func (p *Parser) parseCreateDatabaseOptions() ([]ast.CreateDatabaseOption, error) { + var options []ast.CreateDatabaseOption + + for { + optName := strings.ToUpper(p.curTok.Literal) + switch optName { + case "LEDGER": + p.nextToken() // consume LEDGER + if p.curTok.Type != TokenEquals { + return nil, fmt.Errorf("expected = after LEDGER, got %s", p.curTok.Literal) + } + p.nextToken() // consume = + state := strings.ToUpper(p.curTok.Literal) + p.nextToken() // consume ON/OFF + opt := &ast.OnOffDatabaseOption{ + OptionKind: "Ledger", + OptionState: capitalizeFirst(state), + } + options = append(options, opt) + + case "CATALOG_COLLATION": + p.nextToken() // consume CATALOG_COLLATION + if p.curTok.Type != TokenEquals { + return nil, fmt.Errorf("expected = after CATALOG_COLLATION, got %s", p.curTok.Literal) + } + p.nextToken() // consume = + opt := &ast.IdentifierDatabaseOption{ + OptionKind: "CatalogCollation", + Value: p.parseIdentifier(), + } + options = append(options, opt) + + default: + // Unknown option, return what we have + return options, nil + } + + // Check for comma separator + if p.curTok.Type == TokenComma { + p.nextToken() + } else { + break + } + } + + return options, nil +} + func (p *Parser) parseCreateLoginStatement() (*ast.CreateLoginStatement, error) { p.nextToken() // consume LOGIN diff --git a/parser/testdata/Baselines160_CreateDatabaseTests160/metadata.json b/parser/testdata/Baselines160_CreateDatabaseTests160/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/Baselines160_CreateDatabaseTests160/metadata.json +++ b/parser/testdata/Baselines160_CreateDatabaseTests160/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} diff --git a/parser/testdata/CreateDatabaseTests160/metadata.json b/parser/testdata/CreateDatabaseTests160/metadata.json index 92f70877..e27d63a6 100644 --- a/parser/testdata/CreateDatabaseTests160/metadata.json +++ b/parser/testdata/CreateDatabaseTests160/metadata.json @@ -1 +1 @@ -{"skip": true} \ No newline at end of file +{"skip": false} From f38f4755bce45bb5a66a4583d1de0a0d8a838956 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Dec 2025 18:23:02 +0000 Subject: [PATCH 8/8] Update skipped_tests_by_size.txt after enabling tests --- skipped_tests_by_size.txt | 49 --------------------------------------- 1 file changed, 49 deletions(-) diff --git a/skipped_tests_by_size.txt b/skipped_tests_by_size.txt index d466d6a1..0205bb8a 100644 --- a/skipped_tests_by_size.txt +++ b/skipped_tests_by_size.txt @@ -9,66 +9,32 @@ 129 CreateSchemaStatementErrorTests 133 GetTokenTypesTests 135 SimpleJsonObjectReturn160 -150 Baselines150_DeclareTableVariableTests150 164 Baselines100_RowsetsInSelectTests100 167 Baselines90_SetVariableStatementTests90 168 CreateTriggerStatementErrorTests -178 Baselines110_UseFederationTests110 -181 CreateAlterFederationStatementTestsAzure110 -183 Baselines100_QueueStatementTests100 -183 BaselinesCommon_DeclareTableStatementTests 183 CreateExternalFileFormatStatementTests160 -184 Baselines110_CreateAlterFederationStatementTestsAzure110 -184 BaselinesCommon_SaveTransactionStatementTests -194 AlterServerConfigurationStatementTests -196 Baselines100_AlterServerConfigurationStatementTests 203 Baselines160_CreateExternalFileFormatStatementTests160 208 BaselinesCommon_AlterProcedureStatementTests 208 RowsetsInSelectTests100 -213 Baselines150_AlterDatabaseScopedConfigClearProcCacheTests150 -213 DeclareTableVariableTests150 -218 AlterDatabaseScopedConfigClearProcCacheTests150 223 Baselines160_CreateExternalDataSourceStatementTests160 223 CreateExternalDataSourceStatementTests160 224 Baselines100_MiscTests100 224 Baselines150_AlterServerConfigurationExternalAuthenticationTests150 -224 BaselinesCommon_BigIntRowCountPageCountTests -224 BigIntRowCountPageCountTests 225 AlterServerConfigurationExternalAuthenticationTests150 -225 DeclareTableStatementTests 226 Baselines90_RowsetsInSelectTests90 231 Baselines90_ExecuteStatementTests90 -233 AlterResourceGovernorStatementTests -233 Baselines100_AlterResourceGovernorStatementTests -235 AlterFulltextCatalogStatementTests -235 UseFederationTests110 236 AlterTableStatementTests140_Azure 236 Baselines140_AlterTableStatementTests140_Azure 239 Baselines100_EventNotificationStatementTests100 -239 Baselines120_AlterDatabaseOptionsTests120 239 Baselines160_FuzzyStringMatchingTests160 240 RowsetsInSelectTests90 -241 Baselines150_AlterDatabaseScopedConfigForSecondaryClearProcCacheTests150 -241 Baselines90_AlterCertificateStatementTests -242 CryptographicProviderStatementTests 243 CreateWorkloadClassifierStatementSqlDwTests -244 AlterDatabaseScopedConfigForSecondaryClearProcCacheTests150 -244 QueueStatementTests100 -245 Baselines130_RenameStatementTests -245 Baselines90_AlterFulltextCatalogStatementTests 245 BaselinesCommon_SetVariableStatementTests -245 RenameStatementTests -247 SaveTransactionStatementTests 247 VectorTypeSecondParameterTests170 -249 AlterDatabaseOptionsTests120 250 CreateSpatialIndexStatementTests110 253 Baselines90_AlterTableDropTableElementStatementTests90 257 MiscTests100 -260 Baselines100_CryptographicProviderStatementTests 268 Baselines100_ExpressionTests100 -270 Baselines130_AlterDatabaseStatementTests130 -271 Baselines160_CreateDatabaseTests160 -271 CreateDatabaseTests160 272 AlterTableDropTableElementStatementTests90 273 Baselines110_ServerRoleStatementTests 275 BaselinesCommon_TSqlParserTestScript2 @@ -79,7 +45,6 @@ 278 BaselinesFabricDW_CloneTableTestsFabricDW 278 CloneTableTestsFabricDW 280 WhitespaceTests -284 AlterDatabaseStatementTests130 286 Baselines110_SendStatementTests 286 Baselines130_SecurityStatement130Tests 286 SecurityStatement130Tests @@ -88,13 +53,10 @@ 290 OptimizerHintsTests90 291 Baselines90_DumpLoadStatementTests 292 Baselines90_CreateTypeStatementTests -293 CursorStatementsTests -297 Baselines90_CreateMessageTypeStatementTests 298 ExecuteStatementTests90 301 AlterDatabaseManualCutoverTests170 301 Baselines170_AlterDatabaseManualCutoverTests170 302 CreateHekatonTriggerStatementTest -303 Baselines90_ApplicationRoleStatementTests 304 Baselines90_DumpLoadStatement90Tests 306 CreateTypeStatementTests 308 EventNotificationStatementTests100 @@ -104,19 +66,15 @@ 311 Baselines130_CreateHekatonTriggerStatementTest 312 Baselines110_SendStatementTests110 313 Baselines160_CreateFunctionStatementTests160 -314 BaselinesCommon_CursorStatementsTests 315 ServerAuditStatementTests110 -316 AlterCertificateStatementTests 317 AlterSequenceStatementTests 317 Baselines130_CreateWorkloadClassifierStatementSqlDwTests 320 BaselinesCommon_AlterFunctionStatementTests 322 Baselines90_CreateFulltextCatalogStatementTests -322 BaselinesCommon_CreateStatisticsStatementTests 324 BaselinesCommon_DeleteStatementTests 324 BaselinesCommon_IdentifierTests 325 Baselines130_CreateColumnStoreIndexTests130 325 Baselines150_AlterIndexStatementTests150 -328 EnableDisableTriggerStatementTests 329 Baselines90_InsertStatementTests90 332 SetVariableStatementTests90 334 AlterExternalLibrary140 @@ -131,7 +89,6 @@ 341 Baselines100_TableParametersTests 344 Baselines110_ServerAuditStatementTests110 345 CreateSequenceStatementTests -350 Baselines90_EnableDisableTriggerStatementTests 352 InsertStatementTests90 352 SelectStatementTests140 353 BeginEndStatementErrorTests @@ -151,7 +108,6 @@ 386 Baselines100_CTEStatementTests100 386 CreateDatabaseTests140 387 AlterFunctionJsonObjectTests160 -391 CreateStatisticsStatementTests 393 ServerRoleStatementTests 395 FromClauseTests120 398 Baselines90_AlterAsymmetricKeyStatementTests @@ -172,7 +128,6 @@ 423 Baselines90_ReceiveStatementTests 423 MultipleErrorTests 423 SetOffsetsAndOnOffSetTests -426 ApplicationRoleStatementTests 426 OpenSymmetricKeyStatementTests 427 SymmetricKeyStatementTests100 429 WithinGroupTests160 @@ -180,7 +135,6 @@ 430 ReceiveStatementTests 438 Baselines120_CreateAggregateStatementTests120 438 CreateAggregateStatementTests120 -438 DeclareCursorStatementTests 438 DumpLoadStatementTests 439 Baselines110_CreateSequenceStatementTests 442 ExpressionTests100 @@ -196,7 +150,6 @@ 455 ReturnStatementTests 455 TSqlParserTestScript1 456 Baselines90_AlterEndpointStatementTests -459 BaselinesCommon_DeclareCursorStatementTests 461 BaselinesCommon_ReturnStatementTests 462 Baselines160_AlterTableResumableTests160 463 BaselinesCommon_DropStatementsTests @@ -216,7 +169,6 @@ 483 Baselines90_AlterAssemblyStatementTests 484 TableTypeTests120 485 Baselines110_AlterSearchPropertyListStatementTests -488 BaselinesCommon_UpdateStatisticsStatementTests 492 AlterSearchPropertyListStatementTests 492 Baselines140_RestoreStatementTests140_Azure 493 RestoreStatementTests140_Azure @@ -290,7 +242,6 @@ 593 Baselines80_ParserModeTests 594 CreateIndexStatementTests110 594 EndConversationStatementTests -595 UpdateStatisticsStatementTests 596 SelectWithCollation 598 DeleteStatementTests 599 DbccStatementsTests