Skip to content

Single DDL terminated by a semicolon fails through QueryContext, while multiple DDL succeeds #891

Description

@apstndb

Using database/sql, a single DDL statement with a terminating semicolon fails,
while the same form without the semicolon and a multi-statement DDL string both
succeed. The same reproducer also prints the underlying
StatementParser.Split results for those inputs.

Environment

  • github.com/googleapis/go-sql-spanner v1.26.0
  • Cloud Spanner Emulator v1.5.56
  • GoogleSQL dialect

The underlying StatementParser.Split behavior was also verified at the current
main commit caadf1f.

Reproducer

package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"

	"cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
	_ "github.com/googleapis/go-sql-spanner"
	"github.com/googleapis/go-sql-spanner/parser"
)

func main() {
	const (
		singleWithSemicolon    = "CREATE TABLE SingleWithSemicolon (Id INT64 NOT NULL) PRIMARY KEY (Id);"
		singleWithoutSemicolon = "CREATE TABLE SingleWithoutSemicolon (Id INT64 NOT NULL) " +
			"PRIMARY KEY (Id)"
		multiple = "CREATE TABLE MultiOne (Id INT64 NOT NULL) PRIMARY KEY (Id);" +
			"CREATE TABLE MultiTwo (Id INT64 NOT NULL) PRIMARY KEY (Id);"
	)

	p, err := parser.NewStatementParser(databasepb.DatabaseDialect_GOOGLE_STANDARD_SQL, 0)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("StatementParser.Split:")
	printSplit(p, "single with semicolon", singleWithSemicolon)
	printSplit(p, "single without semicolon", singleWithoutSemicolon)
	printSplit(p, "multiple", multiple)

	ctx := context.Background()
	db, err := sql.Open(
		"spanner",
		"projects/emulator-project/instances/test-instance/databases/test-database;autoConfigEmulator=true",
	)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	fmt.Println("\ndatabase/sql.QueryContext:")
	fmt.Printf("single with semicolon: %v\n", query(ctx, db, singleWithSemicolon))
	fmt.Printf("single without semicolon: %v\n", query(ctx, db, singleWithoutSemicolon))
	fmt.Printf("multiple: %v\n", query(ctx, db, multiple))
}

func printSplit(p *parser.StatementParser, label, statement string) {
	multiple, statements, err := p.Split(statement)
	fmt.Printf("%s: multiple=%v statements=%#v err=%v\n", label, multiple, statements, err)
}

func query(ctx context.Context, db *sql.DB, statement string) error {
	rows, err := db.QueryContext(ctx, statement)
	if err != nil {
		return err
	}
	defer rows.Close()

	for {
		for rows.Next() {
		}
		if err := rows.Err(); err != nil {
			return err
		}
		if !rows.NextResultSet() {
			return rows.Err()
		}
	}
}

Run against a fresh Emulator with:

$ SPANNER_EMULATOR_HOST=localhost:9010 go run .

Actual result

StatementParser.Split:
single with semicolon: multiple=false statements=[]string(nil) err=<nil>
single without semicolon: multiple=false statements=[]string(nil) err=<nil>
multiple: multiple=true statements=[]string{"CREATE TABLE MultiOne (Id INT64 NOT NULL) PRIMARY KEY (Id)", "CREATE TABLE MultiTwo (Id INT64 NOT NULL) PRIMARY KEY (Id)"} err=<nil>

database/sql.QueryContext:
single with semicolon: rpc error: code = InvalidArgument desc = Error parsing Spanner DDL statement: CREATE TABLE SingleWithSemicolon (Id INT64 NOT NULL) PRIMARY KEY (Id); : Syntax error on line 1, column 70: Expecting 'EOF' but found an unknown character (';').
single without semicolon: <nil>
multiple: <nil>

Expected result

The single DDL statement with a separator semicolon should succeed in the same
way as the separator-free single statement and the statements in the
multi-statement string.

Cause

conn.QueryContext calls StatementParser.Split. For exactly one statement,
including one terminated by a semicolon, Split returns false, nil, nil by
design. The driver therefore takes its single-statement path with the original
SQL, leaving the separator semicolon in the DDL sent to UpdateDatabaseDdl.

For multiple statements, Split returns each statement without its separator,
so the same DDL form succeeds.

Relation to #461

Issue #461 described the need for an exported simple statement parser,
including statement splitting for tools such as wrench. This behavior also
affects direct consumers using Split for those use cases: unlike multiple
statements, a separator-free statement is not returned for a single statement
unless they compensate for it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority: p2Moderately-important priority. Fix may not be included in next release.type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions