diff --git a/pkg/sql/BUILD.bazel b/pkg/sql/BUILD.bazel index 1756a415abf0..91c0649a0dc7 100644 --- a/pkg/sql/BUILD.bazel +++ b/pkg/sql/BUILD.bazel @@ -612,6 +612,7 @@ go_test( "copy_test.go", "copy_to_test.go", "crdb_internal_test.go", + "create_as_test.go", "create_function_test.go", "create_stats_test.go", "create_test.go", diff --git a/pkg/sql/create_as_test.go b/pkg/sql/create_as_test.go new file mode 100644 index 000000000000..ebbd3c3418e9 --- /dev/null +++ b/pkg/sql/create_as_test.go @@ -0,0 +1,126 @@ +// Copyright 2023 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package sql + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/cockroachdb/cockroach/pkg/base" + "github.com/cockroachdb/cockroach/pkg/sql/parser" + "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" + "github.com/cockroachdb/cockroach/pkg/sql/types" + "github.com/cockroachdb/cockroach/pkg/testutils/serverutils" + "github.com/cockroachdb/cockroach/pkg/testutils/sqlutils" + "github.com/cockroachdb/cockroach/pkg/util/leaktest" + "github.com/stretchr/testify/require" +) + +// TestCreateAsVTable verifies that all vtables can be used as the source of +// CREATE TABLE AS and CREATE MATERIALIZED VIEW AS. +func TestCreateAsVTable(t *testing.T) { + defer leaktest.AfterTest(t)() + + // These are the vtables that need to be fixed. + // The map should be empty if all vtables are supported. + brokenTables := map[string]struct{}{ + // TODO(sql-foundations): Fix nil pointer dereference. + // See https://github.com/cockroachdb/cockroach/issues/106166. + `pg_catalog.pg_prepared_statements`: {}, + // TODO(sql-foundations): Fix nil pointer dereference. + // See https://github.com/cockroachdb/cockroach/issues/106167. + `pg_catalog.pg_cursors`: {}, + // TODO(sql-foundations): Fix nil pointer dereference. + // See https://github.com/cockroachdb/cockroach/issues/106168. + `"".crdb_internal.create_statements`: {}, + } + + ctx := context.Background() + testCluster := serverutils.StartNewTestCluster(t, 1, base.TestClusterArgs{}) + defer testCluster.Stopper().Stop(ctx) + sqlRunner := sqlutils.MakeSQLRunner(testCluster.ServerConn(0)) + var p parser.Parser + + i := 0 + for _, vSchema := range virtualSchemas { + for _, vSchemaDef := range vSchema.tableDefs { + if vSchemaDef.isUnimplemented() { + continue + } + + var name tree.TableName + var ctasColumns []string + schema := vSchemaDef.getSchema() + statements, err := p.Parse(schema) + require.NoErrorf(t, err, schema) + require.Lenf(t, statements, 1, schema) + switch stmt := statements[0].AST.(type) { + case *tree.CreateTable: + name = stmt.Table + for _, def := range stmt.Defs { + if colDef, ok := def.(*tree.ColumnTableDef); ok { + if colDef.Hidden { + continue + } + // Filter out vector columns to prevent error in CTAS: + // "VECTOR column types are unsupported". + if colDef.Type == types.Int2Vector || colDef.Type == types.OidVector { + continue + } + ctasColumns = append(ctasColumns, colDef.Name.String()) + } + } + case *tree.CreateView: + name = stmt.Name + ctasColumns = []string{"*"} + default: + require.Failf(t, "missing case", "unexpected type %T for schema %s", stmt, schema) + } + + fqName := name.FQString() + if _, ok := brokenTables[fqName]; ok { + continue + } + + // Filter by trace_id to prevent error when selecting from + // crdb_internal.cluster_inflight_traces: + // "pq: a trace_id value needs to be specified". + var where string + if fqName == `"".crdb_internal.cluster_inflight_traces` { + where = " WHERE trace_id = 1" + } + + createTableStmt := fmt.Sprintf( + "CREATE TABLE test_table_%d AS SELECT %s FROM %s%s", + i, strings.Join(ctasColumns, ", "), fqName, where, + ) + sqlRunner.Exec(t, createTableStmt) + createViewStmt := fmt.Sprintf( + "CREATE MATERIALIZED VIEW test_view_%d AS SELECT * FROM %s%s", + i, fqName, where, + ) + sqlRunner.Exec(t, createViewStmt) + i++ + } + } + + waitForJobsSuccess(t, sqlRunner) +} + +func waitForJobsSuccess(t *testing.T, sqlRunner *sqlutils.SQLRunner) { + query := `SELECT job_id, status, error, description +FROM [SHOW JOBS] +WHERE job_type IN ('SCHEMA CHANGE', 'NEW SCHEMA CHANGE') +AND status != 'succeeded'` + sqlRunner.CheckQueryResultsRetry(t, query, [][]string{}) +} diff --git a/pkg/sql/sem/tree/table_name.go b/pkg/sql/sem/tree/table_name.go index bbcc40af19c7..8831542cf799 100644 --- a/pkg/sql/sem/tree/table_name.go +++ b/pkg/sql/sem/tree/table_name.go @@ -10,6 +10,8 @@ package tree +import "github.com/cockroachdb/cockroach/pkg/sql/sem/catconstants" + // TableName corresponds to the name of a table in a FROM clause, // INSERT or UPDATE statement, etc. // @@ -46,8 +48,13 @@ func (t *TableName) objectName() {} // schema and catalog names. Suitable for logging, etc. func (t *TableName) FQString() string { ctx := NewFmtCtx(FmtSimple) - ctx.FormatNode(&t.CatalogName) - ctx.WriteByte('.') + schemaName := t.SchemaName.String() + // The pg_catalog and pg_extension schemas cannot be referenced from inside + // an anonymous ("") database. This makes their FQ string always relative. + if schemaName != catconstants.PgCatalogName && schemaName != catconstants.PgExtensionSchemaName { + ctx.FormatNode(&t.CatalogName) + ctx.WriteByte('.') + } ctx.FormatNode(&t.SchemaName) ctx.WriteByte('.') ctx.FormatNode(&t.ObjectName) diff --git a/pkg/testutils/sqlutils/sql_runner.go b/pkg/testutils/sqlutils/sql_runner.go index dbd4e4aa80e5..b9bcb48289d8 100644 --- a/pkg/testutils/sqlutils/sql_runner.go +++ b/pkg/testutils/sqlutils/sql_runner.go @@ -147,17 +147,17 @@ func (sr *SQLRunner) ExecRowsAffected( func (sr *SQLRunner) ExpectErr(t Fataler, errRE string, query string, args ...interface{}) { helperOrNoop(t)() _, err := sr.DB.ExecContext(context.Background(), query, args...) - sr.expectErr(t, err, errRE) + sr.expectErr(t, query, err, errRE) } -func (sr *SQLRunner) expectErr(t Fataler, err error, errRE string) { +func (sr *SQLRunner) expectErr(t Fataler, query string, err error, errRE string) { helperOrNoop(t)() if !testutils.IsError(err, errRE) { s := "nil" if err != nil { s = pgerror.FullError(err) } - t.Fatalf("expected error '%s', got: %s", errRE, s) + t.Fatalf("expected query '%s' error '%s', got: %s", query, errRE, s) } } @@ -169,7 +169,7 @@ func (sr *SQLRunner) ExpectErrWithHint( helperOrNoop(t)() _, err := sr.DB.ExecContext(context.Background(), query, args...) - sr.expectErr(t, err, errRE) + sr.expectErr(t, query, err, errRE) if pqErr := (*pq.Error)(nil); errors.As(err, &pqErr) { matched, merr := regexp.MatchString(hintRE, pqErr.Hint)