diff --git a/docs/generated/sql/bnf/stmt_block.bnf b/docs/generated/sql/bnf/stmt_block.bnf index dc61b0001606..ded3159051e2 100644 --- a/docs/generated/sql/bnf/stmt_block.bnf +++ b/docs/generated/sql/bnf/stmt_block.bnf @@ -968,11 +968,11 @@ c_expr ::= cast_target ::= typename - | postgres_oid typename ::= simple_typename opt_array_bounds | simple_typename 'ARRAY' + | postgres_oid collation_name ::= unrestricted_name @@ -1314,13 +1314,6 @@ array_subscripts ::= case_expr ::= 'CASE' case_arg when_clause_list case_default 'END' -postgres_oid ::= - 'REGPROC' - | 'REGPROCEDURE' - | 'REGCLASS' - | 'REGTYPE' - | 'REGNAMESPACE' - simple_typename ::= const_typename | bit_with_length @@ -1331,6 +1324,13 @@ opt_array_bounds ::= '[' ']' | +postgres_oid ::= + 'REGPROC' + | 'REGPROCEDURE' + | 'REGCLASS' + | 'REGTYPE' + | 'REGNAMESPACE' + math_op ::= '+' | '-' diff --git a/pkg/sql/logictest/testdata/logic_test/pgoidtype b/pkg/sql/logictest/testdata/logic_test/pgoidtype index 10ca8f306c1a..c675371ae8e2 100644 --- a/pkg/sql/logictest/testdata/logic_test/pgoidtype +++ b/pkg/sql/logictest/testdata/logic_test/pgoidtype @@ -288,3 +288,13 @@ query B SELECT NOT (prorettype::regtype::text = 'foo') AND proretset FROM pg_proc WHERE proretset=false LIMIT 1 ---- false + +query TTTTT +SELECT crdb_internal.create_regtype(10, 'foo'), crdb_internal.create_regclass(10, 'foo'), crdb_internal.create_regproc(10, 'foo'), crdb_internal.create_regprocedure(10, 'foo'), crdb_internal.create_regnamespace(10, 'foo') +---- +foo foo foo foo foo + +query OOOOO +SELECT crdb_internal.create_regtype(10, 'foo')::oid, crdb_internal.create_regclass(10, 'foo')::oid, crdb_internal.create_regproc(10, 'foo')::oid, crdb_internal.create_regprocedure(10, 'foo')::oid, crdb_internal.create_regnamespace(10, 'foo')::oid +---- +10 10 10 10 10 diff --git a/pkg/sql/parser/parse_test.go b/pkg/sql/parser/parse_test.go index 03b61549b269..b209c4867a35 100644 --- a/pkg/sql/parser/parse_test.go +++ b/pkg/sql/parser/parse_test.go @@ -618,6 +618,11 @@ func TestParse(t *testing.T) { {`SELECT '192.168.0.1':::INET`}, {`SELECT INET '192.168.0.1'`}, + {`SELECT 1:::REGTYPE`}, + {`SELECT 1:::REGPROC`}, + {`SELECT 1:::REGCLASS`}, + {`SELECT 1:::REGNAMESPACE`}, + {`SELECT 'a' AS "12345"`}, {`SELECT 'a' AS clnm`}, {`SELECT 'a' AS primary`}, diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index 0ca88ed19629..3e05405ca0e3 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -5808,16 +5808,16 @@ typename: return 1 } } +| postgres_oid + { + $$.val = $1.castTargetType() + } cast_target: typename { $$.val = $1.colType() } -| postgres_oid - { - $$.val = $1.castTargetType() - } opt_array_bounds: // TODO(justin): reintroduce multiple array bounds diff --git a/pkg/sql/sem/builtins/pg_builtins.go b/pkg/sql/sem/builtins/pg_builtins.go index 39c05b7009cb..d00131ff20d6 100644 --- a/pkg/sql/sem/builtins/pg_builtins.go +++ b/pkg/sql/sem/builtins/pg_builtins.go @@ -111,6 +111,12 @@ func initPGBuiltins() { for name, builtin := range makeTypeIOBuiltins("anyarray_", types.AnyArray) { builtins[name] = builtin } + + // Make crdb_internal.create_regfoo builtins. + for _, typ := range []types.TOid{types.RegType, types.RegProc, types.RegProcedure, types.RegClass, types.RegNamespace} { + typName := typ.SQLName() + builtins["crdb_internal.create_"+typName] = makeCreateRegDef(typ) + } } var errUnimplemented = pgerror.NewError(pgerror.CodeFeatureNotSupportedError, "unimplemented") @@ -512,6 +518,22 @@ func evalPrivilegeCheck( return tree.DBoolTrue, nil } +func makeCreateRegDef(typ types.TOid) builtinDefinition { + return makeBuiltin(defProps(), + tree.Overload{ + Types: tree.ArgTypes{ + {"oid", types.Int}, + {"name", types.String}, + }, + ReturnType: tree.FixedReturnType(typ), + Fn: func(_ *tree.EvalContext, d tree.Datums) (tree.Datum, error) { + return tree.NewDOidWithName(tree.MustBeDInt(d[0]), coltypes.OidTypeToColType(typ), string(tree.MustBeDString(d[1]))), nil + }, + Info: notUsableInfo, + }, + ) +} + var pgBuiltins = map[string]builtinDefinition{ // See https://www.postgresql.org/docs/9.6/static/functions-info.html. "pg_backend_pid": makeBuiltin(defProps(), diff --git a/pkg/sql/sem/tree/datum.go b/pkg/sql/sem/tree/datum.go index d03fda1a7c4b..5ed7b5af5d9c 100644 --- a/pkg/sql/sem/tree/datum.go +++ b/pkg/sql/sem/tree/datum.go @@ -3052,6 +3052,16 @@ func NewDOid(d DInt) *DOid { return &oid } +// NewDOidWithName is a helper routine to create a *DOid initialized from a DInt +// and a string. +func NewDOidWithName(d DInt, typ *coltypes.TOid, name string) *DOid { + return &DOid{ + DInt: d, + semanticType: typ, + name: name, + } +} + // AsRegProc changes the input DOid into a regproc with the given name and // returns it. func (d *DOid) AsRegProc(name string) *DOid { @@ -3091,6 +3101,12 @@ func (d *DOid) Format(ctx *FmtCtx) { // roundtrippable. Since in this branch, a DOid is a thin wrapper around // a DInt, I _think_ it's correct to just delegate to the DInt's Format. d.DInt.Format(ctx) + } else if ctx.HasFlags(fmtDisambiguateDatumTypes) { + ctx.Buffer.WriteString("crdb_internal.create_" + strings.ToLower(d.semanticType.Name) + "(") + ctx.Buffer.WriteString(d.DInt.String()) + ctx.Buffer.WriteString(`,'`) + ctx.Buffer.WriteString(d.name) + ctx.Buffer.WriteString(`')`) } else { lex.EncodeSQLStringWithFlags(ctx.Buffer, d.name, lex.EncBareStrings) } diff --git a/pkg/sql/sem/tree/format_test.go b/pkg/sql/sem/tree/format_test.go index 3acffe69a9f0..6a8111d7888c 100644 --- a/pkg/sql/sem/tree/format_test.go +++ b/pkg/sql/sem/tree/format_test.go @@ -21,6 +21,7 @@ import ( "testing" "github.com/cockroachdb/cockroach/pkg/internal/rsg" + "github.com/cockroachdb/cockroach/pkg/sql/coltypes" "github.com/cockroachdb/cockroach/pkg/sql/parser" _ "github.com/cockroachdb/cockroach/pkg/sql/sem/builtins" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" @@ -257,6 +258,39 @@ func TestFormatExpr(t *testing.T) { } } +func TestFormatExpr2(t *testing.T) { + // This tests formatting from an expr AST. Suitable for use if your input + // isn't easily creatable from a string without running an Eval. + testData := []struct { + expr tree.Expr + f tree.FmtFlags + expected string + }{ + {tree.NewDOidWithName(tree.DInt(10), coltypes.RegClass, "foo"), + tree.FmtParsable, `crdb_internal.create_regclass(10,'foo'):::REGCLASS`}, + {tree.NewDOidWithName(tree.DInt(10), coltypes.RegProc, "foo"), + tree.FmtParsable, `crdb_internal.create_regproc(10,'foo'):::REGPROC`}, + {tree.NewDOidWithName(tree.DInt(10), coltypes.RegType, "foo"), + tree.FmtParsable, `crdb_internal.create_regtype(10,'foo'):::REGTYPE`}, + {tree.NewDOidWithName(tree.DInt(10), coltypes.RegNamespace, "foo"), + tree.FmtParsable, `crdb_internal.create_regnamespace(10,'foo'):::REGNAMESPACE`}, + } + + for i, test := range testData { + t.Run(fmt.Sprintf("%d %s", i, test.expr), func(t *testing.T) { + ctx := tree.MakeSemaContext(false) + typeChecked, err := tree.TypeCheck(test.expr, &ctx, types.Any) + if err != nil { + t.Fatal(err) + } + exprStr := tree.AsStringWithFlags(typeChecked, test.f) + if exprStr != test.expected { + t.Fatalf("expected %q, got %q", test.expected, exprStr) + } + }) + } +} + // BenchmarkFormatRandomStatements measures the time needed to format // 1000 random statements. func BenchmarkFormatRandomStatements(b *testing.B) {