Skip to content

Commit

Permalink
sql: add regproc support for UDF
Browse files Browse the repository at this point in the history
Release note (sql change): Previously, the `::regproc` casting
only supported builtin functions. Now it's extened to support
user-defined functions as well.
  • Loading branch information
chengxiong-ruan committed Aug 5, 2022
1 parent d1cdc47 commit 3812048
Show file tree
Hide file tree
Showing 13 changed files with 91 additions and 30 deletions.
4 changes: 2 additions & 2 deletions pkg/ccl/changefeedccl/cdceval/func_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ func (rs *CDCFunctionResolver) WrapFunction(name string) (*tree.ResolvedFunction
// ResolveFunctionByOID implements FunctionReferenceResolver interface.
func (rs *CDCFunctionResolver) ResolveFunctionByOID(
ctx context.Context, oid oid.Oid,
) (*tree.Overload, error) {
) (string, *tree.Overload, error) {
// CDC doesn't support user defined function yet, so there's no need to
// resolve function by OID.
return nil, errors.AssertionFailedf("unimplemented yet")
return "", nil, errors.AssertionFailedf("unimplemented yet")
}
14 changes: 14 additions & 0 deletions pkg/sql/faketreeeval/evalctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,20 @@ func (ep *DummyEvalPlanner) IsActive(_ context.Context, _ clusterversion.Key) bo
return true
}

// ResolveFunction implements FunctionReferenceResolver interface.
func (ep *DummyEvalPlanner) ResolveFunction(
ctx context.Context, name *tree.UnresolvedName, path tree.SearchPath,
) (*tree.ResolvedFunctionDefinition, error) {
return nil, errors.AssertionFailedf("ResolveFunction unimplemented")
}

// ResolveFunctionByOID implements FunctionReferenceResolver interface.
func (ep *DummyEvalPlanner) ResolveFunctionByOID(
ctx context.Context, oid oid.Oid,
) (string, *tree.Overload, error) {
return "", nil, errors.AssertionFailedf("ResolveFunctionByOID unimplemented")
}

// DummyPrivilegedAccessor implements the tree.PrivilegedAccessor interface by returning errors.
type DummyPrivilegedAccessor struct{}

Expand Down
8 changes: 4 additions & 4 deletions pkg/sql/function_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ CREATE FUNCTION f() RETURNS t IMMUTABLE LANGUAGE SQL AS $$ SELECT a, b, c FROM t
require.Equal(t, types.TupleFamily, funcDef.Overloads[2].ReturnType([]tree.TypedExpr{}).Family())
require.NotZero(t, funcDef.Overloads[2].ReturnType([]tree.TypedExpr{}).TypeMeta)

overload, err := funcResolver.ResolveFunctionByOID(ctx, funcDef.Overloads[0].Oid)
_, overload, err := funcResolver.ResolveFunctionByOID(ctx, funcDef.Overloads[0].Oid)
require.NoError(t, err)
require.Equal(t, `SELECT a FROM defaultdb.public.t;
SELECT b FROM defaultdb.public.t@t_idx_b;
Expand All @@ -131,15 +131,15 @@ SELECT nextval(105:::REGCLASS);`, overload.Body)
require.Equal(t, types.EnumFamily, overload.Types.Types()[0].Family())
require.Equal(t, types.Int, overload.ReturnType([]tree.TypedExpr{}))

overload, err = funcResolver.ResolveFunctionByOID(ctx, funcDef.Overloads[1].Oid)
_, overload, err = funcResolver.ResolveFunctionByOID(ctx, funcDef.Overloads[1].Oid)
require.NoError(t, err)
require.Equal(t, `SELECT 1;`, overload.Body)
require.True(t, overload.IsUDF)
require.False(t, overload.UDFContainsOnlySignature)
require.Equal(t, 0, len(overload.Types.Types()))
require.Equal(t, types.Void, overload.ReturnType([]tree.TypedExpr{}))

overload, err = funcResolver.ResolveFunctionByOID(ctx, funcDef.Overloads[2].Oid)
_, overload, err = funcResolver.ResolveFunctionByOID(ctx, funcDef.Overloads[2].Oid)
require.NoError(t, err)
require.Equal(t, `SELECT a, b, c FROM defaultdb.public.t;`, overload.Body)
require.True(t, overload.IsUDF)
Expand Down Expand Up @@ -257,7 +257,7 @@ CREATE FUNCTION sc1.lower() RETURNS INT IMMUTABLE LANGUAGE SQL AS $$ SELECT 3 $$
bodies := make([]string, len(funcDef.Overloads))
schemas := make([]string, len(funcDef.Overloads))
for i, o := range funcDef.Overloads {
overload, err := funcResolver.ResolveFunctionByOID(ctx, o.Oid)
_, overload, err := funcResolver.ResolveFunctionByOID(ctx, o.Oid)
require.NoError(t, err)
bodies[i] = overload.Body
schemas[i] = o.Schema
Expand Down
48 changes: 48 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/udf
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,51 @@ $$

statement ok
SET search_path = public;

subtest udf_regproc

query T
SELECT 'proc_implicit'::REGPROC;
----
proc_implicit

query I
SELECT 'proc_implicit'::REGPROC::INT;
----
100117

query T
SELECT '100117'::REGPROC;
----
proc_implicit

query T
SELECT 'sc.proc_f_2'::REGPROC;
----
proc_f_2

query I
SELECT 'sc.proc_f_2'::REGPROC::INT;
----
100119

statement error pq: unknown function: no_such_func()
SELECT 'no_such_func'::REGPROC;

statement error pq: more than one function named 'proc_f'
SELECT 'proc_f'::REGPROC;

query T
SELECT 100117::regproc;
----
proc_implicit

query I
SELECT 100117::regproc::INT;
----
100117

query T
SELECT 999999::regproc;
----
999999
2 changes: 1 addition & 1 deletion pkg/sql/opt/cat/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ type Catalog interface {
) (*tree.ResolvedFunctionDefinition, error)

// ResolveFunctionByOID resolves a function overload by OID.
ResolveFunctionByOID(ctx context.Context, oid oid.Oid) (*tree.Overload, error)
ResolveFunctionByOID(ctx context.Context, oid oid.Oid) (string, *tree.Overload, error)

// CheckPrivilege verifies that the current user has the given privilege on
// the given catalog object. If not, then CheckPrivilege returns an error.
Expand Down
6 changes: 4 additions & 2 deletions pkg/sql/opt/testutils/testcat/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ func (tc *Catalog) ResolveFunction(
}

// ResolveFunctionByOID part of the tree.FunctionReferenceResolver interface.
func (tc *Catalog) ResolveFunctionByOID(ctx context.Context, oid oid.Oid) (*tree.Overload, error) {
return nil, errors.AssertionFailedf("ResolveFunctionByOID not supported in test catalog")
func (tc *Catalog) ResolveFunctionByOID(
ctx context.Context, oid oid.Oid,
) (string, *tree.Overload, error) {
return "", nil, errors.AssertionFailedf("ResolveFunctionByOID not supported in test catalog")
}

// CreateFunction handles the CREATE FUNCTION statement.
Expand Down
2 changes: 1 addition & 1 deletion pkg/sql/opt_catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ func (oc *optCatalog) ResolveFunction(

func (oc *optCatalog) ResolveFunctionByOID(
ctx context.Context, oid oid.Oid,
) (*tree.Overload, error) {
) (string, *tree.Overload, error) {
return oc.planner.ResolveFunctionByOID(ctx, oid)
}

Expand Down
14 changes: 7 additions & 7 deletions pkg/sql/schema_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,16 +441,16 @@ func (sr *schemaResolver) ResolveFunction(

func (sr *schemaResolver) ResolveFunctionByOID(
ctx context.Context, oid oid.Oid,
) (*tree.Overload, error) {
) (name string, fn *tree.Overload, err error) {
if !funcdesc.IsOIDUserDefinedFunc(oid) {
name, ok := tree.OidToBuiltinName[oid]
if !ok {
return nil, pgerror.Newf(pgcode.UndefinedFunction, "function %d not found", oid)
return "", nil, pgerror.Newf(pgcode.UndefinedFunction, "function %d not found", oid)
}
funcDef := tree.FunDefs[name]
for _, o := range funcDef.Definition {
if o.Oid == oid {
return o, nil
return funcDef.Name, o, nil
}
}
}
Expand All @@ -459,17 +459,17 @@ func (sr *schemaResolver) ResolveFunctionByOID(
flags.AvoidLeased = sr.skipDescriptorCache
descID, err := funcdesc.UserDefinedFunctionOIDToID(oid)
if err != nil {
return nil, err
return "", nil, err
}
funcDesc, err := sr.descCollection.GetImmutableFunctionByID(ctx, sr.txn, descID, flags)
if err != nil {
return nil, err
return "", nil, err
}
ret, err := funcDesc.ToOverload()
if err != nil {
return nil, err
return "", nil, err
}
return ret, nil
return funcDesc.GetName(), ret, nil
}

// NewSkippingCacheSchemaResolver constructs a schemaResolver which always skip
Expand Down
9 changes: 4 additions & 5 deletions pkg/sql/sem/eval/cast.go
Original file line number Diff line number Diff line change
Expand Up @@ -910,7 +910,7 @@ func performCastWithoutPrecisionTruncation(
// performIntToOidCast casts the input integer to the OID type given by the
// input types.T.
func performIntToOidCast(
ctx context.Context, res TypeResolver, t *types.T, v tree.DInt,
ctx context.Context, res Planner, t *types.T, v tree.DInt,
) (tree.Datum, error) {
// OIDs are always unsigned 32-bit integers. Some languages, like Java,
// store OIDs as signed 32-bit integers, so we implement the cast
Expand Down Expand Up @@ -940,13 +940,12 @@ func performIntToOidCast(
return tree.NewDOidWithTypeAndName(o, t, name), nil

case oid.T_regproc, oid.T_regprocedure:
// Mapping an dOid to a regproc is easy: we have a hardcoded map.
name, ok := tree.OidToBuiltinName[o]
if !ok {
name, _, err := res.ResolveFunctionByOID(ctx, oid.Oid(v))
if err != nil {
if v == 0 {
return tree.WrapAsZeroOid(t), nil
}
return tree.NewDOidWithType(o, t), nil
return tree.NewDOidWithType(o, t), nil //nolint:returnerrcheck
}
return tree.NewDOidWithTypeAndName(o, t, name), nil

Expand Down
1 change: 1 addition & 0 deletions pkg/sql/sem/eval/deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ type TypeResolver interface {
type Planner interface {
DatabaseCatalog
TypeResolver
tree.FunctionReferenceResolver

// ExecutorConfig returns *ExecutorConfig
ExecutorConfig() interface{}
Expand Down
6 changes: 1 addition & 5 deletions pkg/sql/sem/eval/parse_doid.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,7 @@ func ParseDOid(ctx *Context, s string, t *types.T) (*tree.DOid, error) {
for i := 0; i < len(substrs); i++ {
name.Parts[i] = substrs[len(substrs)-1-i]
}
fn, err := name.ToFunctionName()
if err != nil {
return nil, err
}
funcDef, err := tree.GetBuiltinFuncDefinitionOrFail(fn, &ctx.SessionData().SearchPath)
funcDef, err := ctx.Planner.ResolveFunction(ctx.Ctx(), &name, &ctx.SessionData().SearchPath)
if err != nil {
return nil, err
}
Expand Down
5 changes: 3 additions & 2 deletions pkg/sql/sem/tree/function_name.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ type FunctionReferenceResolver interface {
) (*ResolvedFunctionDefinition, error)

// ResolveFunctionByOID looks up a function overload by using a given oid.
// Error is thrown if there is no function with the same oid.
// Function name is returned together with the overload. Error is thrown if
// there is no function with the same oid.
ResolveFunctionByOID(
ctx context.Context, oid oid.Oid,
) (*Overload, error)
) (string, *Overload, error)
}

// ResolvableFunctionReference implements the editable reference call of a
Expand Down
2 changes: 1 addition & 1 deletion pkg/sql/sem/tree/type_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -1160,7 +1160,7 @@ func (expr *FuncExpr) TypeCheck(
"%s()", errors.Safe(def.Name))
}
if resolver != nil && overloadImpl.UDFContainsOnlySignature {
overloadImpl, err = resolver.ResolveFunctionByOID(ctx, overloadImpl.Oid)
_, overloadImpl, err = resolver.ResolveFunctionByOID(ctx, overloadImpl.Oid)
if err != nil {
return nil, err
}
Expand Down

0 comments on commit 3812048

Please sign in to comment.