Skip to content

Commit 5d9f46e

Browse files
committed
Merge branch 'create-drop-database' into spanner-lib
2 parents bd4e2b4 + d0a5fc3 commit 5d9f46e

File tree

3 files changed

+65
-2
lines changed

3 files changed

+65
-2
lines changed

conn_with_mockserver_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ func TestCreateDatabase(t *testing.T) {
357357
}
358358
defer silentClose(conn)
359359

360-
if _, err = conn.ExecContext(ctx, "create database foo"); err != nil {
360+
if _, err = conn.ExecContext(ctx, "create database `foo`"); err != nil {
361361
t.Fatalf("failed to execute CREATE DATABASE: %v", err)
362362
}
363363

statement_parser.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,18 +205,36 @@ func (i *identifier) String() string {
205205
// eatIdentifier reads the identifier at the current parser position, updates the parser position,
206206
// and returns the identifier.
207207
func (p *simpleParser) eatIdentifier() (identifier, error) {
208-
// TODO: Add support for quoted identifiers.
209208
p.skipWhitespacesAndComments()
210209
if p.pos >= len(p.sql) {
211210
return identifier{}, status.Errorf(codes.InvalidArgument, "no identifier found at position %d", p.pos)
212211
}
212+
213213
startPos := p.pos
214214
first := true
215215
result := identifier{parts: make([]string, 0, 1)}
216216
appendLastPart := true
217217
for p.pos < len(p.sql) {
218218
if first {
219219
first = false
220+
// Check if this is a quoted identifier.
221+
if p.sql[p.pos] == p.statementParser.identifierQuoteToken() {
222+
pos, quoteLen, err := p.statementParser.skipQuoted(p.sql, p.pos, p.sql[p.pos])
223+
if err != nil {
224+
return identifier{}, err
225+
}
226+
p.pos = pos
227+
result.parts = append(result.parts, string(p.sql[startPos+quoteLen:pos-quoteLen]))
228+
if p.eatToken('.') {
229+
p.skipWhitespacesAndComments()
230+
startPos = p.pos
231+
first = true
232+
continue
233+
} else {
234+
appendLastPart = false
235+
break
236+
}
237+
}
220238
if !p.isValidFirstIdentifierChar() {
221239
return identifier{}, status.Errorf(codes.InvalidArgument, "invalid first identifier character found at position %d: %s", p.pos, p.sql[p.pos:p.pos+1])
222240
}
@@ -450,6 +468,13 @@ func (p *statementParser) supportsNestedComments() bool {
450468
return p.dialect == databasepb.DatabaseDialect_POSTGRESQL
451469
}
452470

471+
func (p *statementParser) identifierQuoteToken() byte {
472+
if p.dialect == databasepb.DatabaseDialect_POSTGRESQL {
473+
return '"'
474+
}
475+
return '`'
476+
}
477+
453478
func (p *statementParser) supportsBacktickQuotes() bool {
454479
return p.dialect != databasepb.DatabaseDialect_POSTGRESQL
455480
}
@@ -657,6 +682,10 @@ func (p *statementParser) skipMultiLineComment(sql []byte, pos int) int {
657682
return pos
658683
}
659684

685+
// skipQuoted skips a quoted string at the given position in the sql string and
686+
// returns the new position, the quote length, or an error if the quoted string
687+
// could not be read.
688+
// The quote length is either 1 for normal quoted strings, and 3 for triple-quoted string.
660689
func (p *statementParser) skipQuoted(sql []byte, pos int, quote byte) (int, int, error) {
661690
isTripleQuoted := p.supportsTripleQuotedLiterals() && len(sql) > pos+2 && sql[pos+1] == quote && sql[pos+2] == quote
662691
if isTripleQuoted && (isMultibyte(sql[pos+1]) || isMultibyte(sql[pos+2])) {

statement_parser_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2494,15 +2494,28 @@ func TestEatIdentifier(t *testing.T) {
24942494
input: "my_property",
24952495
want: identifier{parts: []string{"my_property"}},
24962496
},
2497+
{
2498+
input: "`my_property`",
2499+
want: identifier{parts: []string{"my_property"}},
2500+
},
24972501
{
24982502
input: "my_extension.my_property",
24992503
want: identifier{parts: []string{"my_extension", "my_property"}},
25002504
},
2505+
{
2506+
input: "`my_extension`.`my_property`",
2507+
want: identifier{parts: []string{"my_extension", "my_property"}},
2508+
},
25012509
{
25022510
// spaces are allowed
25032511
input: " \n my_extension . \t my_property ",
25042512
want: identifier{parts: []string{"my_extension", "my_property"}},
25052513
},
2514+
{
2515+
// spaces are allowed
2516+
input: " \n `my_extension` . \t `my_property` ",
2517+
want: identifier{parts: []string{"my_extension", "my_property"}},
2518+
},
25062519
{
25072520
// comments are treated the same as spaces and are allowed
25082521
input: " /* comment */ \n my_extension -- yet another comment\n. \t -- Also a comment \nmy_property ",
@@ -2512,6 +2525,14 @@ func TestEatIdentifier(t *testing.T) {
25122525
input: "p1.p2.p3.p4",
25132526
want: identifier{parts: []string{"p1", "p2", "p3", "p4"}},
25142527
},
2528+
{
2529+
input: "`p1`.`p2`.`p3`.`p4`",
2530+
want: identifier{parts: []string{"p1", "p2", "p3", "p4"}},
2531+
},
2532+
{
2533+
input: "`p1`.p2.`p3`.p4",
2534+
want: identifier{parts: []string{"p1", "p2", "p3", "p4"}},
2535+
},
25152536
{
25162537
input: "a.b.c",
25172538
want: identifier{parts: []string{"a", "b", "c"}},
@@ -2520,6 +2541,11 @@ func TestEatIdentifier(t *testing.T) {
25202541
input: "1a",
25212542
wantErr: true,
25222543
},
2544+
{
2545+
// Double-quotes are not valid around identifiers in GoogleSQL.
2546+
input: `"1a""`,
2547+
wantErr: true,
2548+
},
25232549
{
25242550
input: "my_extension.",
25252551
wantErr: true,
@@ -2537,6 +2563,14 @@ func TestEatIdentifier(t *testing.T) {
25372563
input: "a . 1a",
25382564
wantErr: true,
25392565
},
2566+
{
2567+
input: "`p1 /* looks like a comment */ `.`p2`",
2568+
want: identifier{parts: []string{"p1 /* looks like a comment */ ", "p2"}},
2569+
},
2570+
{
2571+
input: "```p1 -- looks like a comment\n ```.`p2`",
2572+
want: identifier{parts: []string{"p1 -- looks like a comment\n ", "p2"}},
2573+
},
25402574
}
25412575
for _, test := range tests {
25422576
sp := &simpleParser{sql: []byte(test.input), statementParser: parser}

0 commit comments

Comments
 (0)