Skip to content

Commit

Permalink
feat(spanner/spansql): add support for SEQUENCE statements (#8481)
Browse files Browse the repository at this point in the history
* feat(spanner/spansql): add support for SEQUENCE statements

* feat(spanner/spansql): remove using generics for CI env

---------

Co-authored-by: Sri Harsha CH <57220027+harshachinta@users.noreply.github.com>
Co-authored-by: rahul2393 <irahul@google.com>
  • Loading branch information
3 people committed Aug 25, 2023
1 parent c90dd00 commit ccd0205
Show file tree
Hide file tree
Showing 5 changed files with 435 additions and 1 deletion.
169 changes: 169 additions & 0 deletions spanner/spansql/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,16 @@ func (p *parser) parseDDLStmt() (DDLStmt, *parseError) {
return nil, err
}
return &DropChangeStream{Name: name, Position: pos}, nil
case tok.caseEqual("SEQUENCE"):
var ifExists bool
if p.eat("IF", "EXISTS") {
ifExists = true
}
name, err := p.parseTableOrIndexOrColumnName()
if err != nil {
return nil, err
}
return &DropSequence{Name: name, IfExists: ifExists, Position: pos}, nil
}
} else if p.sniff("ALTER", "DATABASE") {
a, err := p.parseAlterDatabase()
Expand All @@ -1080,6 +1090,12 @@ func (p *parser) parseDDLStmt() (DDLStmt, *parseError) {
} else if p.sniff("ALTER", "INDEX") {
ai, err := p.parseAlterIndex()
return ai, err
} else if p.sniff("CREATE", "SEQUENCE") {
cs, err := p.parseCreateSequence()
return cs, err
} else if p.sniff("ALTER", "SEQUENCE") {
as, err := p.parseAlterSequence()
return as, err
}

return nil, p.errorf("unknown DDL statement")
Expand Down Expand Up @@ -2673,6 +2689,159 @@ func (p *parser) parseAlterIndex() (*AlterIndex, *parseError) {
return nil, p.errorf("got %q, expected ADD or DROP", tok.value)
}

func (p *parser) parseCreateSequence() (*CreateSequence, *parseError) {
debugf("parseCreateSequence: %v", p)

/*
CREATE SEQUENCE
[ IF NOT EXISTS ] sequence_name
[ OPTIONS ( sequence_options ) ]
*/

if err := p.expect("CREATE"); err != nil {
return nil, err
}
pos := p.Pos()
if err := p.expect("SEQUENCE"); err != nil {
return nil, err
}
var ifNotExists bool
if p.eat("IF", "NOT", "EXISTS") {
ifNotExists = true
}
sname, err := p.parseTableOrIndexOrColumnName()
if err != nil {
return nil, err
}

cs := &CreateSequence{Name: sname, IfNotExists: ifNotExists, Position: pos}

if p.sniff("OPTIONS") {
cs.Options, err = p.parseSequenceOptions()
if err != nil {
return nil, err
}
}

return cs, nil
}

func (p *parser) parseAlterSequence() (*AlterSequence, *parseError) {
debugf("parseAlterSequence: %v", p)

/*
ALTER SEQUENCE sequence_name
SET OPTIONS sequence_options
*/

if err := p.expect("ALTER"); err != nil {
return nil, err
}
pos := p.Pos()
if err := p.expect("SEQUENCE"); err != nil {
return nil, err
}
sname, err := p.parseTableOrIndexOrColumnName()
if err != nil {
return nil, err
}

as := &AlterSequence{Name: sname, Position: pos}

tok := p.next()
if tok.err != nil {
return nil, tok.err
}
switch {
default:
return nil, p.errorf("got %q, expected SET", tok.value)
case tok.caseEqual("SET"):
options, err := p.parseSequenceOptions()
if err != nil {
return nil, err
}
as.Alteration = SetSequenceOptions{Options: options}
return as, nil
}
}

func (p *parser) parseSequenceOptions() (SequenceOptions, *parseError) {
debugf("parseSequenceOptions: %v", p)

if err := p.expect("OPTIONS", "("); err != nil {
return SequenceOptions{}, err
}

// We ignore case for the key (because it is easier) but not the value.
var so SequenceOptions
for {
if p.eat("sequence_kind", "=") {
tok := p.next()
if tok.err != nil {
return SequenceOptions{}, tok.err
}
if tok.typ != stringToken {
return SequenceOptions{}, p.errorf("invalid sequence_kind value: %v", tok.value)
}
sequenceKind := tok.string
so.SequenceKind = &sequenceKind
} else if p.eat("skip_range_min", "=") {
tok := p.next()
if tok.err != nil {
return SequenceOptions{}, tok.err
}
if tok.typ != int64Token {
return SequenceOptions{}, p.errorf("invalid skip_range_min value: %v", tok.value)
}
value, err := strconv.Atoi(tok.value)
if err != nil {
return SequenceOptions{}, p.errorf("invalid skip_range_min value: %v", tok.value)
}
so.SkipRangeMin = &value
} else if p.eat("skip_range_max", "=") {
tok := p.next()
if tok.err != nil {
return SequenceOptions{}, tok.err
}
if tok.typ != int64Token {
return SequenceOptions{}, p.errorf("invalid skip_range_max value: %v", tok.value)
}
value, err := strconv.Atoi(tok.value)
if err != nil {
return SequenceOptions{}, p.errorf("invalid skip_range_max value: %v", tok.value)
}
so.SkipRangeMax = &value
} else if p.eat("start_with_counter", "=") {
tok := p.next()
if tok.err != nil {
return SequenceOptions{}, tok.err
}
if tok.typ != int64Token {
return SequenceOptions{}, p.errorf("invalid start_with_counter value: %v", tok.value)
}
value, err := strconv.Atoi(tok.value)
if err != nil {
return SequenceOptions{}, p.errorf("invalid start_with_counter value: %v", tok.value)
}
so.StartWithCounter = &value
} else {
tok := p.next()
return SequenceOptions{}, p.errorf("unknown sequence option: %v", tok.value)
}
if p.sniff(")") {
break
}
if !p.eat(",") {
return SequenceOptions{}, p.errorf("missing ',' in options list")
}
}
if err := p.expect(")"); err != nil {
return SequenceOptions{}, err
}

return so, nil
}

var baseTypes = map[string]TypeBase{
"BOOL": Bool,
"INT64": Int64,
Expand Down
71 changes: 70 additions & 1 deletion spanner/spansql/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,20 @@ func TestParseDDL(t *testing.T) {
ALTER INDEX MyFirstIndex ADD STORED COLUMN UpdatedAt;
ALTER INDEX MyFirstIndex DROP STORED COLUMN UpdatedAt;
CREATE SEQUENCE MySequence OPTIONS (
sequence_kind='bit_reversed_positive',
skip_range_min = 1,
skip_range_max = 1000,
start_with_counter = 50
);
ALTER SEQUENCE MySequence SET OPTIONS (
sequence_kind='bit_reversed_positive',
skip_range_min = 1,
skip_range_max = 1000,
start_with_counter = 50
);
DROP SEQUENCE MySequence;
-- Trailing comment at end of file.
`, &DDL{Filename: "filename", List: []DDLStmt{
&CreateTable{
Expand Down Expand Up @@ -1119,6 +1133,29 @@ func TestParseDDL(t *testing.T) {
Alteration: DropStoredColumn{Name: "UpdatedAt"},
Position: line(106),
},
&CreateSequence{
Name: "MySequence",
Options: SequenceOptions{
SequenceKind: stringAddr("bit_reversed_positive"),
SkipRangeMin: intAddr(1),
SkipRangeMax: intAddr(1000),
StartWithCounter: intAddr(50),
},
Position: line(108),
},
&AlterSequence{
Name: "MySequence",
Alteration: SetSequenceOptions{
Options: SequenceOptions{
SequenceKind: stringAddr("bit_reversed_positive"),
SkipRangeMin: intAddr(1),
SkipRangeMax: intAddr(1000),
StartWithCounter: intAddr(50),
},
},
Position: line(114),
},
&DropSequence{Name: "MySequence", Position: line(120)},
}, Comments: []*Comment{
{
Marker: "#", Start: line(2), End: line(2),
Expand Down Expand Up @@ -1154,7 +1191,7 @@ func TestParseDDL(t *testing.T) {
{Marker: "--", Isolated: true, Start: line(75), End: line(75), Text: []string{"Table has a column with a default value."}},

// Comment after everything else.
{Marker: "--", Isolated: true, Start: line(108), End: line(108), Text: []string{"Trailing comment at end of file."}},
{Marker: "--", Isolated: true, Start: line(122), End: line(122), Text: []string{"Trailing comment at end of file."}},
}}},
// No trailing comma:
{`ALTER TABLE T ADD COLUMN C2 INT64`, &DDL{Filename: "filename", List: []DDLStmt{
Expand Down Expand Up @@ -1610,6 +1647,38 @@ func TestParseDDL(t *testing.T) {
},
},
},
{
`CREATE SEQUENCE IF NOT EXISTS sname OPTIONS (sequence_kind='bit_reversed_positive');
ALTER SEQUENCE sname SET OPTIONS (start_with_counter=1);
DROP SEQUENCE IF EXISTS sname;`,
&DDL{
Filename: "filename",
List: []DDLStmt{
&CreateSequence{
Name: "sname",
IfNotExists: true,
Options: SequenceOptions{
SequenceKind: stringAddr("bit_reversed_positive"),
},
Position: line(1),
},
&AlterSequence{
Name: "sname",
Alteration: SetSequenceOptions{
Options: SequenceOptions{
StartWithCounter: intAddr(1),
},
},
Position: line(2),
},
&DropSequence{
Name: "sname",
IfExists: true,
Position: line(3),
},
},
},
},
}
for _, test := range tests {
got, err := ParseDDL("filename", test.in)
Expand Down
55 changes: 55 additions & 0 deletions spanner/spansql/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,61 @@ func (dsc DropStoredColumn) SQL() string {
return "DROP STORED COLUMN " + dsc.Name.SQL()
}

func (cs CreateSequence) SQL() string {
str := "CREATE SEQUENCE "
if cs.IfNotExists {
str += "IF NOT EXISTS "
}
return str + cs.Name.SQL() + " " + cs.Options.SQL()
}

func (as AlterSequence) SQL() string {
return "ALTER SEQUENCE " + as.Name.SQL() + " " + as.Alteration.SQL()
}

func (sa SetSequenceOptions) SQL() string {
return "SET " + sa.Options.SQL()
}

func (so SequenceOptions) SQL() string {
str := "OPTIONS ("
hasOpt := false
if so.SequenceKind != nil {
hasOpt = true
str += fmt.Sprintf("sequence_kind='%s'", *so.SequenceKind)
}
if so.SkipRangeMin != nil {
if hasOpt {
str += ", "
}
hasOpt = true
str += fmt.Sprintf("skip_range_min=%v", *so.SkipRangeMin)
}
if so.SkipRangeMax != nil {
if hasOpt {
str += ", "
}
hasOpt = true
str += fmt.Sprintf("skip_range_max=%v", *so.SkipRangeMax)
}
if so.StartWithCounter != nil {
if hasOpt {
str += ", "
}
hasOpt = true
str += fmt.Sprintf("start_with_counter=%v", *so.StartWithCounter)
}
return str + ")"
}

func (do DropSequence) SQL() string {
str := "DROP SEQUENCE "
if do.IfExists {
str += "IF EXISTS "
}
return str + do.Name.SQL()
}

func (d *Delete) SQL() string {
return "DELETE FROM " + d.Table.SQL() + " WHERE " + d.Where.SQL()
}
Expand Down
Loading

0 comments on commit ccd0205

Please sign in to comment.