Skip to content

Commit 87dd9a1

Browse files
kyleconroyclaude
andcommitted
Add comprehensive FROM clause parsing support
- Add TABLESAMPLE clause parsing (SYSTEM, PERCENT, ROWS options, REPEATABLE) - Add built-in function table reference (::fn_name syntax) - Add variable method call table reference (@var.method() syntax) - Add join hint support (REMOTE, LOOP, HASH, MERGE, etc.) - Add JoinParenthesisTableReference for parenthesized joins - Add PIVOT/UNPIVOT parsing inside join loop - Fix READCOMMITTEDLOCK and other table hint mappings - Fix leading dot handling in column references (e.g., .st.StandardCost) - Fix derived table column list parsing (AS t(c1, c2)) - Rename PivotValue to ValueColumn in UnpivotedTableReference Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ef00a6a commit 87dd9a1

11 files changed

Lines changed: 445 additions & 38 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package ast
2+
3+
// BuiltInFunctionTableReference represents a built-in function used as a table source
4+
// Syntax: ::function_name(parameters)
5+
type BuiltInFunctionTableReference struct {
6+
Name *Identifier `json:"Name,omitempty"`
7+
Parameters []ScalarExpression `json:"Parameters,omitempty"`
8+
Alias *Identifier `json:"Alias,omitempty"`
9+
Columns []*Identifier `json:"Columns,omitempty"`
10+
ForPath bool `json:"ForPath"`
11+
}
12+
13+
func (*BuiltInFunctionTableReference) node() {}
14+
func (*BuiltInFunctionTableReference) tableReference() {}

ast/merge_statement.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ func (a *InsertMergeAction) mergeAction() {}
6464

6565
// JoinParenthesisTableReference represents a parenthesized join table reference
6666
type JoinParenthesisTableReference struct {
67-
Join TableReference // The join inside the parenthesis
67+
Join TableReference `json:"Join,omitempty"` // The join inside the parenthesis
68+
ForPath bool `json:"ForPath"`
6869
}
6970

7071
func (j *JoinParenthesisTableReference) node() {}

ast/named_table_reference.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ package ast
22

33
// NamedTableReference represents a named table reference.
44
type NamedTableReference struct {
5-
SchemaObject *SchemaObjectName `json:"SchemaObject,omitempty"`
6-
Alias *Identifier `json:"Alias,omitempty"`
7-
TableHints []TableHintType `json:"TableHints,omitempty"`
8-
ForPath bool `json:"ForPath,omitempty"`
5+
SchemaObject *SchemaObjectName `json:"SchemaObject,omitempty"`
6+
TableSampleClause *TableSampleClause `json:"TableSampleClause,omitempty"`
7+
Alias *Identifier `json:"Alias,omitempty"`
8+
TableHints []TableHintType `json:"TableHints,omitempty"`
9+
ForPath bool `json:"ForPath,omitempty"`
910
}
1011

1112
func (*NamedTableReference) node() {}

ast/pivoted_table_reference.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type UnpivotedTableReference struct {
1919
TableReference TableReference
2020
InColumns []*ColumnReferenceExpression
2121
PivotColumn *Identifier
22-
PivotValue *Identifier
22+
ValueColumn *Identifier
2323
NullHandling string // "None", "ExcludeNulls", "IncludeNulls"
2424
Alias *Identifier
2525
ForPath bool

ast/query_derived_table.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package ast
33
// QueryDerivedTable represents a derived table (parenthesized query) used as a table reference.
44
type QueryDerivedTable struct {
55
QueryExpression QueryExpression `json:"QueryExpression,omitempty"`
6+
Columns []*Identifier `json:"Columns,omitempty"`
67
Alias *Identifier `json:"Alias,omitempty"`
78
ForPath bool `json:"ForPath,omitempty"`
89
}

ast/table_sample_clause.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package ast
2+
3+
// TableSampleClause represents a TABLESAMPLE clause in a table reference
4+
type TableSampleClause struct {
5+
System bool `json:"System"`
6+
SampleNumber ScalarExpression `json:"SampleNumber,omitempty"`
7+
TableSampleClauseOption string `json:"TableSampleClauseOption"` // "NotSpecified", "Percent", "Rows"
8+
RepeatSeed ScalarExpression `json:"RepeatSeed,omitempty"`
9+
}
10+
11+
func (*TableSampleClause) node() {}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package ast
2+
3+
// VariableMethodCallTableReference represents a method call on a table variable
4+
// Syntax: @variable.method(parameters) [AS alias[(columns)]]
5+
type VariableMethodCallTableReference struct {
6+
Variable *VariableReference `json:"Variable,omitempty"`
7+
MethodName *Identifier `json:"MethodName,omitempty"`
8+
Parameters []ScalarExpression `json:"Parameters,omitempty"`
9+
Columns []*Identifier `json:"Columns,omitempty"`
10+
Alias *Identifier `json:"Alias,omitempty"`
11+
ForPath bool `json:"ForPath"`
12+
}
13+
14+
func (*VariableMethodCallTableReference) node() {}
15+
func (*VariableMethodCallTableReference) tableReference() {}

parser/marshal.go

Lines changed: 90 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2573,6 +2573,9 @@ func tableReferenceToJSON(ref ast.TableReference) jsonNode {
25732573
if r.SchemaObject != nil {
25742574
node["SchemaObject"] = schemaObjectNameToJSON(r.SchemaObject)
25752575
}
2576+
if r.TableSampleClause != nil {
2577+
node["TableSampleClause"] = tableSampleClauseToJSON(r.TableSampleClause)
2578+
}
25762579
if len(r.TableHints) > 0 {
25772580
hints := make([]jsonNode, len(r.TableHints))
25782581
for i, h := range r.TableHints {
@@ -2621,6 +2624,14 @@ func tableReferenceToJSON(ref ast.TableReference) jsonNode {
26212624
node["SecondTableReference"] = tableReferenceToJSON(r.SecondTableReference)
26222625
}
26232626
return node
2627+
case *ast.JoinParenthesisTableReference:
2628+
node := jsonNode{
2629+
"$type": "JoinParenthesisTableReference",
2630+
}
2631+
if r.Join != nil {
2632+
node["Join"] = tableReferenceToJSON(r.Join)
2633+
}
2634+
return node
26242635
case *ast.VariableTableReference:
26252636
node := jsonNode{
26262637
"$type": "VariableTableReference",
@@ -2630,6 +2641,35 @@ func tableReferenceToJSON(ref ast.TableReference) jsonNode {
26302641
}
26312642
node["ForPath"] = r.ForPath
26322643
return node
2644+
case *ast.VariableMethodCallTableReference:
2645+
node := jsonNode{
2646+
"$type": "VariableMethodCallTableReference",
2647+
}
2648+
if r.Variable != nil {
2649+
node["Variable"] = scalarExpressionToJSON(r.Variable)
2650+
}
2651+
if r.MethodName != nil {
2652+
node["MethodName"] = identifierToJSON(r.MethodName)
2653+
}
2654+
if len(r.Parameters) > 0 {
2655+
params := make([]jsonNode, len(r.Parameters))
2656+
for i, p := range r.Parameters {
2657+
params[i] = scalarExpressionToJSON(p)
2658+
}
2659+
node["Parameters"] = params
2660+
}
2661+
if len(r.Columns) > 0 {
2662+
cols := make([]jsonNode, len(r.Columns))
2663+
for i, c := range r.Columns {
2664+
cols[i] = identifierToJSON(c)
2665+
}
2666+
node["Columns"] = cols
2667+
}
2668+
if r.Alias != nil {
2669+
node["Alias"] = identifierToJSON(r.Alias)
2670+
}
2671+
node["ForPath"] = r.ForPath
2672+
return node
26332673
case *ast.SchemaObjectFunctionTableReference:
26342674
node := jsonNode{
26352675
"$type": "SchemaObjectFunctionTableReference",
@@ -2682,6 +2722,32 @@ func tableReferenceToJSON(ref ast.TableReference) jsonNode {
26822722
}
26832723
node["ForPath"] = r.ForPath
26842724
return node
2725+
case *ast.BuiltInFunctionTableReference:
2726+
node := jsonNode{
2727+
"$type": "BuiltInFunctionTableReference",
2728+
}
2729+
if r.Name != nil {
2730+
node["Name"] = identifierToJSON(r.Name)
2731+
}
2732+
if len(r.Parameters) > 0 {
2733+
params := make([]jsonNode, len(r.Parameters))
2734+
for i, p := range r.Parameters {
2735+
params[i] = scalarExpressionToJSON(p)
2736+
}
2737+
node["Parameters"] = params
2738+
}
2739+
if r.Alias != nil {
2740+
node["Alias"] = identifierToJSON(r.Alias)
2741+
}
2742+
if len(r.Columns) > 0 {
2743+
cols := make([]jsonNode, len(r.Columns))
2744+
for i, c := range r.Columns {
2745+
cols[i] = identifierToJSON(c)
2746+
}
2747+
node["Columns"] = cols
2748+
}
2749+
node["ForPath"] = r.ForPath
2750+
return node
26852751
case *ast.InlineDerivedTable:
26862752
node := jsonNode{
26872753
"$type": "InlineDerivedTable",
@@ -2917,14 +2983,6 @@ func tableReferenceToJSON(ref ast.TableReference) jsonNode {
29172983
}
29182984
node["ForPath"] = r.ForPath
29192985
return node
2920-
case *ast.JoinParenthesisTableReference:
2921-
node := jsonNode{
2922-
"$type": "JoinParenthesisTableReference",
2923-
}
2924-
if r.Join != nil {
2925-
node["Join"] = tableReferenceToJSON(r.Join)
2926-
}
2927-
return node
29282986
case *ast.PivotedTableReference:
29292987
node := jsonNode{
29302988
"$type": "PivotedTableReference",
@@ -2974,8 +3032,8 @@ func tableReferenceToJSON(ref ast.TableReference) jsonNode {
29743032
if r.PivotColumn != nil {
29753033
node["PivotColumn"] = identifierToJSON(r.PivotColumn)
29763034
}
2977-
if r.PivotValue != nil {
2978-
node["PivotValue"] = identifierToJSON(r.PivotValue)
3035+
if r.ValueColumn != nil {
3036+
node["ValueColumn"] = identifierToJSON(r.ValueColumn)
29793037
}
29803038
if r.NullHandling != "" && r.NullHandling != "None" {
29813039
node["NullHandling"] = r.NullHandling
@@ -2992,6 +3050,13 @@ func tableReferenceToJSON(ref ast.TableReference) jsonNode {
29923050
if r.QueryExpression != nil {
29933051
node["QueryExpression"] = queryExpressionToJSON(r.QueryExpression)
29943052
}
3053+
if len(r.Columns) > 0 {
3054+
cols := make([]jsonNode, len(r.Columns))
3055+
for i, c := range r.Columns {
3056+
cols[i] = identifierToJSON(c)
3057+
}
3058+
node["Columns"] = cols
3059+
}
29953060
if r.Alias != nil {
29963061
node["Alias"] = identifierToJSON(r.Alias)
29973062
}
@@ -3561,6 +3626,21 @@ func windowDelimiterToJSON(wd *ast.WindowDelimiter) jsonNode {
35613626

35623627
// ======================= New Statement JSON Functions =======================
35633628

3629+
func tableSampleClauseToJSON(tsc *ast.TableSampleClause) jsonNode {
3630+
node := jsonNode{
3631+
"$type": "TableSampleClause",
3632+
"System": tsc.System,
3633+
}
3634+
if tsc.SampleNumber != nil {
3635+
node["SampleNumber"] = scalarExpressionToJSON(tsc.SampleNumber)
3636+
}
3637+
node["TableSampleClauseOption"] = tsc.TableSampleClauseOption
3638+
if tsc.RepeatSeed != nil {
3639+
node["RepeatSeed"] = scalarExpressionToJSON(tsc.RepeatSeed)
3640+
}
3641+
return node
3642+
}
3643+
35643644
func tableHintToJSON(h ast.TableHintType) jsonNode {
35653645
switch th := h.(type) {
35663646
case *ast.TableHint:

parser/parse_dml.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1035,7 +1035,7 @@ func (p *Parser) parseTableHints() ([]ast.TableHintType, error) {
10351035
// isTableHintKeyword checks if a string is a valid table hint keyword
10361036
func isTableHintKeyword(name string) bool {
10371037
switch name {
1038-
case "HOLDLOCK", "NOLOCK", "PAGLOCK", "READCOMMITTED", "READPAST",
1038+
case "HOLDLOCK", "NOLOCK", "PAGLOCK", "READCOMMITTED", "READCOMMITTEDLOCK", "READPAST",
10391039
"READUNCOMMITTED", "REPEATABLEREAD", "ROWLOCK", "SERIALIZABLE",
10401040
"SNAPSHOT", "TABLOCK", "TABLOCKX", "UPDLOCK", "XLOCK", "NOWAIT",
10411041
"INDEX", "FORCESEEK", "FORCESCAN", "KEEPIDENTITY", "KEEPDEFAULTS",

0 commit comments

Comments
 (0)