Skip to content

Commit b19adea

Browse files
committed
Fixing the parse error for @ symbol
1 parent b7c545b commit b19adea

3 files changed

Lines changed: 90 additions & 10 deletions

File tree

parse/queryLex.go

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,17 +114,57 @@ func (l *Lexer) skipUntil(val rune) {
114114
}
115115
}
116116

117-
func (l *Lexer) consumeIdent() {
118-
for !l.done() && !l.isEndIdent(l.next()) {
117+
func isNamedParamStart(r rune) bool {
118+
return r == '_' || unicode.IsLetter(r)
119+
}
120+
121+
func isNamedParamPart(r rune) bool {
122+
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
123+
}
124+
125+
func isNamedParamBoundary(r rune) bool {
126+
return !(r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) || r == '"')
127+
}
128+
129+
func (l *Lexer) currentRuneStart() int {
130+
if l.width == 0 {
131+
return l.pos
132+
}
133+
return l.pos - l.width
134+
}
135+
136+
func (l *Lexer) shouldLexNamedParam() bool {
137+
at := l.currentRuneStart()
138+
if at < 0 || at >= len(l.input) || l.input[at] != '@' {
139+
return false
119140
}
141+
142+
nextPos := at + len("@")
143+
if nextPos >= len(l.input) {
144+
return false
145+
}
146+
147+
next, _ := utf8.DecodeRuneInString(l.input[nextPos:])
148+
if !isNamedParamStart(next) {
149+
return false
150+
}
151+
152+
if at == 0 {
153+
return true
154+
}
155+
156+
prev, _ := utf8.DecodeLastRuneInString(l.input[:at])
157+
return isNamedParamBoundary(prev)
120158
}
121159

122-
func (l *Lexer) isEndIdent(r rune) bool {
123-
shouldEnd := unicode.IsSpace(r) || strings.ContainsRune(",)", r)
124-
if shouldEnd {
125-
l.backup()
160+
func (l *Lexer) consumeNamedParam() {
161+
for !l.done() {
162+
r := l.next()
163+
if !isNamedParamPart(r) {
164+
l.backup()
165+
return
166+
}
126167
}
127-
return shouldEnd
128168
}
129169

130170
func (l *Lexer) next() rune {
@@ -174,7 +214,7 @@ func lexQuery(l *Lexer) stateFunc {
174214
return lexString
175215
}
176216

177-
if r == '@' {
217+
if r == '@' && l.shouldLexNamedParam() {
178218
return lexNamedParam
179219
}
180220

@@ -220,8 +260,8 @@ func lexNamedParam(l *Lexer) stateFunc {
220260
l.writeChunk()
221261
l.next()
222262
l.start = l.pos
223-
// advance through the name
224-
l.consumeIdent()
263+
// advance through a valid named-parameter token
264+
l.consumeNamedParam()
225265
l.onNamed(strings.ToUpper(l.input[l.start:l.pos]))
226266
l.start = l.pos
227267
l.output.WriteRune('?')

parse/queryLex_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,36 @@ func TestLexNamed(t *testing.T) {
155155
a = ?
156156
`,
157157
},
158+
{
159+
name: "at sign inside invalid unquoted object name is not a named parameter",
160+
query: `CREATE VIEW vw_user@2024 AS SELECT user_id FROM "user"`,
161+
expectedNamed: []string{},
162+
expectedOutput: `CREATE VIEW vw_user@2024 AS SELECT user_id FROM "user"`,
163+
},
164+
{
165+
name: "at sign inside quoted identifier is not a named parameter",
166+
query: `CREATE TABLE "user@2024" (user_id INT)`,
167+
expectedNamed: []string{},
168+
expectedOutput: `CREATE TABLE "user@2024" (user_id INT)`,
169+
},
170+
{
171+
name: "at sign followed by digit is not a named parameter",
172+
query: `SELECT @2024`,
173+
expectedNamed: []string{},
174+
expectedOutput: `SELECT @2024`,
175+
},
176+
{
177+
name: "named param stops before arithmetic operator",
178+
query: `select * from table where a = @name+1`,
179+
expectedNamed: []string{"NAME"},
180+
expectedOutput: `select * from table where a = ?+1`,
181+
},
182+
{
183+
name: "named param stops before type cast",
184+
query: `select * from table where a = @name::int`,
185+
expectedNamed: []string{"NAME"},
186+
expectedOutput: `select * from table where a = ?::int`,
187+
},
158188
}
159189
var encounteredNames nameCapture
160190
for _, tc := range testCases {

stmt_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,16 @@ func TestNumInput(t *testing.T) {
257257
query: "select * from test where a = 'test?'",
258258
expected: 0,
259259
},
260+
{
261+
name: "at sign in object name is not input",
262+
query: `CREATE VIEW vw_user@2024 AS SELECT user_id FROM "user"`,
263+
expected: 0,
264+
},
265+
{
266+
name: "at sign in quoted identifier is not input",
267+
query: `CREATE TABLE "user@2024" (user_id INT)`,
268+
expected: 0,
269+
},
260270
}
261271
for _, tc := range testCases {
262272
t.Run(tc.name, func(t *testing.T) {

0 commit comments

Comments
 (0)