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

fix: Support Spanner ROW DELETION POLICY #79

Merged
merged 1 commit into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/ddl/spanner/ddl_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ func (o *Option) String() string {
}

func (o *Option) StringForDiff() string {
if o.Value == nil {
if o == nil || o.Value == nil {
return ""
}
return o.Name + " " + o.Value.StringForDiff()
Expand Down
31 changes: 31 additions & 0 deletions pkg/ddl/spanner/ddl_table_alter.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ func (s *AlterTableStmt) String() string {
} else {
str += " INITIALLY IMMEDIATE"
}
case *AddRowDeletionPolicy:
str += "ADD " + a.RowDeletionPolicy.String()
case *ReplaceRowDeletionPolicy:
str += "REPLACE " + a.RowDeletionPolicy.String()
case *DropRowDeletionPolicy:
str += "DROP ROW DELETION POLICY"
}

return str + ";\n"
Expand Down Expand Up @@ -217,3 +223,28 @@ type AlterConstraint struct {
func (*AlterConstraint) isAlterTableAction() {}

func (s *AlterConstraint) GoString() string { return internal.GoString(*s) }

// AddRowDeletionPolicy represents ALTER TABLE table_name ADD ROW DELETION POLICY.
type AddRowDeletionPolicy struct {
RowDeletionPolicy *Option
}

func (*AddRowDeletionPolicy) isAlterTableAction() {}

func (s *AddRowDeletionPolicy) GoString() string { return internal.GoString(*s) }

// ReplaceRowDeletionPolicy represents ALTER TABLE table_name REPLACE ROW DELETION POLICY.
type ReplaceRowDeletionPolicy struct {
RowDeletionPolicy *Option
}

func (*ReplaceRowDeletionPolicy) isAlterTableAction() {}

func (s *ReplaceRowDeletionPolicy) GoString() string { return internal.GoString(*s) }

// DropRowDeletionPolicy represents ALTER TABLE table_name DROP ROW DELETION POLICY.
type DropRowDeletionPolicy struct{}

func (*DropRowDeletionPolicy) isAlterTableAction() {}

func (s *DropRowDeletionPolicy) GoString() string { return internal.GoString(*s) }
76 changes: 76 additions & 0 deletions pkg/ddl/spanner/ddl_table_alter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ func Test_isAlterTableAction(t *testing.T) {
(&AddConstraint{}).isAlterTableAction()
(&DropConstraint{}).isAlterTableAction()
(&AlterConstraint{}).isAlterTableAction()
(&AddRowDeletionPolicy{}).isAlterTableAction()
(&ReplaceRowDeletionPolicy{}).isAlterTableAction()
(&DropRowDeletionPolicy{}).isAlterTableAction()
}

func TestAlterTableStmt_String(t *testing.T) {
Expand Down Expand Up @@ -266,6 +269,79 @@ func TestAlterTableStmt_String(t *testing.T) {
}
t.Logf("✅: %s: stmt: %#v", t.Name(), stmt)
})

t.Run("success,AddRowDeletionPolicy", func(t *testing.T) {
t.Parallel()

stmt := &AlterTableStmt{
Name: &ObjectName{Name: &Ident{Name: "groups", QuotationMark: `"`, Raw: `"groups"`}},
Action: &AddRowDeletionPolicy{RowDeletionPolicy: &Option{Name: "ROW DELETION POLICY", Value: &Expr{Idents: []*Ident{
NewRawIdent("("),
NewRawIdent("OLDER_THAN"),
NewRawIdent("("),
NewRawIdent("CreatedAt"),
NewRawIdent(","),
NewRawIdent("INTERVAL"),
NewRawIdent("30"),
NewRawIdent("DAY"),
NewRawIdent(")"),
NewRawIdent(")"),
}}}},
}

expected := `ALTER TABLE "groups" ADD ROW DELETION POLICY (OLDER_THAN(CreatedAt, INTERVAL 30 DAY));` + "\n"
actual := stmt.String()

if !assert.Equal(t, expected, actual) {
assert.Equal(t, fmt.Sprintf("%#v", expected), fmt.Sprintf("%#v", actual))
}
t.Logf("✅: %s: stmt: %#v", t.Name(), stmt)
})

t.Run("success,ReplaceRowDeletionPolicy", func(t *testing.T) {
t.Parallel()

stmt := &AlterTableStmt{
Name: &ObjectName{Name: &Ident{Name: "groups", QuotationMark: `"`, Raw: `"groups"`}},
Action: &ReplaceRowDeletionPolicy{RowDeletionPolicy: &Option{Name: "ROW DELETION POLICY", Value: &Expr{Idents: []*Ident{
NewRawIdent("("),
NewRawIdent("OLDER_THAN"),
NewRawIdent("("),
NewRawIdent("CreatedAt"),
NewRawIdent(","),
NewRawIdent("INTERVAL"),
NewRawIdent("30"),
NewRawIdent("DAY"),
NewRawIdent(")"),
NewRawIdent(")"),
}}}},
}

expected := `ALTER TABLE "groups" REPLACE ROW DELETION POLICY (OLDER_THAN(CreatedAt, INTERVAL 30 DAY));` + "\n"
actual := stmt.String()

if !assert.Equal(t, expected, actual) {
assert.Equal(t, fmt.Sprintf("%#v", expected), fmt.Sprintf("%#v", actual))
}
t.Logf("✅: %s: stmt: %#v", t.Name(), stmt)
})

t.Run("success,DropRowDeletionPolicy", func(t *testing.T) {
t.Parallel()

stmt := &AlterTableStmt{
Name: &ObjectName{Name: &Ident{Name: "groups", QuotationMark: `"`, Raw: `"groups"`}},
Action: &DropRowDeletionPolicy{},
}

expected := `ALTER TABLE "groups" DROP ROW DELETION POLICY;` + "\n"
actual := stmt.String()

if !assert.Equal(t, expected, actual) {
assert.Equal(t, fmt.Sprintf("%#v", expected), fmt.Sprintf("%#v", actual))
}
t.Logf("✅: %s: stmt: %#v", t.Name(), stmt)
})
}

func TestAlterTableStmt_GetNameForDiff(t *testing.T) {
Expand Down
18 changes: 11 additions & 7 deletions pkg/ddl/spanner/ddl_table_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import (
var _ Stmt = (*CreateTableStmt)(nil)

type CreateTableStmt struct {
Comment string
Indent string
IfNotExists bool
Name *ObjectName
Columns []*Column
Constraints Constraints
Options Options
Comment string
Indent string
IfNotExists bool
Name *ObjectName
Columns []*Column
Constraints Constraints
Options Options
RowDeletionPolicy *Option
}

func (s *CreateTableStmt) GetNameForDiff() string {
Expand Down Expand Up @@ -74,6 +75,9 @@ func (s *CreateTableStmt) String() string {
}
}
}
if s.RowDeletionPolicy != nil {
str += ",\n" + s.RowDeletionPolicy.String()
}

str += ";\n"
return str
Expand Down
30 changes: 30 additions & 0 deletions pkg/ddl/spanner/diff_create_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,36 @@ func DiffCreateTable(before, after *CreateTableStmt, opts ...DiffCreateTableOpti
})
}

if before.RowDeletionPolicy.StringForDiff() != after.RowDeletionPolicy.StringForDiff() {
switch {
case before.RowDeletionPolicy == nil:
// ALTER TABLE table_name ADD ROW DELETION POLICY
result.Stmts = append(result.Stmts, &AlterTableStmt{
Comment: simplediff.Diff("", after.RowDeletionPolicy.String()).String(),
Name: after.Name,
Action: &AddRowDeletionPolicy{
RowDeletionPolicy: after.RowDeletionPolicy,
},
})
case after.RowDeletionPolicy == nil:
// ALTER TABLE table_name DROP ROW DELETION POLICY
result.Stmts = append(result.Stmts, &AlterTableStmt{
Comment: simplediff.Diff(before.RowDeletionPolicy.String(), "").String(),
Name: after.Name,
Action: &DropRowDeletionPolicy{},
})
default:
// ALTER TABLE table_name REPLACE ROW DELETION POLICY
result.Stmts = append(result.Stmts, &AlterTableStmt{
Comment: simplediff.Diff(before.RowDeletionPolicy.String(), after.RowDeletionPolicy.String()).String(),
Name: after.Name,
Action: &ReplaceRowDeletionPolicy{
RowDeletionPolicy: after.RowDeletionPolicy,
},
})
}
}

if len(result.Stmts) == 0 {
return nil, apperr.Errorf("before: %s, after: %s: %w", before.GetNameForDiff(), after.GetNameForDiff(), ddl.ErrNoDifference)
}
Expand Down
75 changes: 75 additions & 0 deletions pkg/ddl/spanner/diff_create_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,81 @@ CREATE TABLE "users" (
assert.Equal(t, expected, actual.String())
})

t.Run("success,ALTER_ADD_ROW_DELETION_POLICY", func(t *testing.T) {
t.Parallel()

before := `CREATE TABLE "users" (id STRING(36) NOT NULL, group_id STRING(36) NOT NULL REFERENCES "groups" ("id"), "name" STRING(255) NOT NULL, "age" INT64 DEFAULT 0 NOT NULL CHECK ("age" >= 0), description STRING, CreatedAt TIMESTAMP) PRIMARY KEY ("id");`
beforeDDL, err := NewParser(NewLexer(before)).Parse()
require.NoError(t, err)

after := `CREATE TABLE "users" (id STRING(36) NOT NULL, group_id STRING(36) NOT NULL REFERENCES "groups" ("id"), "name" STRING(255) NOT NULL, "age" INT64 DEFAULT 0 NOT NULL CHECK ("age" >= 0), description STRING, CreatedAt TIMESTAMP) PRIMARY KEY ("id"), ROW DELETION POLICY (OLDER_THAN(CreatedAt, INTERVAL 7 DAY));`
afterDDL, err := NewParser(NewLexer(after)).Parse()
require.NoError(t, err)

actual, err := DiffCreateTable(
beforeDDL.Stmts[0].(*CreateTableStmt),
afterDDL.Stmts[0].(*CreateTableStmt),
DiffCreateTableUseAlterTableAddConstraintNotValid(false),
)
assert.NoError(t, err)
expected := `-- -
-- +ROW DELETION POLICY (OLDER_THAN(CreatedAt, INTERVAL 7 DAY))
ALTER TABLE "users" ADD ROW DELETION POLICY (OLDER_THAN(CreatedAt, INTERVAL 7 DAY));
`

assert.Equal(t, expected, actual.String())
})

t.Run("success,ALTER_REPLACE_ROW_DELETION_POLICY", func(t *testing.T) {
t.Parallel()

before := `CREATE TABLE "users" (id STRING(36) NOT NULL, group_id STRING(36) NOT NULL REFERENCES "groups" ("id"), "name" STRING(255) NOT NULL, "age" INT64 DEFAULT 0 NOT NULL CHECK ("age" >= 0), description STRING, CreatedAt TIMESTAMP) PRIMARY KEY ("id"), ROW DELETION POLICY (OLDER_THAN(CreatedAt, INTERVAL 30 DAY));`
beforeDDL, err := NewParser(NewLexer(before)).Parse()
require.NoError(t, err)

after := `CREATE TABLE "users" (id STRING(36) NOT NULL, group_id STRING(36) NOT NULL REFERENCES "groups" ("id"), "name" STRING(255) NOT NULL, "age" INT64 DEFAULT 0 NOT NULL CHECK ("age" >= 0), description STRING, CreatedAt TIMESTAMP) PRIMARY KEY ("id"), ROW DELETION POLICY (OLDER_THAN(CreatedAt, INTERVAL 7 DAY));`
afterDDL, err := NewParser(NewLexer(after)).Parse()
require.NoError(t, err)

actual, err := DiffCreateTable(
beforeDDL.Stmts[0].(*CreateTableStmt),
afterDDL.Stmts[0].(*CreateTableStmt),
DiffCreateTableUseAlterTableAddConstraintNotValid(false),
)
assert.NoError(t, err)
expected := `-- -ROW DELETION POLICY (OLDER_THAN(CreatedAt, INTERVAL 30 DAY))
-- +ROW DELETION POLICY (OLDER_THAN(CreatedAt, INTERVAL 7 DAY))
ALTER TABLE "users" REPLACE ROW DELETION POLICY (OLDER_THAN(CreatedAt, INTERVAL 7 DAY));
`

assert.Equal(t, expected, actual.String())
})

t.Run("success,ALTER_REPLACE_DROP_DELETION_POLICY", func(t *testing.T) {
t.Parallel()

before := `CREATE TABLE "users" (id STRING(36) NOT NULL, group_id STRING(36) NOT NULL REFERENCES "groups" ("id"), "name" STRING(255) NOT NULL, "age" INT64 DEFAULT 0 NOT NULL CHECK ("age" >= 0), description STRING, CreatedAt TIMESTAMP) PRIMARY KEY ("id"), ROW DELETION POLICY (OLDER_THAN(CreatedAt, INTERVAL 30 DAY));`
beforeDDL, err := NewParser(NewLexer(before)).Parse()
require.NoError(t, err)

after := `CREATE TABLE "users" (id STRING(36) NOT NULL, group_id STRING(36) NOT NULL REFERENCES "groups" ("id"), "name" STRING(255) NOT NULL, "age" INT64 DEFAULT 0 NOT NULL CHECK ("age" >= 0), description STRING, CreatedAt TIMESTAMP) PRIMARY KEY ("id");`
afterDDL, err := NewParser(NewLexer(after)).Parse()
require.NoError(t, err)

actual, err := DiffCreateTable(
beforeDDL.Stmts[0].(*CreateTableStmt),
afterDDL.Stmts[0].(*CreateTableStmt),
DiffCreateTableUseAlterTableAddConstraintNotValid(false),
)
assert.NoError(t, err)
expected := `-- -ROW DELETION POLICY (OLDER_THAN(CreatedAt, INTERVAL 30 DAY))
-- +
ALTER TABLE "users" DROP ROW DELETION POLICY;
`

assert.Equal(t, expected, actual.String())
})

t.Run("success,DROP_ADD_FOREIGN_KEY", func(t *testing.T) {
t.Parallel()

Expand Down
2 changes: 1 addition & 1 deletion pkg/ddl/spanner/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ LabelTableOptions:
//
opt.Value = opt.Value.Append(rowDeletionPolicyContent...)

createTableStmt.Options = append(createTableStmt.Options, opt)
createTableStmt.RowDeletionPolicy = opt
case TOKEN_COMMA:
// do nothing
case TOKEN_SEMICOLON, TOKEN_EOF:
Expand Down
Loading