Skip to content

Commit

Permalink
sql: support casts from index name to regclass
Browse files Browse the repository at this point in the history
Release note (sql change): Casts from index name to regclass are now
supported. Previously, only table names could be cast to regclass.
  • Loading branch information
rafiss committed Dec 2, 2022
1 parent 6d7ccb8 commit 6ae31e6
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 3 deletions.
111 changes: 111 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/pgoidtype
Original file line number Diff line number Diff line change
Expand Up @@ -579,3 +579,114 @@ SELECT 1:::OID >= -2147483649:::INT8

query error OID out of range: -2147483649
SELECT -2147483649:::INT8 >= 1:::OID

# Test that we can cast index names to a regclass, and vice-versa.

statement ok
CREATE TABLE table_with_indexes (a INT PRIMARY key, b INT)

query TT
SELECT relname, oid::regclass::string from pg_class where oid='table_with_indexes_pkey'::regclass
----
table_with_indexes_pkey table_with_indexes_pkey

statement ok
CREATE INDEX my_b_index ON table_with_indexes(b)

query TT
SELECT relname, oid::regclass::string from pg_class where oid='my_b_index'::regclass
----
my_b_index my_b_index

# Test that we can cast table and index names from different schemas.

statement ok
CREATE SCHEMA other_schema

statement ok
CREATE TABLE other_schema.table_with_indexes (a INT PRIMARY key, b INT)

statement ok
CREATE INDEX my_b_index ON other_schema.table_with_indexes(b)

query TTT
SELECT c.relname, c.oid::regclass::string, n.nspname from pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.oid='other_schema.table_with_indexes'::regclass
----
table_with_indexes table_with_indexes other_schema

query TTT
SELECT c.relname, c.oid::regclass::string, n.nspname from pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.oid='other_schema.table_with_indexes_pkey'::regclass
----
table_with_indexes_pkey table_with_indexes_pkey other_schema

query TTT
SELECT c.relname, c.oid::regclass::string, n.nspname from pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.oid='other_schema.my_b_index'::regclass
----
my_b_index my_b_index other_schema

# Changing the search_path should influence what gets resolved while casting.

statement ok
SET search_path = other_schema, public

query TTT
SELECT c.relname, c.oid::regclass::string, n.nspname from pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.oid='table_with_indexes'::regclass
----
table_with_indexes table_with_indexes other_schema

query TTT
SELECT c.relname, c.oid::regclass::string, n.nspname from pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.oid='table_with_indexes_pkey'::regclass
----
table_with_indexes_pkey table_with_indexes_pkey other_schema

query TTT
SELECT c.relname, c.oid::regclass::string, n.nspname from pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.oid='my_b_index'::regclass
----
my_b_index my_b_index other_schema

statement ok
SET search_path = public, other_schema

query TTT
SELECT c.relname, c.oid::regclass::string, n.nspname from pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.oid='table_with_indexes'::regclass
----
table_with_indexes table_with_indexes public

query TTT
SELECT c.relname, c.oid::regclass::string, n.nspname from pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.oid='table_with_indexes_pkey'::regclass
----
table_with_indexes_pkey table_with_indexes_pkey public

query TTT
SELECT c.relname, c.oid::regclass::string, n.nspname from pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.oid='my_b_index'::regclass
----
my_b_index my_b_index public

statement ok
RESET search_path

# Check that we can cast table names from pg_catalog also.
query TTT
SELECT c.relname, c.oid::regclass::string, n.nspname from pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.oid='pg_type'::regclass
----
pg_type pg_type pg_catalog
1 change: 1 addition & 0 deletions pkg/sql/sem/eval/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ go_library(
"//pkg/sql/sem/tree/treewindow",
"//pkg/sql/sessiondata",
"//pkg/sql/sessiondatapb",
"//pkg/sql/sqlerrors",
"//pkg/sql/sqlliveness",
"//pkg/sql/sqltelemetry",
"//pkg/sql/types",
Expand Down
65 changes: 62 additions & 3 deletions pkg/sql/sem/eval/parse_doid.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package eval

import (
"context"
"fmt"
"regexp"
"strings"

Expand All @@ -20,6 +21,8 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
"github.com/cockroachdb/cockroach/pkg/sql/sem/catid"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/sessiondata"
"github.com/cockroachdb/cockroach/pkg/sql/sqlerrors"
"github.com/cockroachdb/cockroach/pkg/sql/types"
"github.com/cockroachdb/errors"
"github.com/lib/pq/oid"
Expand Down Expand Up @@ -178,19 +181,75 @@ func ParseDOid(ctx context.Context, evalCtx *Context, s string, t *types.T) (*tr
if err != nil {
return nil, err
}
id, err := evalCtx.Planner.ResolveTableName(ctx, &tn)
if id, err := evalCtx.Planner.ResolveTableName(ctx, &tn); err == nil {
// tree.ID is a uint32, so this type conversion is safe.
return tree.NewDOidWithTypeAndName(oid.Oid(id), t, tn.ObjectName.String()), nil
} else if pgerror.GetPGCode(err) != pgcode.UndefinedTable {
return nil, err
}
// If the above resulted in an UndefinedTable error, then we can try
// searching for an index with this name,
oidRes, err := indexNameToOID(ctx, evalCtx, tn)
if err != nil {
return nil, err
}
// tree.ID is a uint32, so this type conversion is safe.
return tree.NewDOidWithTypeAndName(oid.Oid(id), t, tn.ObjectName.String()), nil
return tree.NewDOidWithTypeAndName(oidRes, t, tn.ObjectName.String()), nil

default:
d, _ /* errSafeToIgnore */, err := evalCtx.Planner.ResolveOIDFromString(ctx, t, tree.NewDString(s))
return d, err
}
}

// indexNameToOID finds the OID for the given index. If the name is qualified,
// then the index must belong to that schema. Otherwise, the schemas are
// searched in order of the search_path.
func indexNameToOID(ctx context.Context, evalCtx *Context, tn tree.TableName) (oid.Oid, error) {
query := `SELECT c.oid FROM %[1]spg_catalog.pg_class AS c
JOIN %[1]spg_catalog.pg_namespace AS n ON c.relnamespace = n.oid
WHERE c.relname = $1
AND n.nspname = $2
LIMIT 1`
args := []interface{}{tn.Object(), tn.Schema()}
if !tn.ExplicitSchema {
// If there is no explicit schema, then we need a different query that
// looks for the object name for each schema in the search_path. Choose
// the first match in the order of the search_path array. There is an
// unused $2 placeholder in the query so that the call to QueryRow can
// be consolidated.
query = `WITH
current_schemas AS (
SELECT * FROM unnest(current_schemas(true)) WITH ORDINALITY AS scname
)
SELECT c.oid
FROM %[1]spg_catalog.pg_class AS c
JOIN %[1]spg_catalog.pg_namespace AS n ON c.relnamespace = n.oid
JOIN current_schemas AS cs ON cs.scname = n.nspname
WHERE c.relname = $1
ORDER BY cs.ordinality ASC
LIMIT 1`
args = []interface{}{tn.Object()}
}
catalogPrefix := ""
if tn.ExplicitCatalog {
catalogPrefix = tn.CatalogName.String() + "."
}
row, err := evalCtx.Planner.QueryRowEx(
ctx,
"regclass-cast",
sessiondata.NoSessionDataOverride,
fmt.Sprintf(query, catalogPrefix),
args...,
)
if err != nil {
return 0, err
}
if row == nil {
return 0, sqlerrors.NewUndefinedRelationError(&tn)
}
return tree.MustBeDOid(row[0]).Oid, nil
}

// castStringToRegClassTableName normalizes a TableName from a string.
func castStringToRegClassTableName(s string) (tree.TableName, error) {
components, err := splitIdentifierList(s)
Expand Down

0 comments on commit 6ae31e6

Please sign in to comment.