Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,7 @@ Each test in `parser/testdata/` contains:
- `metadata.json` - `{"skip": true}` or `{"skip": false}`
- `query.sql` - T-SQL to parse
- `ast.json` - Expected AST output

## Important Rules

- **NEVER modify `ast.json` files** - These are golden files containing the expected output. If tests fail due to JSON mismatches, fix the Go code to match the expected output, not the other way around.
12 changes: 12 additions & 0 deletions ast/select_set_variable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ast

// SelectSetVariable represents a variable assignment in a SELECT statement.
// Example: SELECT @a = 1, @b ||= 'foo'
type SelectSetVariable struct {
Variable *VariableReference `json:"Variable,omitempty"`
Expression ScalarExpression `json:"Expression,omitempty"`
AssignmentKind string `json:"AssignmentKind,omitempty"`
}

func (*SelectSetVariable) node() {}
func (*SelectSetVariable) selectElement() {}
87 changes: 87 additions & 0 deletions ast/workload_classifier_statement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Package ast provides AST types for T-SQL parsing.
package ast

// CreateWorkloadClassifierStatement represents a CREATE WORKLOAD CLASSIFIER statement.
type CreateWorkloadClassifierStatement struct {
ClassifierName *Identifier
Options []WorkloadClassifierOption
}

func (s *CreateWorkloadClassifierStatement) statement() {}
func (s *CreateWorkloadClassifierStatement) node() {}

// WorkloadClassifierOption is the interface for workload classifier options.
type WorkloadClassifierOption interface {
node()
workloadClassifierOption()
}

// ClassifierWorkloadGroupOption represents the WORKLOAD_GROUP option.
type ClassifierWorkloadGroupOption struct {
WorkloadGroupName *StringLiteral
OptionType string
}

func (o *ClassifierWorkloadGroupOption) node() {}
func (o *ClassifierWorkloadGroupOption) workloadClassifierOption() {}

// ClassifierMemberNameOption represents the MEMBERNAME option.
type ClassifierMemberNameOption struct {
MemberName *StringLiteral
OptionType string
}

func (o *ClassifierMemberNameOption) node() {}
func (o *ClassifierMemberNameOption) workloadClassifierOption() {}

// ClassifierWlmContextOption represents the WLM_CONTEXT option.
type ClassifierWlmContextOption struct {
WlmContext *StringLiteral
OptionType string
}

func (o *ClassifierWlmContextOption) node() {}
func (o *ClassifierWlmContextOption) workloadClassifierOption() {}

// WlmTimeLiteral represents a time literal for WLM START_TIME/END_TIME options.
type WlmTimeLiteral struct {
TimeString *StringLiteral
}

func (t *WlmTimeLiteral) node() {}

// ClassifierStartTimeOption represents the START_TIME option.
type ClassifierStartTimeOption struct {
Time *WlmTimeLiteral
OptionType string
}

func (o *ClassifierStartTimeOption) node() {}
func (o *ClassifierStartTimeOption) workloadClassifierOption() {}

// ClassifierEndTimeOption represents the END_TIME option.
type ClassifierEndTimeOption struct {
Time *WlmTimeLiteral
OptionType string
}

func (o *ClassifierEndTimeOption) node() {}
func (o *ClassifierEndTimeOption) workloadClassifierOption() {}

// ClassifierWlmLabelOption represents the WLM_LABEL option.
type ClassifierWlmLabelOption struct {
WlmLabel *StringLiteral
OptionType string
}

func (o *ClassifierWlmLabelOption) node() {}
func (o *ClassifierWlmLabelOption) workloadClassifierOption() {}

// ClassifierImportanceOption represents the IMPORTANCE option.
type ClassifierImportanceOption struct {
Importance string
OptionType string
}

func (o *ClassifierImportanceOption) node() {}
func (o *ClassifierImportanceOption) workloadClassifierOption() {}
123 changes: 108 additions & 15 deletions parser/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,19 @@ const (
TokenRBrace
TokenLeftShift
TokenRightShift
TokenPipe // |
TokenDoublePipe // ||
TokenConcatEquals // ||=
TokenBitwiseAnd // &
TokenPlusEquals // +=
TokenMinusEquals // -=
TokenStarEquals // *=
TokenSlashEquals // /=
TokenModuloEquals // %=
TokenAndEquals // &=
TokenOrEquals // |=
TokenXorEquals // ^=
TokenCaret // ^

// DML Keywords
TokenInsert
Expand Down Expand Up @@ -265,9 +278,16 @@ func (l *Lexer) NextToken() Token {
tok.Type = TokenEOF
tok.Literal = ""
case '*':
tok.Type = TokenStar
tok.Literal = "*"
l.readChar()
if l.peekChar() == '=' {
l.readChar()
tok.Type = TokenStarEquals
tok.Literal = "*="
l.readChar()
} else {
tok.Type = TokenStar
tok.Literal = "*"
l.readChar()
}
case ',':
tok.Type = TokenComma
tok.Literal = ","
Expand Down Expand Up @@ -355,21 +375,94 @@ func (l *Lexer) NextToken() Token {
tok.Literal = "}"
l.readChar()
case '+':
tok.Type = TokenPlus
tok.Literal = "+"
l.readChar()
if l.peekChar() == '=' {
l.readChar()
tok.Type = TokenPlusEquals
tok.Literal = "+="
l.readChar()
} else {
tok.Type = TokenPlus
tok.Literal = "+"
l.readChar()
}
case '-':
tok.Type = TokenMinus
tok.Literal = "-"
l.readChar()
if l.peekChar() == '=' {
l.readChar()
tok.Type = TokenMinusEquals
tok.Literal = "-="
l.readChar()
} else {
tok.Type = TokenMinus
tok.Literal = "-"
l.readChar()
}
case '/':
tok.Type = TokenSlash
tok.Literal = "/"
l.readChar()
if l.peekChar() == '=' {
l.readChar()
tok.Type = TokenSlashEquals
tok.Literal = "/="
l.readChar()
} else {
tok.Type = TokenSlash
tok.Literal = "/"
l.readChar()
}
case '%':
tok.Type = TokenModulo
tok.Literal = "%"
l.readChar()
if l.peekChar() == '=' {
l.readChar()
tok.Type = TokenModuloEquals
tok.Literal = "%="
l.readChar()
} else {
tok.Type = TokenModulo
tok.Literal = "%"
l.readChar()
}
case '|':
if l.peekChar() == '|' {
l.readChar() // consume first |
if l.peekChar() == '=' {
l.readChar() // consume second |
tok.Type = TokenConcatEquals
tok.Literal = "||="
l.readChar() // consume =
} else {
tok.Type = TokenDoublePipe
tok.Literal = "||"
l.readChar() // consume second |
}
} else if l.peekChar() == '=' {
l.readChar()
tok.Type = TokenOrEquals
tok.Literal = "|="
l.readChar()
} else {
tok.Type = TokenPipe
tok.Literal = "|"
l.readChar()
}
case '&':
if l.peekChar() == '=' {
l.readChar()
tok.Type = TokenAndEquals
tok.Literal = "&="
l.readChar()
} else {
tok.Type = TokenBitwiseAnd
tok.Literal = "&"
l.readChar()
}
case '^':
if l.peekChar() == '=' {
l.readChar()
tok.Type = TokenXorEquals
tok.Literal = "^="
l.readChar()
} else {
tok.Type = TokenCaret
tok.Literal = "^"
l.readChar()
}
case '\'':
tok = l.readString()
default:
Expand Down
Loading
Loading