From e52822139e2821a11873c2d6af85a5fea07700e8 Mon Sep 17 00:00:00 2001 From: TOGASHI Tomoki Date: Sun, 22 Jan 2023 22:13:08 +0900 Subject: [PATCH] feat(spanner/spansql): add support for managing the optimizer statistics package (#7283) * feat(spanner/spansql): add support for optimizer_statistics_package database option * feat(spanner/spansql): add support for ALTER STATISTICS statement --- spanner/spansql/parser.go | 119 +++++++++++++++++++++++++++++++++ spanner/spansql/parser_test.go | 50 +++++++++----- spanner/spansql/sql.go | 28 ++++++++ spanner/spansql/sql_test.go | 35 +++++++--- spanner/spansql/types.go | 37 ++++++++-- 5 files changed, 240 insertions(+), 29 deletions(-) diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go index 6c11a1a7671f..335d4e56c1d6 100644 --- a/spanner/spansql/parser.go +++ b/spanner/spansql/parser.go @@ -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") @@ -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 { @@ -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, diff --git a/spanner/spansql/parser_test.go b/spanner/spansql/parser_test.go index 35a4cd69fe2b..2ffe46b5c6d5 100644 --- a/spanner/spansql/parser_test.go +++ b/spanner/spansql/parser_test.go @@ -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), @@ -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), @@ -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), @@ -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) diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go index 11fd55b01467..92bf0f804ec4 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -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 += ", " @@ -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() } diff --git a/spanner/spansql/sql_test.go b/spanner/spansql/sql_test.go index 411b0a7d953f..da2ba900d555 100644 --- a/spanner/spansql/sql_test.go +++ b/spanner/spansql/sql_test.go @@ -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, }, { @@ -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", diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go index 548922fa06de..eba8312c601f 100644 --- a/spanner/spansql/types.go +++ b/spanner/spansql/types.go @@ -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. @@ -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 +}