Skip to content

Commit

Permalink
feat(spanner/spansql): add support for managing the optimizer statist…
Browse files Browse the repository at this point in the history
…ics package (#7283)

* feat(spanner/spansql): add support for optimizer_statistics_package database option

* feat(spanner/spansql): add support for ALTER STATISTICS statement
  • Loading branch information
toga4 committed Jan 22, 2023
1 parent 3044b0c commit e528221
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 29 deletions.
119 changes: 119 additions & 0 deletions spanner/spansql/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,9 @@ func (p *parser) parseDDLStmt() (DDLStmt, *parseError) {
} else if p.sniff("ALTER", "CHANGE", "STREAM") {
acs, err := p.parseAlterChangeStream()
return acs, err
} else if p.sniff("ALTER", "STATISTICS") {
as, err := p.parseAlterStatistics()
return as, err
}

return nil, p.errorf("unknown DDL statement")
Expand Down Expand Up @@ -1827,6 +1830,21 @@ func (p *parser) parseDatabaseOptions() (DatabaseOptions, *parseError) {
*optimizerVersion = version
}
opts.OptimizerVersion = optimizerVersion
} else if p.eat("optimizer_statistics_package", "=") {
tok := p.next()
if tok.err != nil {
return DatabaseOptions{}, tok.err
}
optimizerStatisticsPackage := new(string)
if tok.value == "null" {
*optimizerStatisticsPackage = ""
} else {
if tok.typ != stringToken {
return DatabaseOptions{}, p.errorf("invalid optimizer_statistics_package: %v", tok.value)
}
*optimizerStatisticsPackage = tok.string
}
opts.OptimizerStatisticsPackage = optimizerStatisticsPackage
} else if p.eat("version_retention_period", "=") {
tok := p.next()
if tok.err != nil {
Expand Down Expand Up @@ -2202,6 +2220,107 @@ func (p *parser) parseChangeStreamOptions() (ChangeStreamOptions, *parseError) {
return cso, nil
}

func (p *parser) parseAlterStatistics() (*AlterStatistics, *parseError) {
debugf("parseAlterStatistics: %v", p)

/*
ALTER STATISTICS package_name
action
where package_name is:
{a—z}[{a—z|0—9|_|-}+]{a—z|0—9}
and action is:
SET OPTIONS ( options_def )
and options_def is:
{ allow_gc = { true | false } }
*/

if err := p.expect("ALTER"); err != nil {
return nil, err
}
pos := p.Pos()
if err := p.expect("STATISTICS"); err != nil {
return nil, err
}
// This is not 100% correct as package_name identifiers have slightly more
// restrictions than table names, but the restrictions are currently not
// applied in the spansql parser.
// TODO: Apply restrictions for all identifiers.
dbname, err := p.parseTableOrIndexOrColumnName()
if err != nil {
return nil, err
}
a := &AlterStatistics{Name: dbname, 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.parseStatisticsOptions()
if err != nil {
return nil, err
}
a.Alteration = SetStatisticsOptions{Options: options}
return a, nil
}
}

func (p *parser) parseStatisticsOptions() (StatisticsOptions, *parseError) {
debugf("parseDatabaseOptions: %v", p)
/*
options_def is:
{ allow_gc = { true | false } }
*/

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

// We ignore case for the key (because it is easier) but not the value.
var opts StatisticsOptions
for {
if p.eat("allow_gc", "=") {
tok := p.next()
if tok.err != nil {
return StatisticsOptions{}, tok.err
}
allowGC := new(bool)
switch tok.value {
case "true":
*allowGC = true
case "false":
*allowGC = false
default:
return StatisticsOptions{}, p.errorf("invalid allow_gc: %v", tok.value)
}
opts.AllowGC = allowGC
} else {
tok := p.next()
return StatisticsOptions{}, p.errorf("unknown statistics option: %v", tok.value)
}
if p.sniff(")") {
break
}
if !p.eat(",") {
return StatisticsOptions{}, p.errorf("missing ',' in options list")
}
}
if err := p.expect(")"); err != nil {
return StatisticsOptions{}, err
}

return opts, nil
}

var baseTypes = map[string]TypeBase{
"BOOL": Bool,
"INT64": Int64,
Expand Down
50 changes: 35 additions & 15 deletions spanner/spansql/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1016,17 +1016,18 @@ func TestParseDDL(t *testing.T) {
},
}}},
{
`ALTER DATABASE dbname SET OPTIONS (optimizer_version=2, version_retention_period='7d', enable_key_visualizer=true, default_leader='europe-west1')`,
`ALTER DATABASE dbname SET OPTIONS (optimizer_version=2, optimizer_statistics_package='auto_20191128_14_47_22UTC', version_retention_period='7d', enable_key_visualizer=true, default_leader='europe-west1')`,
&DDL{
Filename: "filename", List: []DDLStmt{
&AlterDatabase{
Name: "dbname",
Alteration: SetDatabaseOptions{
Options: DatabaseOptions{
OptimizerVersion: func(i int) *int { return &i }(2),
VersionRetentionPeriod: func(s string) *string { return &s }("7d"),
EnableKeyVisualizer: func(b bool) *bool { return &b }(true),
DefaultLeader: func(s string) *string { return &s }("europe-west1"),
OptimizerVersion: func(i int) *int { return &i }(2),
OptimizerStatisticsPackage: func(s string) *string { return &s }("auto_20191128_14_47_22UTC"),
VersionRetentionPeriod: func(s string) *string { return &s }("7d"),
EnableKeyVisualizer: func(b bool) *bool { return &b }(true),
DefaultLeader: func(s string) *string { return &s }("europe-west1"),
},
},
Position: line(1),
Expand All @@ -1035,17 +1036,18 @@ func TestParseDDL(t *testing.T) {
},
},
{
`ALTER DATABASE dbname SET OPTIONS (optimizer_version=2, version_retention_period='7d', enable_key_visualizer=true, default_leader='europe-west1'); CREATE TABLE users (UserId STRING(MAX) NOT NULL,) PRIMARY KEY (UserId);`,
`ALTER DATABASE dbname SET OPTIONS (optimizer_version=2, optimizer_statistics_package='auto_20191128_14_47_22UTC', version_retention_period='7d', enable_key_visualizer=true, default_leader='europe-west1'); CREATE TABLE users (UserId STRING(MAX) NOT NULL,) PRIMARY KEY (UserId);`,
&DDL{
Filename: "filename", List: []DDLStmt{
&AlterDatabase{
Name: "dbname",
Alteration: SetDatabaseOptions{
Options: DatabaseOptions{
OptimizerVersion: func(i int) *int { return &i }(2),
VersionRetentionPeriod: func(s string) *string { return &s }("7d"),
EnableKeyVisualizer: func(b bool) *bool { return &b }(true),
DefaultLeader: func(s string) *string { return &s }("europe-west1"),
OptimizerVersion: func(i int) *int { return &i }(2),
OptimizerStatisticsPackage: func(s string) *string { return &s }("auto_20191128_14_47_22UTC"),
VersionRetentionPeriod: func(s string) *string { return &s }("7d"),
EnableKeyVisualizer: func(b bool) *bool { return &b }(true),
DefaultLeader: func(s string) *string { return &s }("europe-west1"),
},
},
Position: line(1),
Expand All @@ -1063,17 +1065,18 @@ func TestParseDDL(t *testing.T) {
},
},
{
`ALTER DATABASE dbname SET OPTIONS (optimizer_version=null, version_retention_period=null, enable_key_visualizer=null, default_leader=null)`,
`ALTER DATABASE dbname SET OPTIONS (optimizer_version=null, optimizer_statistics_package=null, version_retention_period=null, enable_key_visualizer=null, default_leader=null)`,
&DDL{
Filename: "filename", List: []DDLStmt{
&AlterDatabase{
Name: "dbname",
Alteration: SetDatabaseOptions{
Options: DatabaseOptions{
OptimizerVersion: func(i int) *int { return &i }(0),
VersionRetentionPeriod: func(s string) *string { return &s }(""),
EnableKeyVisualizer: func(b bool) *bool { return &b }(false),
DefaultLeader: func(s string) *string { return &s }(""),
OptimizerVersion: func(i int) *int { return &i }(0),
OptimizerStatisticsPackage: func(s string) *string { return &s }(""),
VersionRetentionPeriod: func(s string) *string { return &s }(""),
EnableKeyVisualizer: func(b bool) *bool { return &b }(false),
DefaultLeader: func(s string) *string { return &s }(""),
},
},
Position: line(1),
Expand Down Expand Up @@ -1131,6 +1134,23 @@ func TestParseDDL(t *testing.T) {
Position: line(1),
},
}}},
{
`ALTER STATISTICS auto_20191128_14_47_22UTC SET OPTIONS (allow_gc=false)`,
&DDL{
Filename: "filename",
List: []DDLStmt{
&AlterStatistics{
Name: "auto_20191128_14_47_22UTC",
Alteration: SetStatisticsOptions{
Options: StatisticsOptions{
AllowGC: func(b bool) *bool { return &b }(false),
},
},
Position: line(1),
},
},
},
},
}
for _, test := range tests {
got, err := ParseDDL("filename", test.in)
Expand Down
28 changes: 28 additions & 0 deletions spanner/spansql/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,17 @@ func (do DatabaseOptions) SQL() string {
str += fmt.Sprintf("optimizer_version=%v", *do.OptimizerVersion)
}
}
if do.OptimizerStatisticsPackage != nil {
if hasOpt {
str += ", "
}
hasOpt = true
if *do.OptimizerStatisticsPackage == "" {
str += "optimizer_statistics_package=null"
} else {
str += fmt.Sprintf("optimizer_statistics_package='%s'", *do.OptimizerStatisticsPackage)
}
}
if do.VersionRetentionPeriod != nil {
if hasOpt {
str += ", "
Expand Down Expand Up @@ -311,6 +322,23 @@ func (do DatabaseOptions) SQL() string {
return str
}

func (as AlterStatistics) SQL() string {
return "ALTER STATISTICS " + as.Name.SQL() + " " + as.Alteration.SQL()
}

func (sso SetStatisticsOptions) SQL() string {
return "SET " + sso.Options.SQL()
}

func (sa StatisticsOptions) SQL() string {
str := "OPTIONS ("
if sa.AllowGC != nil {
str += fmt.Sprintf("allow_gc=%v", *sa.AllowGC)
}
str += ")"
return str
}

func (d *Delete) SQL() string {
return "DELETE FROM " + d.Table.SQL() + " WHERE " + d.Where.SQL()
}
Expand Down
35 changes: 25 additions & 10 deletions spanner/spansql/sql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,28 +389,30 @@ func TestSQL(t *testing.T) {
&AlterDatabase{
Name: "dbname",
Alteration: SetDatabaseOptions{Options: DatabaseOptions{
VersionRetentionPeriod: func(s string) *string { return &s }("7d"),
OptimizerVersion: func(i int) *int { return &i }(2),
EnableKeyVisualizer: func(b bool) *bool { return &b }(true),
DefaultLeader: func(s string) *string { return &s }("europe-west1"),
VersionRetentionPeriod: func(s string) *string { return &s }("7d"),
OptimizerVersion: func(i int) *int { return &i }(2),
OptimizerStatisticsPackage: func(s string) *string { return &s }("auto_20191128_14_47_22UTC"),
EnableKeyVisualizer: func(b bool) *bool { return &b }(true),
DefaultLeader: func(s string) *string { return &s }("europe-west1"),
}},
Position: line(1),
},
"ALTER DATABASE dbname SET OPTIONS (optimizer_version=2, version_retention_period='7d', enable_key_visualizer=true, default_leader='europe-west1')",
"ALTER DATABASE dbname SET OPTIONS (optimizer_version=2, optimizer_statistics_package='auto_20191128_14_47_22UTC', version_retention_period='7d', enable_key_visualizer=true, default_leader='europe-west1')",
reparseDDL,
},
{
&AlterDatabase{
Name: "dbname",
Alteration: SetDatabaseOptions{Options: DatabaseOptions{
VersionRetentionPeriod: func(s string) *string { return &s }(""),
OptimizerVersion: func(i int) *int { return &i }(0),
EnableKeyVisualizer: func(b bool) *bool { return &b }(false),
DefaultLeader: func(s string) *string { return &s }(""),
VersionRetentionPeriod: func(s string) *string { return &s }(""),
OptimizerVersion: func(i int) *int { return &i }(0),
OptimizerStatisticsPackage: func(s string) *string { return &s }(""),
EnableKeyVisualizer: func(b bool) *bool { return &b }(false),
DefaultLeader: func(s string) *string { return &s }(""),
}},
Position: line(1),
},
"ALTER DATABASE dbname SET OPTIONS (optimizer_version=null, version_retention_period=null, enable_key_visualizer=null, default_leader=null)",
"ALTER DATABASE dbname SET OPTIONS (optimizer_version=null, optimizer_statistics_package=null, version_retention_period=null, enable_key_visualizer=null, default_leader=null)",
reparseDDL,
},
{
Expand Down Expand Up @@ -451,6 +453,19 @@ func TestSQL(t *testing.T) {
"ALTER CHANGE STREAM csname SET OPTIONS (value_capture_type='NEW_VALUES')",
reparseDDL,
},
{
&AlterStatistics{
Name: "auto_20191128_14_47_22UTC",
Alteration: SetStatisticsOptions{
Options: StatisticsOptions{
AllowGC: func(b bool) *bool { return &b }(false),
},
},
Position: line(1),
},
"ALTER STATISTICS auto_20191128_14_47_22UTC SET OPTIONS (allow_gc=false)",
reparseDDL,
},
{
&Insert{
Table: "Singers",
Expand Down
37 changes: 33 additions & 4 deletions spanner/spansql/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,10 +290,11 @@ func (SetDatabaseOptions) isDatabaseAlteration() {}
// DatabaseOptions represents options on a database as part of a
// ALTER DATABASE statement.
type DatabaseOptions struct {
OptimizerVersion *int
VersionRetentionPeriod *string
EnableKeyVisualizer *bool
DefaultLeader *string
OptimizerVersion *int
OptimizerStatisticsPackage *string
VersionRetentionPeriod *string
EnableKeyVisualizer *bool
DefaultLeader *string
}

// Delete represents a DELETE statement.
Expand Down Expand Up @@ -1117,3 +1118,31 @@ type ChangeStreamOptions struct {
RetentionPeriod *string
ValueCaptureType *string
}

// AlterStatistics represents an ALTER STATISTICS statement.
// https://cloud.google.com/spanner/docs/data-definition-language#alter-statistics
type AlterStatistics struct {
Name ID
Alteration StatisticsAlteration

Position Position // position of the "ALTER" token
}

func (as *AlterStatistics) String() string { return fmt.Sprintf("%#v", as) }
func (*AlterStatistics) isDDLStmt() {}
func (as *AlterStatistics) Pos() Position { return as.Position }
func (as *AlterStatistics) clearOffset() { as.Position.Offset = 0 }

type StatisticsAlteration interface {
isStatisticsAlteration()
SQL() string
}

type SetStatisticsOptions struct{ Options StatisticsOptions }

func (SetStatisticsOptions) isStatisticsAlteration() {}

// StatisticsOptions represents options on a statistics as part of a ALTER STATISTICS statement.
type StatisticsOptions struct {
AllowGC *bool
}

0 comments on commit e528221

Please sign in to comment.