diff --git a/parser/ast.go b/parser/ast.go index da902fbc..17da6e9c 100644 --- a/parser/ast.go +++ b/parser/ast.go @@ -317,7 +317,7 @@ func (a *AlterTableDropPartition) Pos() Pos { func (a *AlterTableDropPartition) End() Pos { if a.Settings != nil { - a.Settings.End() + return a.Settings.End() } return a.Partition.End() } @@ -4103,6 +4103,7 @@ func (d *DictionarySchemaClause) Accept(visitor ASTVisitor) error { type DictionaryAttribute struct { NamePos Pos + AttrEnd Pos // end of the last property, or of the type when there is none Name *Ident Type ColumnType Default Literal @@ -4117,22 +4118,7 @@ func (d *DictionaryAttribute) Pos() Pos { } func (d *DictionaryAttribute) End() Pos { - if d.IsObjectId { - return d.NamePos + Pos(len("IS_OBJECT_ID")) - } - if d.Injective { - return d.NamePos + Pos(len("INJECTIVE")) - } - if d.Hierarchical { - return d.NamePos + Pos(len("HIERARCHICAL")) - } - if d.Expression != nil { - return d.Expression.End() - } - if d.Default != nil { - return d.Default.End() - } - return d.Type.End() + return d.AttrEnd } func (d *DictionaryAttribute) Accept(visitor ASTVisitor) error { @@ -4595,16 +4581,17 @@ func (f *FromClause) Accept(visitor ASTVisitor) error { } type IsNullExpr struct { - IsPos Pos - Expr Expr + IsPos Pos // position of the IS keyword + NullEnd Pos // end of the NULL keyword + Expr Expr } func (n *IsNullExpr) Pos() Pos { - return n.IsPos + return n.Expr.Pos() } func (n *IsNullExpr) End() Pos { - return n.Expr.End() + return n.NullEnd } func (n *IsNullExpr) Accept(visitor ASTVisitor) error { @@ -4617,8 +4604,9 @@ func (n *IsNullExpr) Accept(visitor ASTVisitor) error { } type IsNotNullExpr struct { - IsPos Pos - Expr Expr + IsPos Pos // position of the IS keyword + NullEnd Pos // end of the NULL keyword + Expr Expr } func (n *IsNotNullExpr) Pos() Pos { @@ -4626,7 +4614,7 @@ func (n *IsNotNullExpr) Pos() Pos { } func (n *IsNotNullExpr) End() Pos { - return n.Expr.End() + return n.NullEnd } func (n *IsNotNullExpr) Accept(visitor ASTVisitor) error { diff --git a/parser/parser_alter.go b/parser/parser_alter.go index e347c9cf..186166a0 100644 --- a/parser/parser_alter.go +++ b/parser/parser_alter.go @@ -35,8 +35,11 @@ func (p *Parser) parseAlterTable(pos Pos) (*AlterTable, error) { case p.matchKeyword(KeywordAttach): alter, err = p.parseAlterTableAttachPartition(p.Pos()) case p.matchKeyword(KeywordDetach): + // like the sibling branches, the clause position is the keyword + // itself, so capture it before consuming DETACH + detachPos := p.Pos() _ = p.lexer.consumeToken() - alter, err = p.parseAlterTableDetachPartition(p.Pos()) + alter, err = p.parseAlterTableDetachPartition(detachPos) case p.matchKeyword(KeywordFreeze): alter, err = p.parseAlterTableFreezePartition(p.Pos()) case p.matchKeyword(KeywordRemove): diff --git a/parser/parser_column.go b/parser/parser_column.go index bc61dbd1..69609a25 100644 --- a/parser/parser_column.go +++ b/parser/parser_column.go @@ -200,20 +200,26 @@ func (p *Parser) parseInfix(expr Expr, precedence int) (Expr, error) { case p.matchTokenKind(TokenKindQuestionMark): return p.parseTernaryExpr(expr) case p.matchKeyword(KeywordIs): + isPos := p.Pos() _ = p.lexer.consumeToken() isNotNull := p.tryConsumeKeywords(KeywordNot) + // the expression ends at the NULL keyword; capture its end before + // expectKeyword consumes it + nullEnd := p.End() if err := p.expectKeyword(KeywordNull); err != nil { return nil, err } if isNotNull { return &IsNotNullExpr{ - IsPos: p.Pos(), - Expr: expr, + IsPos: isPos, + NullEnd: nullEnd, + Expr: expr, }, nil } return &IsNullExpr{ - IsPos: p.Pos(), - Expr: expr, + IsPos: isPos, + NullEnd: nullEnd, + Expr: expr, }, nil default: return nil, fmt.Errorf("unexpected token kind: %s", p.currentTokenKind()) diff --git a/parser/parser_common.go b/parser/parser_common.go index 18b927b5..05cb039d 100644 --- a/parser/parser_common.go +++ b/parser/parser_common.go @@ -430,7 +430,8 @@ func (p *Parser) parseRatioExpr(pos Pos) (*RatioExpr, error) { var denominator *NumberLiteral if p.tryConsumeTokenKind(TokenKindDiv) != nil { - denominator, err = p.parseNumber(pos) + // the denominator starts at its own token, not at the numerator + denominator, err = p.parseNumber(p.Pos()) if err != nil { return nil, err } diff --git a/parser/parser_query.go b/parser/parser_query.go index f4d55ac8..31c0a01a 100644 --- a/parser/parser_query.go +++ b/parser/parser_query.go @@ -503,6 +503,7 @@ func (p *Parser) parseGroupByClause(pos Pos) (*GroupByClause, error) { var expr Expr var err error + var groupByEnd Pos aggregateType := "" switch { case p.matchKeyword(KeywordCube) || p.matchKeyword(KeywordRollup): @@ -512,7 +513,10 @@ func (p *Parser) parseGroupByClause(pos Pos) (*GroupByClause, error) { case p.tryConsumeKeywords(KeywordGrouping, KeywordSets): aggregateType = "GROUPING SETS" expr, err = p.parseFunctionParams(p.Pos()) - case p.tryConsumeKeywords(KeywordAll): + case p.matchKeyword(KeywordAll): + // GROUP BY ALL has no expression list; the clause ends at ALL itself + groupByEnd = p.End() + _ = p.lexer.consumeToken() aggregateType = "ALL" default: expr, err = p.parseColumnExprListWithLParen(p.Pos()) @@ -520,14 +524,21 @@ func (p *Parser) parseGroupByClause(pos Pos) (*GroupByClause, error) { if err != nil { return nil, err } + if expr != nil { + groupByEnd = expr.End() + } groupBy := &GroupByClause{ GroupByPos: pos, + GroupByEnd: groupByEnd, AggregateType: aggregateType, Expr: expr, } // parse WITH CUBE, ROLLUP, TOTALS for p.tryConsumeKeywords(KeywordWith) { + // the clause now extends to the CUBE/ROLLUP/TOTALS token; capture its + // end before it is consumed + keywordEnd := p.End() switch { case p.tryConsumeKeywords(KeywordCube): groupBy.WithCube = true @@ -538,8 +549,8 @@ func (p *Parser) parseGroupByClause(pos Pos) (*GroupByClause, error) { default: return nil, fmt.Errorf("expected CUBE, ROLLUP or TOTALS, got %s", p.currentTokenKind()) } + groupBy.GroupByEnd = keywordEnd } - groupBy.GroupByEnd = p.Pos() return groupBy, nil } @@ -1075,13 +1086,15 @@ func (p *Parser) parseSelectStmt(pos Pos) (*SelectQuery, error) { // nolint: fun statementEnd = groupBy.End() } withTotal := false - lastPos := p.Pos() if p.tryConsumeKeywords(KeywordWith) { + // the statement now ends at the TOTALS token; capture its end before + // expectKeyword consumes it + totalsEnd := p.End() if err := p.expectKeyword(KeywordTotals); err != nil { return nil, err } withTotal = true - statementEnd = lastPos + statementEnd = totalsEnd } having, err := p.tryParseHavingClause(p.Pos()) if err != nil { diff --git a/parser/parser_table.go b/parser/parser_table.go index 0380b755..798de91f 100644 --- a/parser/parser_table.go +++ b/parser/parser_table.go @@ -2058,10 +2058,14 @@ func (p *Parser) parseDictionaryAttribute(pos Pos) (*DictionaryAttribute, error) NamePos: pos, Name: name, Type: columnType, + AttrEnd: columnType.End(), } // Parse optional attribute properties for { + // end of the property keyword about to be consumed; flag-only + // properties (HIERARCHICAL, ...) end at the keyword itself + keywordEnd := p.End() switch { case p.tryConsumeKeywords(KeywordDefault): if attr.Default != nil { @@ -2072,6 +2076,7 @@ func (p *Parser) parseDictionaryAttribute(pos Pos) (*DictionaryAttribute, error) return nil, err } attr.Default = literal + attr.AttrEnd = literal.End() case p.tryConsumeKeywords(KeywordExpression): if attr.Expression != nil { return nil, fmt.Errorf("duplicate EXPRESSION clause") @@ -2081,21 +2086,25 @@ func (p *Parser) parseDictionaryAttribute(pos Pos) (*DictionaryAttribute, error) return nil, err } attr.Expression = expr + attr.AttrEnd = expr.End() case p.tryConsumeKeywords(KeywordHierarchical): if attr.Hierarchical { return nil, fmt.Errorf("duplicate HIERARCHICAL clause") } attr.Hierarchical = true + attr.AttrEnd = keywordEnd case p.tryConsumeKeywords(KeywordInjective): if attr.Injective { return nil, fmt.Errorf("duplicate INJECTIVE clause") } attr.Injective = true + attr.AttrEnd = keywordEnd case p.tryConsumeKeywords(KeywordIs_object_id): if attr.IsObjectId { return nil, fmt.Errorf("duplicate IS_OBJECT_ID clause") } attr.IsObjectId = true + attr.AttrEnd = keywordEnd default: // No more attribute properties return attr, nil diff --git a/parser/position_test.go b/parser/position_test.go new file mode 100644 index 00000000..dab4d8ba --- /dev/null +++ b/parser/position_test.go @@ -0,0 +1,92 @@ +package parser + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func parseOneStmt(t *testing.T, sql string) Expr { + t.Helper() + stmts, err := NewParser(sql).ParseStmts() + require.NoError(t, err) + require.Len(t, stmts, 1) + return stmts[0] +} + +func TestRatioExprPositions(t *testing.T) { + sql := "SELECT * FROM t SAMPLE 1/2" + stmt := parseOneStmt(t, sql).(*SelectQuery) + sample := stmt.From.Expr.(*JoinTableExpr).SampleRatio + require.NotNil(t, sample) + ratio := sample.Ratio + require.NotNil(t, ratio.Denominator) + // numerator "1" at offset 23, denominator "2" at offset 25 — the spans + // must not overlap (the denominator used to inherit the numerator's pos) + require.Equal(t, Pos(23), ratio.Numerator.Pos()) + require.Equal(t, Pos(24), ratio.Numerator.End()) + require.Equal(t, Pos(25), ratio.Denominator.Pos()) + require.Equal(t, Pos(26), ratio.Denominator.End()) +} + +func TestGroupByClauseEnd(t *testing.T) { + sql := "SELECT a FROM t GROUP BY a WITH TOTALS" + stmt := parseOneStmt(t, sql).(*SelectQuery) + require.NotNil(t, stmt.GroupBy) + // the clause ends at the TOTALS keyword, not at the next token's start + require.Equal(t, Pos(len(sql)), stmt.GroupBy.End()) + + sql = "SELECT a FROM t GROUP BY a" + stmt = parseOneStmt(t, sql).(*SelectQuery) + require.Equal(t, Pos(len(sql)), stmt.GroupBy.End()) + + sql = "SELECT a FROM t GROUP BY ALL" + stmt = parseOneStmt(t, sql).(*SelectQuery) + require.Equal(t, Pos(len(sql)), stmt.GroupBy.End()) +} + +func TestIsNullExprPositions(t *testing.T) { + sql := "SELECT a IS NULL" + stmt := parseOneStmt(t, sql).(*SelectQuery) + isNull := stmt.SelectItems[0].Expr.(*IsNullExpr) + // the node spans `a IS NULL`: from the operand to the end of NULL + require.Equal(t, Pos(7), isNull.Pos()) + require.Equal(t, Pos(len(sql)), isNull.End()) + require.Equal(t, Pos(9), isNull.IsPos) // position of IS + + sql = "SELECT a IS NOT NULL" + stmt = parseOneStmt(t, sql).(*SelectQuery) + isNotNull := stmt.SelectItems[0].Expr.(*IsNotNullExpr) + require.Equal(t, Pos(7), isNotNull.Pos()) + require.Equal(t, Pos(len(sql)), isNotNull.End()) + require.Equal(t, Pos(9), isNotNull.IsPos) +} + +func TestAlterDetachPartitionPos(t *testing.T) { + sql := "ALTER TABLE t DETACH PARTITION p" + stmt := parseOneStmt(t, sql).(*AlterTable) + detach := stmt.AlterExprs[0].(*AlterTableDetachPartition) + // the clause starts at the DETACH keyword, like every sibling clause + require.Equal(t, Pos(14), detach.Pos()) +} + +func TestAlterDropPartitionEndIncludesSettings(t *testing.T) { + sql := "ALTER TABLE t DROP PARTITION p SETTINGS mutations_sync=1" + stmt := parseOneStmt(t, sql).(*AlterTable) + drop := stmt.AlterExprs[0].(*AlterTableDropPartition) + require.NotNil(t, drop.Settings) + // End() used to discard the Settings end and stop at the partition + require.Equal(t, drop.Settings.End(), drop.End()) + require.Greater(t, drop.End(), drop.Partition.End()) +} + +func TestDictionaryAttributeEnd(t *testing.T) { + sql := "CREATE DICTIONARY d (user_id UInt64 IS_OBJECT_ID) PRIMARY KEY user_id SOURCE(CLICKHOUSE()) LAYOUT(FLAT()) LIFETIME(300)" + stmt := parseOneStmt(t, sql).(*CreateDictionary) + attrs := stmt.Schema.Attributes + require.Len(t, attrs, 1) + // the attribute ends at IS_OBJECT_ID; the old implementation returned + // NamePos + len("IS_OBJECT_ID"), landing in the middle of the type + require.Equal(t, "IS_OBJECT_ID", sql[36:48]) + require.Equal(t, Pos(48), attrs[0].End()) +} diff --git a/parser/testdata/ddl/output/alter_table_add_projection.sql.golden.json b/parser/testdata/ddl/output/alter_table_add_projection.sql.golden.json index fbe03cf5..915b6ab8 100644 --- a/parser/testdata/ddl/output/alter_table_add_projection.sql.golden.json +++ b/parser/testdata/ddl/output/alter_table_add_projection.sql.golden.json @@ -51,7 +51,7 @@ }, "GroupBy": { "GroupByPos": 86, - "GroupByEnd": 105, + "GroupByEnd": 104, "AggregateType": "", "Expr": { "ListPos": 95, diff --git a/parser/testdata/ddl/output/alter_table_detach_partition.sql.golden.json b/parser/testdata/ddl/output/alter_table_detach_partition.sql.golden.json index e43197aa..3b4309ec 100644 --- a/parser/testdata/ddl/output/alter_table_detach_partition.sql.golden.json +++ b/parser/testdata/ddl/output/alter_table_detach_partition.sql.golden.json @@ -19,7 +19,7 @@ "OnCluster": null, "AlterExprs": [ { - "DetachPos": 27, + "DetachPos": 20, "Partition": { "PartitionPos": 27, "Expr": { diff --git a/parser/testdata/ddl/output/alter_table_drop_detach_partition.sql.golden.json b/parser/testdata/ddl/output/alter_table_drop_detach_partition.sql.golden.json index 037df24f..4876b2c7 100644 --- a/parser/testdata/ddl/output/alter_table_drop_detach_partition.sql.golden.json +++ b/parser/testdata/ddl/output/alter_table_drop_detach_partition.sql.golden.json @@ -1,7 +1,7 @@ [ { "AlterPos": 0, - "StatementEnd": 120, + "StatementEnd": 154, "TableIdentifier": { "Database": { "Name": "app_utc_00", diff --git a/parser/testdata/ddl/output/create_dictionary_basic.sql.golden.json b/parser/testdata/ddl/output/create_dictionary_basic.sql.golden.json index 408c55f0..92ceff41 100644 --- a/parser/testdata/ddl/output/create_dictionary_basic.sql.golden.json +++ b/parser/testdata/ddl/output/create_dictionary_basic.sql.golden.json @@ -25,6 +25,7 @@ "Attributes": [ { "NamePos": 37, + "AttrEnd": 46, "Name": { "Name": "id", "QuoteType": 1, @@ -47,6 +48,7 @@ }, { "NamePos": 52, + "AttrEnd": 73, "Name": { "Name": "name", "QuoteType": 1, @@ -73,6 +75,7 @@ }, { "NamePos": 80, + "AttrEnd": 125, "Name": { "Name": "value", "QuoteType": 1, @@ -123,6 +126,7 @@ }, { "NamePos": 132, + "AttrEnd": 161, "Name": { "Name": "parent_id", "QuoteType": 1, @@ -145,6 +149,7 @@ }, { "NamePos": 167, + "AttrEnd": 192, "Name": { "Name": "is_active", "QuoteType": 1, @@ -167,6 +172,7 @@ }, { "NamePos": 198, + "AttrEnd": 227, "Name": { "Name": "object_id", "QuoteType": 1, diff --git a/parser/testdata/ddl/output/create_dictionary_comprehensive.sql.golden.json b/parser/testdata/ddl/output/create_dictionary_comprehensive.sql.golden.json index 66f4d335..5ea4e3a9 100644 --- a/parser/testdata/ddl/output/create_dictionary_comprehensive.sql.golden.json +++ b/parser/testdata/ddl/output/create_dictionary_comprehensive.sql.golden.json @@ -39,6 +39,7 @@ "Attributes": [ { "NamePos": 134, + "AttrEnd": 143, "Name": { "Name": "id", "QuoteType": 1, @@ -61,6 +62,7 @@ }, { "NamePos": 149, + "AttrEnd": 170, "Name": { "Name": "name", "QuoteType": 1, @@ -87,6 +89,7 @@ }, { "NamePos": 177, + "AttrEnd": 222, "Name": { "Name": "value", "QuoteType": 1, @@ -137,6 +140,7 @@ }, { "NamePos": 229, + "AttrEnd": 258, "Name": { "Name": "parent_id", "QuoteType": 1, @@ -159,6 +163,7 @@ }, { "NamePos": 264, + "AttrEnd": 289, "Name": { "Name": "is_active", "QuoteType": 1, @@ -181,6 +186,7 @@ }, { "NamePos": 295, + "AttrEnd": 324, "Name": { "Name": "object_id", "QuoteType": 1, diff --git a/parser/testdata/ddl/output/create_dictionary_with_comment.sql.golden.json b/parser/testdata/ddl/output/create_dictionary_with_comment.sql.golden.json index af9074de..a89ab6f3 100644 --- a/parser/testdata/ddl/output/create_dictionary_with_comment.sql.golden.json +++ b/parser/testdata/ddl/output/create_dictionary_with_comment.sql.golden.json @@ -25,6 +25,7 @@ "Attributes": [ { "NamePos": 37, + "AttrEnd": 46, "Name": { "Name": "id", "QuoteType": 1, @@ -47,6 +48,7 @@ }, { "NamePos": 52, + "AttrEnd": 73, "Name": { "Name": "name", "QuoteType": 1, @@ -73,6 +75,7 @@ }, { "NamePos": 80, + "AttrEnd": 125, "Name": { "Name": "value", "QuoteType": 1, @@ -123,6 +126,7 @@ }, { "NamePos": 132, + "AttrEnd": 161, "Name": { "Name": "parent_id", "QuoteType": 1, @@ -145,6 +149,7 @@ }, { "NamePos": 167, + "AttrEnd": 192, "Name": { "Name": "is_active", "QuoteType": 1, @@ -167,6 +172,7 @@ }, { "NamePos": 198, + "AttrEnd": 227, "Name": { "Name": "object_id", "QuoteType": 1, diff --git a/parser/testdata/ddl/output/create_table_with_projection_group_by_only.sql.golden.json b/parser/testdata/ddl/output/create_table_with_projection_group_by_only.sql.golden.json index 2273c6e7..fe488dd3 100644 --- a/parser/testdata/ddl/output/create_table_with_projection_group_by_only.sql.golden.json +++ b/parser/testdata/ddl/output/create_table_with_projection_group_by_only.sql.golden.json @@ -274,7 +274,7 @@ }, "GroupBy": { "GroupByPos": 324, - "GroupByEnd": 354, + "GroupByEnd": 349, "AggregateType": "", "Expr": { "ListPos": 333, diff --git a/parser/testdata/query/output/select_extract_with_regex.sql.golden.json b/parser/testdata/query/output/select_extract_with_regex.sql.golden.json index 018dab2b..99a88b7b 100644 --- a/parser/testdata/query/output/select_extract_with_regex.sql.golden.json +++ b/parser/testdata/query/output/select_extract_with_regex.sql.golden.json @@ -223,7 +223,7 @@ }, "GroupBy": { "GroupByPos": 198, - "GroupByEnd": 465, + "GroupByEnd": 464, "AggregateType": "", "Expr": { "ListPos": 209, diff --git a/parser/testdata/query/output/select_simple.sql.golden.json b/parser/testdata/query/output/select_simple.sql.golden.json index 514213db..4b0fa8bb 100644 --- a/parser/testdata/query/output/select_simple.sql.golden.json +++ b/parser/testdata/query/output/select_simple.sql.golden.json @@ -361,7 +361,7 @@ }, "GroupBy": { "GroupByPos": 239, - "GroupByEnd": 258, + "GroupByEnd": 256, "AggregateType": "", "Expr": { "ListPos": 248, diff --git a/parser/testdata/query/output/select_simple_with_group_by_with_cube_totals.sql.golden.json b/parser/testdata/query/output/select_simple_with_group_by_with_cube_totals.sql.golden.json index aaad6b83..383dd51c 100644 --- a/parser/testdata/query/output/select_simple_with_group_by_with_cube_totals.sql.golden.json +++ b/parser/testdata/query/output/select_simple_with_group_by_with_cube_totals.sql.golden.json @@ -79,7 +79,7 @@ "Where": null, "GroupBy": { "GroupByPos": 37, - "GroupByEnd": 76, + "GroupByEnd": 75, "AggregateType": "CUBE", "Expr": { "LeftParenPos": 50, diff --git a/parser/testdata/query/output/select_simple_with_is_not_null.sql.golden.json b/parser/testdata/query/output/select_simple_with_is_not_null.sql.golden.json index 5adf2063..f269c8b9 100644 --- a/parser/testdata/query/output/select_simple_with_is_not_null.sql.golden.json +++ b/parser/testdata/query/output/select_simple_with_is_not_null.sql.golden.json @@ -1,7 +1,7 @@ [ { "SelectPos": 0, - "StatementEnd": 133, + "StatementEnd": 145, "With": null, "Top": null, "HasDistinct": false, @@ -187,7 +187,8 @@ }, "Operation": "AND", "RightExpr": { - "IsPos": 127, + "IsPos": 117, + "NullEnd": 124, "Expr": { "Name": "f2", "QuoteType": 1, @@ -200,7 +201,8 @@ }, "Operation": "AND", "RightExpr": { - "IsPos": 145, + "IsPos": 134, + "NullEnd": 145, "Expr": { "Name": "f3", "QuoteType": 1, diff --git a/parser/testdata/query/output/select_simple_with_is_null.sql.golden.json b/parser/testdata/query/output/select_simple_with_is_null.sql.golden.json index 48028203..601deb64 100644 --- a/parser/testdata/query/output/select_simple_with_is_null.sql.golden.json +++ b/parser/testdata/query/output/select_simple_with_is_null.sql.golden.json @@ -1,7 +1,7 @@ [ { "SelectPos": 0, - "StatementEnd": 112, + "StatementEnd": 120, "With": null, "Top": null, "HasDistinct": false, @@ -186,7 +186,8 @@ }, "Operation": "AND", "RightExpr": { - "IsPos": 120, + "IsPos": 113, + "NullEnd": 120, "Expr": { "Name": "f2", "QuoteType": 1, diff --git a/parser/testdata/query/output/select_window_cte.sql.golden.json b/parser/testdata/query/output/select_window_cte.sql.golden.json index a0b579d9..127d5768 100644 --- a/parser/testdata/query/output/select_window_cte.sql.golden.json +++ b/parser/testdata/query/output/select_window_cte.sql.golden.json @@ -16,7 +16,7 @@ }, "Alias": { "SelectPos": 30, - "StatementEnd": 236, + "StatementEnd": 231, "With": null, "Top": null, "HasDistinct": false, @@ -156,7 +156,7 @@ }, "GroupBy": { "GroupByPos": 205, - "GroupByEnd": 236, + "GroupByEnd": 231, "AggregateType": "", "Expr": { "ListPos": 214, diff --git a/parser/testdata/query/output/select_with_group_by.sql.golden.json b/parser/testdata/query/output/select_with_group_by.sql.golden.json index 65f86ba4..2d1a5627 100644 --- a/parser/testdata/query/output/select_with_group_by.sql.golden.json +++ b/parser/testdata/query/output/select_with_group_by.sql.golden.json @@ -1,7 +1,7 @@ [ { "SelectPos": 0, - "StatementEnd": 171, + "StatementEnd": 170, "With": null, "Top": null, "HasDistinct": false, @@ -94,7 +94,7 @@ "Where": null, "GroupBy": { "GroupByPos": 78, - "GroupByEnd": 171, + "GroupByEnd": 170, "AggregateType": "GROUPING SETS", "Expr": { "LeftParenPos": 104, diff --git a/parser/testdata/query/output/select_with_keyword_in_group_by.sql.golden.json b/parser/testdata/query/output/select_with_keyword_in_group_by.sql.golden.json index d63d1fa9..c1c799b1 100644 --- a/parser/testdata/query/output/select_with_keyword_in_group_by.sql.golden.json +++ b/parser/testdata/query/output/select_with_keyword_in_group_by.sql.golden.json @@ -124,7 +124,7 @@ }, "GroupBy": { "GroupByPos": 112, - "GroupByEnd": 145, + "GroupByEnd": 143, "AggregateType": "", "Expr": { "ListPos": 121,