From 3a6a8a8183f59e8a6b0e42412cf703ff28c9cc25 Mon Sep 17 00:00:00 2001 From: Tommy Reilly Date: Tue, 4 Apr 2023 21:21:22 +0000 Subject: [PATCH] opt: improve opt-tester ddl support Add support for enum's appearing in stats and add preliminary support for indexes on materialized views, it doesn't do anything but doesn't panic because it can't find the table. Epic: none Release note: None Informs: #100510 --- pkg/sql/opt/testutils/opttester/testdata/ddl | 103 ++++++++++++++++++ pkg/sql/opt/testutils/testcat/alter_table.go | 6 +- pkg/sql/opt/testutils/testcat/create_index.go | 22 ++-- pkg/sql/opt/testutils/testcat/create_view.go | 5 + pkg/sql/opt/testutils/testcat/test_catalog.go | 20 +++- 5 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 pkg/sql/opt/testutils/opttester/testdata/ddl diff --git a/pkg/sql/opt/testutils/opttester/testdata/ddl b/pkg/sql/opt/testutils/opttester/testdata/ddl new file mode 100644 index 000000000000..80b106f3e26b --- /dev/null +++ b/pkg/sql/opt/testutils/opttester/testdata/ddl @@ -0,0 +1,103 @@ +exec-ddl +CREATE TYPE greeting AS ENUM ('hello', 'howdy', 'hi') +---- + +exec-ddl +CREATE TABLE t (x INT, g greeting) +---- + +exec-ddl +ALTER TABLE t INJECT STATISTICS '[ + { + "avg_size": 4, + "columns": [ + "g" + ], + "created_at": "2023-03-14 14:05:25.635783", + "distinct_count": 3, + "histo_buckets": [ + { + "distinct_range": 0, + "num_eq": 1, + "num_range": 0, + "upper_bound": "hello" + }, + { + "distinct_range": 0, + "num_eq": 2, + "num_range": 0, + "upper_bound": "howdy" + }, + { + "distinct_range": 0, + "num_eq": 3, + "num_range": 0, + "upper_bound": "hi" + } + ], + "histo_col_type": "greeting", + "histo_version": 2, + "name": "__auto__", + "null_count": 0, + "row_count": 6 + }, + { + "avg_size": 4, + "columns": [ + "x" + ], + "created_at": "2023-03-14 14:05:25.635783", + "distinct_count": 10, + "histo_buckets": [ + { + "distinct_range": 0, + "num_eq": 0, + "num_range": 0, + "upper_bound": "0" + }, + { + "distinct_range": 5, + "num_eq": 1, + "num_range": 5, + "upper_bound": "10" + } + ], + "histo_col_type": "INT8", + "histo_version": 2, + "name": "__auto__", + "null_count": 0, + "row_count": 6 + } + ]' +---- + +opt format=show-stats +SELECT * FROM t WHERE x > 0 AND g = 'howdy' +---- +select + ├── columns: x:1(int!null) g:2(greeting!null) + ├── immutable + ├── stats: [rows=2, distinct(1)=2, null(1)=0, distinct(2)=1, null(2)=0] + │ histogram(1)= 0 0 1.6667 0.33333 + │ <--- 0 --------- 10 -- + │ histogram(2)= 0 2 + │ <--- 'howdy' + ├── fd: ()-->(2) + ├── scan t + │ ├── columns: x:1(int) g:2(greeting) + │ └── stats: [rows=6, distinct(1)=6, null(1)=0, distinct(2)=3, null(2)=0] + │ histogram(1)= 0 0 5 1 + │ <--- 0 --- 10 + │ histogram(2)= 0 1 0 2 0 3 + │ <--- 'hello' --- 'howdy' --- 'hi' + └── filters + ├── gt [type=bool, outer=(1), constraints=(/1: [/1 - ]; tight)] + │ ├── variable: x:1 [type=int] + │ └── const: 0 [type=int] + └── eq [type=bool, outer=(2), immutable, constraints=(/2: [/'howdy' - /'howdy']; tight), fd=()-->(2)] + ├── variable: g:2 [type=greeting] + └── const: 'howdy' [type=greeting] + +exec-ddl +CREATE MATERIALIZED VIEW v AS SELECT x FROM t +---- diff --git a/pkg/sql/opt/testutils/testcat/alter_table.go b/pkg/sql/opt/testutils/testcat/alter_table.go index 7473982e0067..9b18b67bd8d9 100644 --- a/pkg/sql/opt/testutils/testcat/alter_table.go +++ b/pkg/sql/opt/testutils/testcat/alter_table.go @@ -37,7 +37,7 @@ func (tc *Catalog) AlterTable(stmt *tree.AlterTable) { for _, cmd := range stmt.Cmds { switch t := cmd.(type) { case *tree.AlterTableInjectStats: - injectTableStats(tab, t.Stats) + injectTableStats(tab, t.Stats, tc) case *tree.AlterTableAddConstraint: switch d := t.ConstraintDef.(type) { @@ -55,7 +55,7 @@ func (tc *Catalog) AlterTable(stmt *tree.AlterTable) { } // injectTableStats sets the table statistics as specified by a JSON object. -func injectTableStats(tt *Table, statsExpr tree.Expr) { +func injectTableStats(tt *Table, statsExpr tree.Expr, tc *Catalog) { ctx := context.Background() semaCtx := tree.MakeSemaContext() evalCtx := eval.MakeTestingEvalContext(cluster.MakeTestingClusterSettings()) @@ -78,7 +78,7 @@ func injectTableStats(tt *Table, statsExpr tree.Expr) { } tt.Stats = make([]*TableStat, len(stats)) for i := range stats { - tt.Stats[i] = &TableStat{js: stats[i], tt: tt, evalCtx: &evalCtx} + tt.Stats[i] = &TableStat{js: stats[i], tt: tt, evalCtx: &evalCtx, tc: tc} } // Call ColumnOrdinal on all possible columns to assert that // the column names are valid. diff --git a/pkg/sql/opt/testutils/testcat/create_index.go b/pkg/sql/opt/testutils/testcat/create_index.go index fdb63ba4c992..3a41fd1f2caf 100644 --- a/pkg/sql/opt/testutils/testcat/create_index.go +++ b/pkg/sql/opt/testutils/testcat/create_index.go @@ -21,13 +21,17 @@ func (tc *Catalog) CreateIndex(stmt *tree.CreateIndex, version descpb.IndexDescr tn := stmt.Table // Update the table name to include catalog and schema if not provided. tc.qualifyTableName(&tn) - tab := tc.Table(&tn) - - for _, idx := range tab.Indexes { - in := stmt.Name.String() - if idx.IdxName == in { - panic(errors.Newf(`relation "%s" already exists`, in)) + tab, err := tc.LookupTable(&tn) + var view *View + if err == nil { + for _, idx := range tab.Indexes { + in := stmt.Name.String() + if idx.IdxName == in { + panic(errors.Newf(`relation "%s" already exists`, in)) + } } + } else { + view = tc.View(&tn) } // Convert stmt to a tree.IndexTableDef so that Table.addIndex can be used @@ -48,5 +52,9 @@ func (tc *Catalog) CreateIndex(stmt *tree.CreateIndex, version descpb.IndexDescr idxType = uniqueIndex } - tab.addIndexWithVersion(indexTableDef, idxType, version) + if tab != nil { + tab.addIndexWithVersion(indexTableDef, idxType, version) + } else if view != nil { + view.addIndex(indexTableDef) + } } diff --git a/pkg/sql/opt/testutils/testcat/create_view.go b/pkg/sql/opt/testutils/testcat/create_view.go index fd85a75ce68b..5a04487682a0 100644 --- a/pkg/sql/opt/testutils/testcat/create_view.go +++ b/pkg/sql/opt/testutils/testcat/create_view.go @@ -33,3 +33,8 @@ func (tc *Catalog) CreateView(stmt *tree.CreateView) *View { return view } + +func (*View) addIndex(stmt *tree.IndexTableDef) { + // TODO(cucaroach): implement + panic("view indexes are not supported by the test catalog") +} diff --git a/pkg/sql/opt/testutils/testcat/test_catalog.go b/pkg/sql/opt/testutils/testcat/test_catalog.go index 045b6a1102ba..7ac06fc34419 100644 --- a/pkg/sql/opt/testutils/testcat/test_catalog.go +++ b/pkg/sql/opt/testutils/testcat/test_catalog.go @@ -343,6 +343,20 @@ func (tc *Catalog) Table(name *tree.TableName) *Table { "\"%q\" is not a table", tree.ErrString(name))) } +// LookupTable returns the test table that was previously added with the given +// name but returns an error if the name does not exist instead of panicking. +func (tc *Catalog) LookupTable(name *tree.TableName) (*Table, error) { + ds, _, err := tc.ResolveDataSource(context.TODO(), cat.Flags{}, name) + if err != nil { + return nil, err + } + if tab, ok := ds.(*Table); ok { + return tab, nil + } + return nil, pgerror.Newf(pgcode.WrongObjectType, + "\"%q\" is not a table", tree.ErrString(name)) +} + // Tables returns a list of all tables added to the test catalog. func (tc *Catalog) Tables() []*Table { tables := make([]*Table, 0, len(tc.testSchema.dataSources)) @@ -1183,6 +1197,7 @@ type TableStat struct { evalCtx *eval.Context histogram []cat.HistogramBucket histogramType *types.T + tc *Catalog } var _ cat.TableStatistic = &TableStat{} @@ -1243,7 +1258,10 @@ func (ts *TableStat) Histogram() []cat.HistogramBucket { if err != nil { panic(err) } - colType := tree.MustBeStaticallyKnownType(colTypeRef) + colType, err := tree.ResolveType(context.Background(), colTypeRef, ts.tc) + if err != nil { + return nil + } var offset int if ts.js.NullCount > 0 {