Skip to content

Commit 118a450

Browse files
committed
Add support of accessing fields with the dot operation
1 parent 693137d commit 118a450

11 files changed

+189
-21
lines changed

parser/ast.go

+34
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,40 @@ func (p *BinaryOperation) Accept(visitor ASTVisitor) error {
187187
return visitor.VisitBinaryExpr(p)
188188
}
189189

190+
type IndexOperation struct {
191+
LeftExpr Expr
192+
Operation TokenKind
193+
Index Expr
194+
}
195+
196+
func (i *IndexOperation) Accept(visitor ASTVisitor) error {
197+
visitor.enter(i)
198+
defer visitor.leave(i)
199+
if err := i.LeftExpr.Accept(visitor); err != nil {
200+
return err
201+
}
202+
if err := i.Index.Accept(visitor); err != nil {
203+
return err
204+
}
205+
return visitor.VisitIndexOperation(i)
206+
}
207+
208+
func (i *IndexOperation) Pos() Pos {
209+
return i.LeftExpr.Pos()
210+
}
211+
212+
func (i *IndexOperation) End() Pos {
213+
return i.Index.End()
214+
}
215+
216+
func (i *IndexOperation) String() string {
217+
var builder strings.Builder
218+
builder.WriteString(i.LeftExpr.String())
219+
builder.WriteString(string(i.Operation))
220+
builder.WriteString(i.Index.String())
221+
return builder.String()
222+
}
223+
190224
type JoinTableExpr struct {
191225
Table *TableExpr
192226
StatementEnd Pos

parser/ast_visitor.go

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ type ASTVisitor interface {
44
VisitOperationExpr(expr *OperationExpr) error
55
VisitTernaryExpr(expr *TernaryOperation) error
66
VisitBinaryExpr(expr *BinaryOperation) error
7+
VisitIndexOperation(expr *IndexOperation) error
78
VisitAlterTable(expr *AlterTable) error
89
VisitAlterTableAttachPartition(expr *AlterTableAttachPartition) error
910
VisitAlterTableDetachPartition(expr *AlterTableDetachPartition) error
@@ -188,6 +189,13 @@ func (v *DefaultASTVisitor) VisitBinaryExpr(expr *BinaryOperation) error {
188189
return nil
189190
}
190191

192+
func (v *DefaultASTVisitor) VisitIndexOperation(expr *IndexOperation) error {
193+
if v.Visit != nil {
194+
return v.Visit(expr)
195+
}
196+
return nil
197+
}
198+
191199
func (v *DefaultASTVisitor) VisitJoinTableExpr(expr *JoinTableExpr) error {
192200
if v.Visit != nil {
193201
return v.Visit(expr)

parser/lexer.go

+8-12
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const (
1515
TokenInt TokenKind = "<int>"
1616
TokenFloat TokenKind = "<float>"
1717
TokenString TokenKind = "<string>"
18+
TokenDot = "."
1819
)
1920

2021
const (
@@ -328,19 +329,14 @@ func (l *Lexer) consumeToken() error {
328329
return nil
329330
}
330331
case '.':
331-
// check if the next token is a number. If so, parse it as a float number
332-
if l.peekOk(1) && IsDigit(l.peekN(1)) {
333-
return l.consumeNumber()
334-
}
335-
// check if the previous lastToken is an Ident. If so, it's a field name.
336-
if l.lastToken != nil && l.lastToken.Kind != TokenIdent {
337-
return fmt.Errorf("'.' should be after an Ident, but got <%q>", l.lastToken.Kind)
332+
l.lastToken = &Token{
333+
String: l.slice(0, 1),
334+
Kind: TokenDot,
335+
Pos: Pos(l.current),
336+
End: Pos(l.current + 1),
338337
}
339-
}
340-
341-
// The subsequent lastToken after the dot should be an Ident.
342-
if l.lastToken != nil && l.lastToken.Kind == "." && !IsIdentStart(l.peekN(0)) {
343-
return fmt.Errorf("'.' should follow with an Ident, but got <%q>", l.lastToken.Kind)
338+
l.skipN(1)
339+
return nil
344340
}
345341

346342
if IsIdentStart(l.peekN(0)) {

parser/lexer_test.go

-2
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ func TestConsumeNumber(t *testing.T) {
106106
t.Run("Float number", func(t *testing.T) {
107107
floats := []string{
108108
"123.456",
109-
".456",
110109
"123.456e+10",
111110
"123.456e-10",
112111
"123.456e10",
@@ -126,7 +125,6 @@ func TestConsumeNumber(t *testing.T) {
126125

127126
t.Run("Invalid float number", func(t *testing.T) {
128127
invalidFloats := []string{
129-
".456a",
130128
"123.456b",
131129
"123.456e",
132130
"123.456e+",

parser/parse_system.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,7 @@ func (p *Parser) parseGrantSource(_ Pos) (*TableIdentifier, error) {
921921
return nil, err
922922
}
923923

924-
if p.tryConsumeTokenKind(".") == nil {
924+
if p.tryConsumeTokenKind(TokenDot) == nil {
925925
return &TableIdentifier{
926926
Table: ident,
927927
}, nil

parser/parser_column.go

+23-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const (
2020
PrecedenceMulDivMod
2121
PrecedenceBracket
2222
PrecedenceArrow
23+
PrecedenceDot
2324
PrecedenceDoubleColon
2425
)
2526

@@ -56,6 +57,8 @@ func (p *Parser) getNextPrecedence() int {
5657
return PrecedenceBracket
5758
case p.matchTokenKind(opTypeCast):
5859
return PrecedenceDoubleColon
60+
case p.matchTokenKind(TokenDot):
61+
return PrecedenceDot
5962
case p.matchKeyword(KeywordBetween), p.matchKeyword(KeywordLike), p.matchKeyword(KeywordIlike):
6063
return PrecedenceBetweenLike
6164
case p.matchKeyword(KeywordIn):
@@ -106,7 +109,24 @@ func (p *Parser) parseInfix(expr Expr, precedence int) (Expr, error) {
106109
Operation: "GLOBAL IN",
107110
RightExpr: rightExpr,
108111
}, nil
109-
112+
case p.matchTokenKind(TokenDot):
113+
_ = p.lexer.consumeToken()
114+
// access column with dot notation
115+
var rightExpr Expr
116+
var err error
117+
if p.matchTokenKind(TokenIdent) {
118+
rightExpr, err = p.parseIdent()
119+
} else {
120+
rightExpr, err = p.parseDecimal(p.Pos())
121+
}
122+
if err != nil {
123+
return nil, err
124+
}
125+
return &IndexOperation{
126+
LeftExpr: expr,
127+
Operation: TokenDot,
128+
Index: rightExpr,
129+
}, nil
110130
case p.matchKeyword(KeywordNot):
111131
_ = p.lexer.consumeToken()
112132
switch {
@@ -331,6 +351,8 @@ func (p *Parser) parseColumnExpr(pos Pos) (Expr, error) { //nolint:funlen
331351
return p.parseQueryParam(p.Pos())
332352
}
333353
return p.parseMapLiteral(p.Pos())
354+
case p.matchTokenKind(TokenDot):
355+
return p.parseNumber(p.Pos())
334356
case p.matchTokenKind(opTypeQuery):
335357
// Placeholder `?`
336358
_ = p.lexer.consumeToken()

parser/parser_common.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func (p *Parser) parseIdentOrStar() (*Ident, error) {
110110
}
111111

112112
func (p *Parser) tryParseDotIdent(_ Pos) (*Ident, error) {
113-
if p.tryConsumeTokenKind(".") == nil {
113+
if p.tryConsumeTokenKind(TokenDot) == nil {
114114
return nil, nil // nolint
115115
}
116116
return p.parseIdent()
@@ -211,6 +211,17 @@ func (p *Parser) parseNumber(pos Pos) (*NumberLiteral, error) {
211211
lastToken, err = p.consumeTokenKind(TokenInt)
212212
case p.matchTokenKind(TokenFloat):
213213
lastToken, err = p.consumeTokenKind(TokenFloat)
214+
case p.matchTokenKind(TokenDot):
215+
p.lexer.consumeToken()
216+
lastToken, err = p.consumeTokenKind(TokenInt)
217+
if err != nil {
218+
return nil, err
219+
}
220+
if lastToken.Base != 10 {
221+
return nil, fmt.Errorf("invalid decimal literal: %q", lastToken.String)
222+
}
223+
lastToken.String = "." + lastToken.String
224+
lastToken.Kind = TokenFloat
214225
default:
215226
return nil, fmt.Errorf("expected <int> or <float>, but got %q", p.lastTokenKind())
216227
}

parser/parser_table.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -223,14 +223,14 @@ func (p *Parser) parseIdentOrFunction(_ Pos) (Expr, error) {
223223
}, nil
224224
}
225225
return funcExpr, nil
226-
case p.tryConsumeTokenKind(".") != nil:
226+
case p.tryConsumeTokenKind(TokenDot) != nil:
227227
switch {
228228
case p.matchTokenKind(TokenIdent):
229229
nextIdent, err := p.parseIdent()
230230
if err != nil {
231231
return nil, err
232232
}
233-
if p.tryConsumeTokenKind(".") != nil {
233+
if p.tryConsumeTokenKind(TokenDot) != nil {
234234
thirdIdent, err := p.parseIdent()
235235
if err != nil {
236236
return nil, err
@@ -310,7 +310,7 @@ func (p *Parser) parseTableSchemaClause(pos Pos) (*TableSchemaClause, error) {
310310
return nil, err
311311
}
312312
switch {
313-
case p.matchTokenKind("."):
313+
case p.matchTokenKind(TokenDot):
314314
// it's a database.table
315315
dotIdent, err := p.tryParseDotIdent(p.Pos())
316316
if err != nil {
@@ -489,7 +489,7 @@ func (p *Parser) parseTableArgExpr(pos Pos) (Expr, error) {
489489
}
490490
switch {
491491
// nest identifier
492-
case p.matchTokenKind("."):
492+
case p.matchTokenKind(TokenDot):
493493
dotIdent, err := p.tryParseDotIdent(p.Pos())
494494
if err != nil {
495495
return nil, err
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SELECT tuple('a','b','c').3, .1234
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- Origin SQL:
2+
SELECT tuple('a','b','c').3, .1234
3+
4+
-- Format SQL:
5+
SELECT tuple('a', 'b', 'c').3, .1234;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
[
2+
{
3+
"SelectPos": 0,
4+
"StatementEnd": 34,
5+
"With": null,
6+
"Top": null,
7+
"SelectItems": [
8+
{
9+
"Expr": {
10+
"LeftExpr": {
11+
"Name": {
12+
"Name": "tuple",
13+
"QuoteType": 1,
14+
"NamePos": 7,
15+
"NameEnd": 12
16+
},
17+
"Params": {
18+
"LeftParenPos": 12,
19+
"RightParenPos": 24,
20+
"Items": {
21+
"ListPos": 14,
22+
"ListEnd": 23,
23+
"HasDistinct": false,
24+
"Items": [
25+
{
26+
"Expr": {
27+
"LiteralPos": 14,
28+
"LiteralEnd": 15,
29+
"Literal": "a"
30+
},
31+
"Alias": null
32+
},
33+
{
34+
"Expr": {
35+
"LiteralPos": 18,
36+
"LiteralEnd": 19,
37+
"Literal": "b"
38+
},
39+
"Alias": null
40+
},
41+
{
42+
"Expr": {
43+
"LiteralPos": 22,
44+
"LiteralEnd": 23,
45+
"Literal": "c"
46+
},
47+
"Alias": null
48+
}
49+
]
50+
},
51+
"ColumnArgList": null
52+
}
53+
},
54+
"Operation": ".",
55+
"Index": {
56+
"NumPos": 26,
57+
"NumEnd": 27,
58+
"Literal": "3",
59+
"Base": 10
60+
}
61+
},
62+
"Modifiers": [],
63+
"Alias": null
64+
},
65+
{
66+
"Expr": {
67+
"NumPos": 29,
68+
"NumEnd": 34,
69+
"Literal": ".1234",
70+
"Base": 10
71+
},
72+
"Modifiers": [],
73+
"Alias": null
74+
}
75+
],
76+
"From": null,
77+
"ArrayJoin": null,
78+
"Window": null,
79+
"Prewhere": null,
80+
"Where": null,
81+
"GroupBy": null,
82+
"WithTotal": false,
83+
"Having": null,
84+
"OrderBy": null,
85+
"LimitBy": null,
86+
"Limit": null,
87+
"Settings": null,
88+
"Format": null,
89+
"UnionAll": null,
90+
"UnionDistinct": null,
91+
"Except": null
92+
}
93+
]

0 commit comments

Comments
 (0)