Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(spanner/spansql): add ROW DELETION POLICY parsing #4496

Merged
merged 5 commits into from Jul 27, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
78 changes: 71 additions & 7 deletions spanner/spansql/parser.go
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
Expand Down
52 changes: 51 additions & 1 deletion spanner/spansql/parser_test.go
Expand Up @@ -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{
Expand Down Expand Up @@ -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."}},
Expand All @@ -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{
Expand Down
18 changes: 18 additions & 0 deletions spanner/spansql/sql.go
Expand Up @@ -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
}

Expand Down Expand Up @@ -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 {
nktks marked this conversation as resolved.
Show resolved Hide resolved
return "DROP ROW DELETION POLICY"
}
func (sct SetColumnType) SQL() string {
str := sct.Type.SQL()
if sct.NotNull {
Expand Down Expand Up @@ -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() + " ("
Expand Down
58 changes: 58 additions & 0 deletions spanner/spansql/sql_test.go
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
38 changes: 26 additions & 12 deletions spanner/spansql/types.go
Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 }
Expand All @@ -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 {
Expand Down