diff --git a/pkg/sql/data_source.go b/pkg/sql/data_source.go index 33960870e89e..9c0fe6a24950 100644 --- a/pkg/sql/data_source.go +++ b/pkg/sql/data_source.go @@ -506,19 +506,11 @@ func expandStar( } } case *tree.AllColumnsSelector: - tn, err := tree.NormalizeTableName(&sel.TableName) - if err != nil { - return nil, nil, err - } resolver := sqlbase.ColumnResolver{Sources: src} - numRes, _, _, err := resolver.FindSourceMatchingName(ctx, tn) + _, _, err := sel.Resolve(ctx, &resolver) if err != nil { return nil, nil, err } - if numRes == tree.NoResults { - return nil, nil, pgerror.NewErrorf(pgerror.CodeUndefinedColumnError, - "no data source named %q", tree.ErrString(&tn)) - } ds := src[resolver.ResolverState.SrcIdx] colSet := ds.SourceAliases[resolver.ResolverState.ColSetIdx].ColumnSet for i, ok := colSet.Next(0); ok; i, ok = colSet.Next(i + 1) { diff --git a/pkg/sql/logictest/testdata/logic_test/insert b/pkg/sql/logictest/testdata/logic_test/insert index 15f6b9888a6f..95e318187c7b 100644 --- a/pkg/sql/logictest/testdata/logic_test/insert +++ b/pkg/sql/logictest/testdata/logic_test/insert @@ -511,7 +511,7 @@ INSERT INTO return AS r VALUES (5, 6) statement ok INSERT INTO return VALUES (5, 6) RETURNING test.return.a -statement error no data source named "x" +statement error no data source matches pattern: x.\* INSERT INTO return VALUES (1, 2) RETURNING x.*[1] statement error column name "x" not found diff --git a/pkg/sql/logictest/testdata/logic_test/select b/pkg/sql/logictest/testdata/logic_test/select index d44b49238400..fd8b44739aa5 100644 --- a/pkg/sql/logictest/testdata/logic_test/select +++ b/pkg/sql/logictest/testdata/logic_test/select @@ -139,7 +139,28 @@ SELECT kv.* FROM kv ---- a NULL -query error no data source named "foo" +# Regression tests for #24169 +query TT +SELECT test.kv.* FROM kv +---- +a NULL + +query TT +SELECT test.public.kv.* FROM kv +---- +a NULL + +query TT +SELECT test.public.kv.* FROM test.kv +---- +a NULL + +query TT +SELECT test.kv.* FROM test.public.kv +---- +a NULL + +query error no data source matches pattern: foo.\* SELECT foo.* FROM kv query error cannot use "\*" without a FROM clause @@ -156,7 +177,7 @@ SELECT 1, * FROM nocols query error "kv.*" cannot be aliased SELECT kv.* AS foo FROM kv -query error no data source named "bar.kv" +query error no data source matches pattern: bar.kv.\* SELECT bar.kv.* FROM kv # Don't panic with invalid names (#8024) diff --git a/pkg/sql/sem/tree/name_resolution.go b/pkg/sql/sem/tree/name_resolution.go index 3ef1397e4472..f3620769fac9 100644 --- a/pkg/sql/sem/tree/name_resolution.go +++ b/pkg/sql/sem/tree/name_resolution.go @@ -186,6 +186,36 @@ type ColumnResolutionResult interface { ColumnResolutionResult() } +// Resolve performs name resolution for a qualified star using a resolver. +func (a *AllColumnsSelector) Resolve( + ctx context.Context, r ColumnItemResolver, +) (srcName *TableName, srcMeta ColumnSourceMeta, err error) { + prefix := makeTableNameFromUnresolvedName(&a.TableName) + + // Is there a data source with this prefix? + var res NumResolutionResults + res, srcName, srcMeta, err = r.FindSourceMatchingName(ctx, prefix) + if err != nil { + return nil, nil, err + } + if res == NoResults && a.TableName.NumParts == 2 { + // No, but name of form db.tbl.*? + // Special rule for compatibility with CockroachDB v1.x: + // search name db.public.tbl.* instead. + prefix.ExplicitCatalog = true + prefix.CatalogName = prefix.SchemaName + prefix.SchemaName = PublicSchemaName + res, srcName, srcMeta, err = r.FindSourceMatchingName(ctx, prefix) + if err != nil { + return nil, nil, err + } + } + if res == NoResults { + return nil, nil, newSourceNotFoundError("no data source matches pattern: %s", a) + } + return srcName, srcMeta, nil +} + // Resolve performs name resolution for a column item using a resolver. func (c *ColumnItem) Resolve( ctx context.Context, r ColumnItemResolver, diff --git a/pkg/sql/sem/tree/name_resolution_test.go b/pkg/sql/sem/tree/name_resolution_test.go index 72525c3f91fa..f99cdc2967f3 100644 --- a/pkg/sql/sem/tree/name_resolution_test.go +++ b/pkg/sql/sem/tree/name_resolution_test.go @@ -390,6 +390,67 @@ func newFakeSource() *fakeSource { } } +func TestResolveQualifiedStar(t *testing.T) { + testCases := []struct { + in string + tnout string + csout string + err string + }{ + {`a.*`, ``, ``, `no data source matches pattern: a.*`}, + {`foo.*`, ``, ``, `ambiguous table name: foo`}, + {`db1.public.foo.*`, `db1.public.foo`, `x`, ``}, + {`db1.foo.*`, `db1.public.foo`, `x`, ``}, + {`dbx.foo.*`, ``, ``, `no data source matches pattern: dbx.foo.*`}, + {`kv.*`, `db1.public.kv`, `k, v`, ``}, + } + fakeFrom := newFakeSource() + for _, tc := range testCases { + t.Run(tc.in, func(t *testing.T) { + fakeFrom.t = t + tnout, csout, err := func() (string, string, error) { + stmt, err := parser.ParseOne(fmt.Sprintf("SELECT %s", tc.in)) + if err != nil { + return "", "", err + } + v := stmt.(*tree.Select).Select.(*tree.SelectClause).Exprs[0].Expr.(tree.VarName) + c, err := v.NormalizeVarName() + if err != nil { + return "", "", err + } + acs, ok := c.(*tree.AllColumnsSelector) + if !ok { + return "", "", fmt.Errorf("var name %s (%T) did not resolve to AllColumnsSelector, found %T instead", + v, v, c) + } + tn, res, err := acs.Resolve(context.Background(), fakeFrom) + if err != nil { + return "", "", err + } + cs, ok := res.(colsRes) + if !ok { + return "", "", fmt.Errorf("fake resolver did not return colsRes, found %T instead", res) + } + nl := tree.NameList(cs) + return tn.String(), nl.String(), nil + }() + if !testutils.IsError(err, tc.err) { + t.Fatalf("%s: expected %s, but found %v", tc.in, tc.err, err) + } + if tc.err != "" { + return + } + + if tc.tnout != tnout { + t.Fatalf("%s: expected tn %s, but found %s", tc.in, tc.tnout, tnout) + } + if tc.csout != csout { + t.Fatalf("%s: expected cs %s, but found %s", tc.in, tc.csout, csout) + } + }) + } +} + func TestResolveColumnItem(t *testing.T) { testCases := []struct { in string