Skip to content
Permalink
Browse files
feat(spanner/spansql): add ROW DELETION POLICY parsing (#4496)
* chore(spanner/spansql): modify expect functions to allow ...string

* feat(spanner/spansql): add ROW DELETION POLICY parse.

This enables to parse `ROW DELETION POLICY` at `CREATE TABLE`, `ALTER
TABLE` clauses.

* fix(spanner/spansql): add empty line between functions

* fix(spanner/spansql): eat `ROW DELETION POLICY` first

Co-authored-by: Knut Olav Løite <koloite@gmail.com>
  • Loading branch information
nktks and olavloite committed Jul 27, 2021
1 parent 361235a commit 3d6c6c7873e1b75e8b492ede2e561411dc40536a
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 20 deletions.
@@ -933,13 +933,15 @@ func (p *parser) eat(want ...string) bool {
return true
}

func (p *parser) expect(want string) *parseError {
tok := p.next()
if tok.err != nil {
return tok.err
}
if !tok.caseEqual(want) {
return p.errorf("got %q while expecting %q", tok.value, want)
func (p *parser) expect(want ...string) *parseError {
for _, w := range want {
tok := p.next()
if tok.err != nil {
return tok.err
}
if !tok.caseEqual(w) {
return p.errorf("got %q while expecting %q", tok.value, w)
}
}
return nil
}
@@ -1082,6 +1084,13 @@ func (p *parser) parseCreateTable() (*CreateTable, *parseError) {
ct.Interleave.OnDelete = od
}
}
if p.eat(",", "ROW", "DELETION", "POLICY") {
rdp, err := p.parseRowDeletionPolicy()
if err != nil {
return nil, err
}
ct.RowDeletionPolicy = &rdp
}

return ct, nil
}
@@ -1236,6 +1245,15 @@ func (p *parser) parseAlterTable() (*AlterTable, *parseError) {
return a, nil
}

if p.eat("ROW", "DELETION", "POLICY") {
rdp, err := p.parseRowDeletionPolicy()
if err != nil {
return nil, err
}
a.Alteration = AddRowDeletionPolicy{RowDeletionPolicy: rdp}
return a, nil
}

// TODO: "COLUMN" is optional.
if err := p.expect("COLUMN"); err != nil {
return nil, err
@@ -1256,6 +1274,11 @@ func (p *parser) parseAlterTable() (*AlterTable, *parseError) {
return a, nil
}

if p.eat("ROW", "DELETION", "POLICY") {
a.Alteration = DropRowDeletionPolicy{}
return a, nil
}

// TODO: "COLUMN" is optional.
if err := p.expect("COLUMN"); err != nil {
return nil, err
@@ -1297,7 +1320,17 @@ func (p *parser) parseAlterTable() (*AlterTable, *parseError) {
Alteration: ca,
}
return a, nil
case tok.caseEqual("REPLACE"):
if p.eat("ROW", "DELETION", "POLICY") {
rdp, err := p.parseRowDeletionPolicy()
if err != nil {
return nil, err
}
a.Alteration = ReplaceRowDeletionPolicy{RowDeletionPolicy: rdp}
return a, nil
}
}
return a, nil
}

func (p *parser) parseAlterDatabase() (*AlterDatabase, *parseError) {
@@ -2963,6 +2996,37 @@ func (p *parser) parseOnDelete() (OnDelete, *parseError) {
return NoActionOnDelete, nil
}

func (p *parser) parseRowDeletionPolicy() (RowDeletionPolicy, *parseError) {
if err := p.expect("(", "OLDER_THAN", "("); err != nil {
return RowDeletionPolicy{}, err
}
cname, err := p.parseTableOrIndexOrColumnName()
if err != nil {
return RowDeletionPolicy{}, err
}
if err := p.expect(",", "INTERVAL"); err != nil {
return RowDeletionPolicy{}, err
}
tok := p.next()
if tok.err != nil {
return RowDeletionPolicy{}, tok.err
}
if tok.typ != int64Token {
return RowDeletionPolicy{}, p.errorf("got %q, expected int64 token", tok.value)
}
n, serr := strconv.ParseInt(tok.value, tok.int64Base, 64)
if serr != nil {
return RowDeletionPolicy{}, p.errorf("%v", serr)
}
if err := p.expect("DAY", ")", ")"); err != nil {
return RowDeletionPolicy{}, err
}
return RowDeletionPolicy{
Column: cname,
NumDays: n,
}, nil
}

// parseCommaList parses a comma-separated list enclosed by bra and ket,
// delegating to f for the individual element parsing.
// Only invoke this with symbols as bra/ket; they are matched literally, not case insensitively.
@@ -477,6 +477,17 @@ func TestParseDDL(t *testing.T) {
NameLen INT64 AS (char_length(Name)) STORED,
) PRIMARY KEY (Name);
-- Table with row deletion policy.
CREATE TABLE WithRowDeletionPolicy (
Name STRING(MAX) NOT NULL,
DelTimestamp TIMESTAMP NOT NULL,
) PRIMARY KEY (Name)
, ROW DELETION POLICY ( OLDER_THAN ( DelTimestamp, INTERVAL 30 DAY ));
ALTER TABLE WithRowDeletionPolicy DROP ROW DELETION POLICY;
ALTER TABLE WithRowDeletionPolicy ADD ROW DELETION POLICY ( OLDER_THAN ( DelTimestamp, INTERVAL 30 DAY ));
ALTER TABLE WithRowDeletionPolicy REPLACE ROW DELETION POLICY ( OLDER_THAN ( DelTimestamp, INTERVAL 30 DAY ));
-- Trailing comment at end of file.
`, &DDL{Filename: "filename", List: []DDLStmt{
&CreateTable{
@@ -628,6 +639,44 @@ func TestParseDDL(t *testing.T) {
PrimaryKey: []KeyPart{{Column: "Name"}},
Position: line(44),
},
&CreateTable{
Name: "WithRowDeletionPolicy",
Columns: []ColumnDef{
{Name: "Name", Type: Type{Base: String, Len: MaxLen}, NotNull: true, Position: line(51)},
{Name: "DelTimestamp", Type: Type{Base: Timestamp}, NotNull: true, Position: line(52)},
},
PrimaryKey: []KeyPart{{Column: "Name"}},
RowDeletionPolicy: &RowDeletionPolicy{
Column: ID("DelTimestamp"),
NumDays: 30,
},
Position: line(50),
},
&AlterTable{
Name: "WithRowDeletionPolicy",
Alteration: DropRowDeletionPolicy{},
Position: line(56),
},
&AlterTable{
Name: "WithRowDeletionPolicy",
Alteration: AddRowDeletionPolicy{
RowDeletionPolicy: RowDeletionPolicy{
Column: ID("DelTimestamp"),
NumDays: 30,
},
},
Position: line(57),
},
&AlterTable{
Name: "WithRowDeletionPolicy",
Alteration: ReplaceRowDeletionPolicy{
RowDeletionPolicy: RowDeletionPolicy{
Column: ID("DelTimestamp"),
NumDays: 30,
},
},
Position: line(58),
},
}, Comments: []*Comment{
{Marker: "#", Start: line(2), End: line(2),
Text: []string{"This is a comment."}},
@@ -647,9 +696,10 @@ func TestParseDDL(t *testing.T) {
{Marker: "--", Isolated: true, Start: line(38), End: line(38), Text: []string{"leading multi comment immediately after inline comment"}},

{Marker: "--", Isolated: true, Start: line(43), End: line(43), Text: []string{"Table with generated column."}},
{Marker: "--", Isolated: true, Start: line(49), End: line(49), Text: []string{"Table with row deletion policy."}},

// Comment after everything else.
{Marker: "--", Isolated: true, Start: line(49), End: line(49), Text: []string{"Trailing comment at end of file."}},
{Marker: "--", Isolated: true, Start: line(60), End: line(60), Text: []string{"Trailing comment at end of file."}},
}}},
// No trailing comma:
{`ALTER TABLE T ADD COLUMN C2 INT64`, &DDL{Filename: "filename", List: []DDLStmt{
@@ -56,6 +56,9 @@ func (ct CreateTable) SQL() string {
if il := ct.Interleave; il != nil {
str += ",\n INTERLEAVE IN PARENT " + il.Parent.SQL() + " ON DELETE " + il.OnDelete.SQL()
}
if rdp := ct.RowDeletionPolicy; rdp != nil {
str += ",\n " + rdp.SQL()
}
return str
}

@@ -130,6 +133,17 @@ func (ac AlterColumn) SQL() string {
return "ALTER COLUMN " + ac.Name.SQL() + " " + ac.Alteration.SQL()
}

func (ardp AddRowDeletionPolicy) SQL() string {
return "ADD " + ardp.RowDeletionPolicy.SQL()
}

func (rrdp ReplaceRowDeletionPolicy) SQL() string {
return "REPLACE " + rrdp.RowDeletionPolicy.SQL()
}

func (drdp DropRowDeletionPolicy) SQL() string {
return "DROP ROW DELETION POLICY"
}
func (sct SetColumnType) SQL() string {
str := sct.Type.SQL()
if sct.NotNull {
@@ -246,6 +260,10 @@ func (tc TableConstraint) SQL() string {
return str
}

func (rdp RowDeletionPolicy) SQL() string {
return "ROW DELETION POLICY ( OLDER_THAN ( " + rdp.Column.SQL() + ", INTERVAL " + strconv.FormatInt(rdp.NumDays, 10) + " DAY ))"
}

func (fk ForeignKey) SQL() string {
str := "FOREIGN KEY (" + idList(fk.Columns, ", ")
str += ") REFERENCES " + fk.RefTable.SQL() + " ("
@@ -135,6 +135,27 @@ func TestSQL(t *testing.T) {
INTERLEAVE IN PARENT Ta ON DELETE CASCADE`,
reparseDDL,
},
{
&CreateTable{
Name: "WithRowDeletionPolicy",
Columns: []ColumnDef{
{Name: "Name", Type: Type{Base: String, Len: MaxLen}, NotNull: true, Position: line(2)},
{Name: "DelTimestamp", Type: Type{Base: Timestamp}, NotNull: true, Position: line(3)},
},
PrimaryKey: []KeyPart{{Column: "Name"}},
RowDeletionPolicy: &RowDeletionPolicy{
Column: ID("DelTimestamp"),
NumDays: 30,
},
Position: line(1),
},
`CREATE TABLE WithRowDeletionPolicy (
Name STRING(MAX) NOT NULL,
DelTimestamp TIMESTAMP NOT NULL,
) PRIMARY KEY(Name),
ROW DELETION POLICY ( OLDER_THAN ( DelTimestamp, INTERVAL 30 DAY ))`,
reparseDDL,
},
{
&DropTable{
Name: "Ta",
@@ -230,6 +251,43 @@ func TestSQL(t *testing.T) {
"ALTER TABLE Ta ALTER COLUMN Ci SET OPTIONS (allow_commit_timestamp = null)",
reparseDDL,
},
{
&AlterTable{
Name: "WithRowDeletionPolicy",
Alteration: DropRowDeletionPolicy{},
Position: line(1),
},
"ALTER TABLE WithRowDeletionPolicy DROP ROW DELETION POLICY",
reparseDDL,
},
{
&AlterTable{
Name: "WithRowDeletionPolicy",
Alteration: AddRowDeletionPolicy{
RowDeletionPolicy: RowDeletionPolicy{
Column: ID("DelTimestamp"),
NumDays: 30,
},
},
Position: line(1),
},
"ALTER TABLE WithRowDeletionPolicy ADD ROW DELETION POLICY ( OLDER_THAN ( DelTimestamp, INTERVAL 30 DAY ))",
reparseDDL,
},
{
&AlterTable{
Name: "WithRowDeletionPolicy",
Alteration: ReplaceRowDeletionPolicy{
RowDeletionPolicy: RowDeletionPolicy{
Column: ID("DelTimestamp"),
NumDays: 30,
},
},
Position: line(1),
},
"ALTER TABLE WithRowDeletionPolicy REPLACE ROW DELETION POLICY ( OLDER_THAN ( DelTimestamp, INTERVAL 30 DAY ))",
reparseDDL,
},
{
&AlterDatabase{
Name: "dbname",
@@ -33,11 +33,12 @@ import (
// CreateTable represents a CREATE TABLE statement.
// https://cloud.google.com/spanner/docs/data-definition-language#create_table
type CreateTable struct {
Name ID
Columns []ColumnDef
Constraints []TableConstraint
PrimaryKey []KeyPart
Interleave *Interleave
Name ID
Columns []ColumnDef
Constraints []TableConstraint
PrimaryKey []KeyPart
Interleave *Interleave
RowDeletionPolicy *RowDeletionPolicy

Position Position // position of the "CREATE" token
}
@@ -90,6 +91,12 @@ type Interleave struct {
OnDelete OnDelete
}

// RowDeletionPolicy represents an row deletion policy clause of a CREATE, ALTER TABLE statement.
type RowDeletionPolicy struct {
Column ID
NumDays int64
}

// CreateIndex represents a CREATE INDEX statement.
// https://cloud.google.com/spanner/docs/data-definition-language#create-index
type CreateIndex struct {
@@ -162,18 +169,22 @@ func (at *AlterTable) clearOffset() {
}

// TableAlteration is satisfied by AddColumn, DropColumn, AddConstraint,
// DropConstraint, SetOnDelete and AlterColumn.
// DropConstraint, SetOnDelete and AlterColumn,
// AddRowDeletionPolicy, ReplaceRowDeletionPolicy, DropRowDeletionPolicy.
type TableAlteration interface {
isTableAlteration()
SQL() string
}

func (AddColumn) isTableAlteration() {}
func (DropColumn) isTableAlteration() {}
func (AddConstraint) isTableAlteration() {}
func (DropConstraint) isTableAlteration() {}
func (SetOnDelete) isTableAlteration() {}
func (AlterColumn) isTableAlteration() {}
func (AddColumn) isTableAlteration() {}
func (DropColumn) isTableAlteration() {}
func (AddConstraint) isTableAlteration() {}
func (DropConstraint) isTableAlteration() {}
func (SetOnDelete) isTableAlteration() {}
func (AlterColumn) isTableAlteration() {}
func (AddRowDeletionPolicy) isTableAlteration() {}
func (ReplaceRowDeletionPolicy) isTableAlteration() {}
func (DropRowDeletionPolicy) isTableAlteration() {}

type AddColumn struct{ Def ColumnDef }
type DropColumn struct{ Name ID }
@@ -184,6 +195,9 @@ type AlterColumn struct {
Name ID
Alteration ColumnAlteration
}
type AddRowDeletionPolicy struct{ RowDeletionPolicy RowDeletionPolicy }
type ReplaceRowDeletionPolicy struct{ RowDeletionPolicy RowDeletionPolicy }
type DropRowDeletionPolicy struct{}

// ColumnAlteration is satisfied by SetColumnType and SetColumnOptions.
type ColumnAlteration interface {

0 comments on commit 3d6c6c7

Please sign in to comment.