Skip to content

Commit

Permalink
feat: Support Spanner ROW DELETION POLICY (#75)
Browse files Browse the repository at this point in the history
  • Loading branch information
ginokent authored Jun 6, 2024
2 parents 7076f37 + b0fd756 commit aa1359f
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 14 deletions.
3 changes: 0 additions & 3 deletions pkg/ddl/cockroachdb/lexar.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,6 @@ const (
TOKEN_TRUE TokenType = "TRUE"
TOKEN_FALSE TokenType = "FALSE"

// LITERAL.
TOKEN_LITERAL TokenType = "LITERAL"

// IDENTIFIER.
TOKEN_IDENT TokenType = "IDENT"
)
Expand Down
3 changes: 0 additions & 3 deletions pkg/ddl/mysql/lexar.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,6 @@ const (
TOKEN_TRUE TokenType = "TRUE"
TOKEN_FALSE TokenType = "FALSE"

// LITERAL.
TOKEN_LITERAL TokenType = "LITERAL"

// IDENTIFIER.
TOKEN_IDENT TokenType = "IDENT"
)
Expand Down
3 changes: 0 additions & 3 deletions pkg/ddl/postgres/lexar.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,6 @@ const (
TOKEN_TRUE TokenType = "TRUE"
TOKEN_FALSE TokenType = "FALSE"

// LITERAL.
TOKEN_LITERAL TokenType = "LITERAL"

// IDENTIFIER.
TOKEN_IDENT TokenType = "IDENT"
)
Expand Down
21 changes: 19 additions & 2 deletions pkg/ddl/spanner/lexar.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,13 @@ const (
TOKEN_TRUE TokenType = "TRUE"
TOKEN_FALSE TokenType = "FALSE"

// LITERAL.
TOKEN_LITERAL TokenType = "LITERAL"
// OPTION
TOKEN_ROW TokenType = "ROW"
TOKEN_DELETION TokenType = "DELETION"
TOKEN_POLICY TokenType = "POLICY"
TOKEN_OLDER_THAN TokenType = "OLDER_THAN"
TOKEN_INTERVAL TokenType = "INTERVAL"
TOKEN_DAY TokenType = "DAY"

// IDENTIFIER.
TOKEN_IDENT TokenType = "IDENT"
Expand Down Expand Up @@ -234,6 +239,18 @@ func lookupIdent(ident string) TokenType {
return TOKEN_TRUE
case "FALSE":
return TOKEN_FALSE
case "ROW":
return TOKEN_ROW
case "DELETION":
return TOKEN_DELETION
case "POLICY":
return TOKEN_POLICY
case "OLDER_THAN":
return TOKEN_OLDER_THAN
case "INTERVAL":
return TOKEN_INTERVAL
case "DAY":
return TOKEN_DAY
default:
return TOKEN_IDENT
}
Expand Down
79 changes: 78 additions & 1 deletion pkg/ddl/spanner/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func (p *Parser) parseCreateStatement() (Stmt, error) { //nolint:ireturn
}
}

//nolint:cyclop,funlen,gocognit,gocyclo
//nolint:cyclop,funlen,gocognit,gocyclo,maintidx
func (p *Parser) parseCreateTableStmt() (*CreateTableStmt, error) {
createTableStmt := &CreateTableStmt{
Indent: Indent,
Expand Down Expand Up @@ -236,6 +236,83 @@ LabelTableOptions:
}
opt.Value = opt.Value.Append(NewRawIdent(onAction))
}
createTableStmt.Options = append(createTableStmt.Options, opt)
case TOKEN_ROW:
// ROW DELETION POLICY (OLDER_THAN(ExpiredDate, INTERVAL 0 DAY))
opt := &Option{}
p.nextToken() // current = DELETION
if err := p.checkCurrentToken(TOKEN_DELETION); err != nil {
return nil, apperr.Errorf(errFmtPrefix+"checkCurrentToken: %w", err)
}
p.nextToken() // current = POLICY
if err := p.checkCurrentToken(TOKEN_POLICY); err != nil {
return nil, apperr.Errorf(errFmtPrefix+"checkCurrentToken: %w", err)
}
opt.Name = "ROW DELETION POLICY"
rowDeletionPolicyContent := make([]*Ident, 0)
//
p.nextToken() // current = `(`
if err := p.checkCurrentToken(TOKEN_OPEN_PAREN); err != nil {
return nil, apperr.Errorf(errFmtPrefix+"checkCurrentToken: %w", err)
}
rowDeletionPolicyContent = append(rowDeletionPolicyContent, NewRawIdent(p.currentToken.Literal.String()))
//
p.nextToken() // current = OLDER_THAN
if err := p.checkCurrentToken(TOKEN_OLDER_THAN); err != nil {
return nil, apperr.Errorf(errFmtPrefix+"checkCurrentToken: %w", err)
}
rowDeletionPolicyContent = append(rowDeletionPolicyContent, NewRawIdent(p.currentToken.Literal.String()))
//
p.nextToken() // current = `(`
if err := p.checkCurrentToken(TOKEN_OPEN_PAREN); err != nil {
return nil, apperr.Errorf(errFmtPrefix+"checkCurrentToken: %w", err)
}
rowDeletionPolicyContent = append(rowDeletionPolicyContent, NewRawIdent(p.currentToken.Literal.String()))
//
p.nextToken() // current = column_name
if err := p.checkCurrentToken(TOKEN_IDENT); err != nil {
return nil, apperr.Errorf(errFmtPrefix+"checkCurrentToken: %w", err)
}
rowDeletionPolicyContent = append(rowDeletionPolicyContent, NewRawIdent(p.currentToken.Literal.String()))
//
p.nextToken() // current = COMMA
if err := p.checkCurrentToken(TOKEN_COMMA); err != nil {
return nil, apperr.Errorf(errFmtPrefix+"checkCurrentToken: %w", err)
}
rowDeletionPolicyContent = append(rowDeletionPolicyContent, NewRawIdent(p.currentToken.Literal.String()))
//
p.nextToken() // current = INTERVAL
if err := p.checkCurrentToken(TOKEN_INTERVAL); err != nil {
return nil, apperr.Errorf(errFmtPrefix+"checkCurrentToken: %w", err)
}
rowDeletionPolicyContent = append(rowDeletionPolicyContent, NewRawIdent(p.currentToken.Literal.String()))
//
p.nextToken() // current = 0
if err := p.checkCurrentToken(TOKEN_IDENT); err != nil {
return nil, apperr.Errorf(errFmtPrefix+"checkCurrentToken: %w", err)
}
rowDeletionPolicyContent = append(rowDeletionPolicyContent, NewRawIdent(p.currentToken.Literal.String()))
//
p.nextToken() // current = DAY
if err := p.checkCurrentToken(TOKEN_DAY); err != nil {
return nil, apperr.Errorf(errFmtPrefix+"checkCurrentToken: %w", err)
}
rowDeletionPolicyContent = append(rowDeletionPolicyContent, NewRawIdent(p.currentToken.Literal.String()))
//
p.nextToken() // current = `)`
if err := p.checkCurrentToken(TOKEN_CLOSE_PAREN); err != nil {
return nil, apperr.Errorf(errFmtPrefix+"checkCurrentToken: %w", err)
}
rowDeletionPolicyContent = append(rowDeletionPolicyContent, NewRawIdent(p.currentToken.Literal.String()))
//
p.nextToken() // current = `)`
if err := p.checkCurrentToken(TOKEN_CLOSE_PAREN); err != nil {
return nil, apperr.Errorf(errFmtPrefix+"checkCurrentToken: %w", err)
}
rowDeletionPolicyContent = append(rowDeletionPolicyContent, NewRawIdent(p.currentToken.Literal.String()))
//
opt.Value = opt.Value.Append(rowDeletionPolicyContent...)

createTableStmt.Options = append(createTableStmt.Options, opt)
case TOKEN_COMMA:
// do nothing
Expand Down
72 changes: 70 additions & 2 deletions pkg/ddl/spanner/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestParser_Parse(t *testing.T) {
t.Run("success,CREATE_TABLE", func(t *testing.T) {
// t.Parallel()

l := NewLexer(`CREATE TABLE "groups" ("id" STRING(36) NOT NULL, description STRING) PRIMARY KEY ("id"); CREATE TABLE "users" (id STRING(36) NOT NULL, group_id STRING(36) NOT NULL, "name" STRING(255) NOT NULL, "age" INT64 DEFAULT 0, description STRING, CONSTRAINT users_age_check CHECK ("age" >= 0), CONSTRAINT users_group_id_fkey FOREIGN KEY (group_id) REFERENCES "groups" ("id")) PRIMARY KEY ("id"), INTERLEAVE IN PARENT names ON DELETE NO ACTION;`)
l := NewLexer(`CREATE TABLE "groups" ("id" STRING(36) NOT NULL, description STRING) PRIMARY KEY ("id"); CREATE TABLE "users" (id STRING(36) NOT NULL, group_id STRING(36) NOT NULL, "name" STRING(255) NOT NULL, "age" INT64 DEFAULT 0, ExpiredDate TIMESTAMP, description STRING, CONSTRAINT users_age_check CHECK ("age" >= 0), CONSTRAINT users_group_id_fkey FOREIGN KEY (group_id) REFERENCES "groups" ("id")) PRIMARY KEY ("id"), INTERLEAVE IN PARENT names ON DELETE NO ACTION, ROW DELETION POLICY (OLDER_THAN(ExpiredDate, INTERVAL 0 DAY));`)
p := NewParser(l)
actual, err := p.Parse()
require.NoError(t, err)
Expand All @@ -36,11 +36,13 @@ CREATE TABLE "users" (
group_id STRING(36) NOT NULL,
"name" STRING(255) NOT NULL,
"age" INT64 DEFAULT 0,
ExpiredDate TIMESTAMP,
description STRING,
CONSTRAINT users_age_check CHECK ("age" >= 0),
CONSTRAINT users_group_id_fkey FOREIGN KEY (group_id) REFERENCES "groups" ("id")
) PRIMARY KEY ("id"),
INTERLEAVE IN PARENT names ON DELETE NO ACTION;
INTERLEAVE IN PARENT names ON DELETE NO ACTION,
ROW DELETION POLICY (OLDER_THAN(ExpiredDate, INTERVAL 0 DAY));
`

if !assert.Equal(t, expected, actual.String()) {
Expand Down Expand Up @@ -281,6 +283,72 @@ CREATE TABLE IF NOT EXISTS complex_defaults (
input: `CREATE TABLE "users" ("id" STRING(36)) PRIMARY KEY (id), INTERLEAVE IN PARENT table_name ON DELETE NO;`,
wantErr: ddl.ErrUnexpectedToken,
},
// ROW DELETION POLICY (OLDER_THAN(ExpiredDate, INTERVAL 0 DAY));
{
name: "failure,CREATE_TABLE_table_name_column_name_OPTION_PRIMARY_KEY_ROW_INVALID",
input: `CREATE TABLE "users" ("id" STRING(36)) PRIMARY KEY (id), ROW;`,
wantErr: ddl.ErrUnexpectedToken,
},
{
name: "failure,CREATE_TABLE_table_name_column_name_OPTION_PRIMARY_KEY_ROW_DELETION_INVALID",
input: `CREATE TABLE "users" ("id" STRING(36)) PRIMARY KEY (id), ROW DELETION;`,
wantErr: ddl.ErrUnexpectedToken,
},
{
name: "failure,CREATE_TABLE_table_name_column_name_OPTION_PRIMARY_KEY_ROW_DELETION_POLICY_INVALID",
input: `CREATE TABLE "users" ("id" STRING(36)) PRIMARY KEY (id), ROW DELETION POLICY;`,
wantErr: ddl.ErrUnexpectedToken,
},
{
name: "failure,CREATE_TABLE_table_name_column_name_OPTION_PRIMARY_KEY_ROW_DELETION_POLICY_(_INVALID",
input: `CREATE TABLE "users" ("id" STRING(36)) PRIMARY KEY (id), ROW DELETION POLICY (;`,
wantErr: ddl.ErrUnexpectedToken,
},
{
name: "failure,CREATE_TABLE_table_name_column_name_OPTION_PRIMARY_KEY_ROW_DELETION_POLICY_(_INVALID",
input: `CREATE TABLE "users" ("id" STRING(36)) PRIMARY KEY (id), ROW DELETION POLICY (;`,
wantErr: ddl.ErrUnexpectedToken,
},
{
name: "failure,CREATE_TABLE_table_name_column_name_OPTION_PRIMARY_KEY_ROW_DELETION_POLICY_OLDER_THAN_INVALID",
input: `CREATE TABLE "users" ("id" STRING(36)) PRIMARY KEY (id), ROW DELETION POLICY (OLDER_THAN;`,
wantErr: ddl.ErrUnexpectedToken,
},
{
name: "failure,CREATE_TABLE_table_name_column_name_OPTION_PRIMARY_KEY_ROW_DELETION_POLICY_OLDER_THAN_OPEN_INVALID",
input: `CREATE TABLE "users" ("id" STRING(36)) PRIMARY KEY (id), ROW DELETION POLICY (OLDER_THAN(;`,
wantErr: ddl.ErrUnexpectedToken,
},
{
name: "failure,CREATE_TABLE_table_name_column_name_OPTION_PRIMARY_KEY_ROW_DELETION_POLICY_OLDER_THAN_OPEN_column_name_INVALID",
input: `CREATE TABLE "users" ("id" STRING(36)) PRIMARY KEY (id), ROW DELETION POLICY (OLDER_THAN(ExpiredDate;`,
wantErr: ddl.ErrUnexpectedToken,
},
{
name: "failure,CREATE_TABLE_table_name_column_name_OPTION_PRIMARY_KEY_ROW_DELETION_POLICY_OLDER_THAN_OPEN_column_name_COMMA_INVALID",
input: `CREATE TABLE "users" ("id" STRING(36)) PRIMARY KEY (id), ROW DELETION POLICY (OLDER_THAN(ExpiredDate,;`,
wantErr: ddl.ErrUnexpectedToken,
},
{
name: "failure,CREATE_TABLE_table_name_column_name_OPTION_PRIMARY_KEY_ROW_DELETION_POLICY_OLDER_THAN_OPEN_column_name_COMMA_INTERVAL_INVALID",
input: `CREATE TABLE "users" ("id" STRING(36)) PRIMARY KEY (id), ROW DELETION POLICY (OLDER_THAN(ExpiredDate, INTERVAL;`,
wantErr: ddl.ErrUnexpectedToken,
},
{
name: "failure,CREATE_TABLE_table_name_column_name_OPTION_PRIMARY_KEY_ROW_DELETION_POLICY_OLDER_THAN_OPEN_column_name_COMMA_INTERVAL_NUMBER_INVALID",
input: `CREATE TABLE "users" ("id" STRING(36)) PRIMARY KEY (id), ROW DELETION POLICY (OLDER_THAN(ExpiredDate, INTERVAL 0;`,
wantErr: ddl.ErrUnexpectedToken,
},
{
name: "failure,CREATE_TABLE_table_name_column_name_OPTION_PRIMARY_KEY_ROW_DELETION_POLICY_OLDER_THAN_OPEN_column_name_COMMA_INTERVAL_NUMBER_DAY_INVALID",
input: `CREATE TABLE "users" ("id" STRING(36)) PRIMARY KEY (id), ROW DELETION POLICY (OLDER_THAN(ExpiredDate, INTERVAL 0 DAY;`,
wantErr: ddl.ErrUnexpectedToken,
},
{
name: "failure,CREATE_TABLE_table_name_column_name_OPTION_PRIMARY_KEY_ROW_DELETION_POLICY_OLDER_THAN_OPEN_column_name_COMMA_INTERVAL_NUMBER_DAY_CLOSE_INVALID",
input: `CREATE TABLE "users" ("id" STRING(36)) PRIMARY KEY (id), ROW DELETION POLICY (OLDER_THAN(ExpiredDate, INTERVAL 0 DAY);`,
wantErr: ddl.ErrUnexpectedToken,
},
{
name: "failure,CREATE_TABLE_table_name_column_name_CONSTRAINT_INVALID_FOREIGN",
input: `CREATE TABLE "users" ("id" STRING(36), FOREIGN NOT`,
Expand Down

0 comments on commit aa1359f

Please sign in to comment.