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 {