这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

重载 UDx

您可能希望 UDx 接受多个不同的签名(参数集)。例如,您可能希望 UDx 接受以下内容:

  • 一个或多个可选参数。

  • 一个或多个参数(可以是多个数据类型之一)。

  • 完全不同的签名(例如,所有 INTEGER 或所有 VARCHAR)。

可以通过以下方法创建具有此行为的函数:创建分别接受不同的签名(参数的个数和数据类型)的多个工厂类。然后,您可以将单个 SQL 函数名称与所有工厂类相关联。只要每个工厂定义的签名都是唯一的,您就可以使用相同的 SQL 函数名称来引用多个工厂类。当用户调用 UDx 时,Vertica 会将用户所提供的实参的数量和类型与函数的每个工厂类所接受的实参进行匹配。如果有一个匹配,Vertica 将使用它来实例化函数类以处理数据。

多个工厂类可以实例化相同的函数类,所以您可以重复使用能够处理多个参数集的一个函数分类,然后为每个函数签名创建工厂类。如果您需要,您还可以创建多个函数类。

请参阅 C++ 示例:重载 UDxJava 示例:重载 UDx 示例。

1 - C++ 示例:重载 UDx

以下示例代码演示了创建一个将两个或三个整数相加的用户定义标量函数 (UDSF)。Add2or3ints 类已准备好处理两个或三个参数。processBlock() 函数将检查已传入的参数个数,并将两个或三个参数全部相加。此外,如果对此函数发出的调用具有少于 2 个参数或具有多于 3 个参数,此函数将退出并显示错误消息。理论上,这不应当发生,因为如果用户的函数调用与您为函数创建的某一个工厂类上的签名相匹配,则 Vertica 仅调用 UDSF。在实践中,如果您的(或其他人的)工厂类不正确地报告函数类无法处理一组参数,最好执行此健全性检查。

#include "Vertica.h"
using namespace Vertica;
using namespace std;
// a ScalarFunction that accepts two or three
// integers and adds them together.
class Add2or3ints : public Vertica::ScalarFunction
{
public:
    virtual void processBlock(Vertica::ServerInterface &srvInterface,
                              Vertica::BlockReader &arg_reader,
                              Vertica::BlockWriter &res_writer)
    {
        const size_t numCols = arg_reader.getNumCols();

        // Ensure that only two or three parameters are passed in
        if ( numCols < 2 || numCols > 3)
            vt_report_error(0, "Function only accept 2 or 3 arguments, "
                                "but %zu provided", arg_reader.getNumCols());
      // Add two integers together
        do {
            const vint a = arg_reader.getIntRef(0);
            const vint b = arg_reader.getIntRef(1);
            vint c = 0;
        // Check for third argument, add it in if it exists.
            if (numCols == 3)
                c = arg_reader.getIntRef(2);
            res_writer.setInt(a+b+c);
            res_writer.next();
        } while (arg_reader.next());
    }
};
// This factory accepts function calls with two integer arguments.
class Add2intsFactory : public Vertica::ScalarFunctionFactory
{
    virtual Vertica::ScalarFunction *createScalarFunction(Vertica::ServerInterface
                &srvInterface)
    { return vt_createFuncObj(srvInterface.allocator, Add2or3ints); }
    virtual void getPrototype(Vertica::ServerInterface &srvInterface,
                              Vertica::ColumnTypes &argTypes,
                              Vertica::ColumnTypes &returnType)
    {   // Accept 2 integer values
        argTypes.addInt();
        argTypes.addInt();
        returnType.addInt();
    }
};
RegisterFactory(Add2intsFactory);
// This factory defines a function that accepts 3 ints.
class Add3intsFactory : public Vertica::ScalarFunctionFactory
{
    virtual Vertica::ScalarFunction *createScalarFunction(Vertica::ServerInterface
                &srvInterface)
    { return vt_createFuncObj(srvInterface.allocator, Add2or3ints); }
    virtual void getPrototype(Vertica::ServerInterface &srvInterface,
                              Vertica::ColumnTypes &argTypes,
                              Vertica::ColumnTypes &returnType)
    {   // accept 3 integer values
        argTypes.addInt();
        argTypes.addInt();
        argTypes.addInt();
        returnType.addInt();
    }
};
RegisterFactory(Add3intsFactory);

此示例具有两个 ScalarFunctionFactory 类,函数所接受的每个签名(两个整数和三个整数)各一个。除了其 ScalarFunctionFactory::createScalarFunction() 实施都将创建 Add2or3ints 对象之外,这两个工厂类没有其他例外情况。

最后一步是将同一个 SQL 函数名称绑定到这两个工厂类。只要每个工厂的 getPrototype() 实施所定义的签名不相同,您就可以将多个工厂分配给同一个 SQL 函数。

=> CREATE LIBRARY add2or3IntsLib AS '/home/dbadmin/Add2or3Ints.so';
CREATE LIBRARY
=> CREATE FUNCTION add2or3Ints as NAME 'Add2intsFactory' LIBRARY add2or3IntsLib FENCED;
CREATE FUNCTION
=> CREATE FUNCTION add2or3Ints as NAME 'Add3intsFactory' LIBRARY add2or3IntsLib FENCED;
CREATE FUNCTION
=> SELECT add2or3Ints(1,2);
 add2or3Ints
-------------
           3
(1 row)
=> SELECT add2or3Ints(1,2,4);
 add2or3Ints
-------------
           7
(1 row)
=> SELECT add2or3Ints(1,2,3,4); -- Will generate an error
ERROR 3467:  Function add2or3Ints(int, int, int, int) does not exist, or
permission is denied for add2or3Ints(int, int, int, int)
HINT:  No function matches the given name and argument types. You may
need to add explicit type casts

Vertica 在对 add2or3Ints 函数的最终调用的响应中生成了错误消息,因为它无法找到与接受了四个整数实参的 add2or3Ints 关联的工厂类。要进一步扩展 add2or3Ints,您可以创建可接受此签名的另一个工厂类,然后更改 Add2or3ints ScalarFunction 类,或者创建完全不同的类以处理更多整数的相加。但是,添加更多类可以快速接受参数中的每个变体,这具有压倒性优势。在这种情况下,您应考虑创建多态 UDx。

2 - Java 示例:重载 UDx

以下示例代码演示了创建一个将两个或三个整数相加的用户定义标量函数 (UDSF)。Add2or3ints 类已准备好处理两个或三个参数。它将检查已传入的参数个数,并将两个或三个参数全部相加。processBlock() 方法将检查对它发出的调用是否具有少于 2 个参数或具有多于 3 个参数。理论上,这不应当发生,因为如果用户的函数调用与您为函数创建的某一个工厂类上的签名相匹配,则 Vertica 仅调用 UDSF。在实践中,如果您的(或其他人的)工厂类报告函数类接受了它实际上不接受的一组参数,最好执行此健全性检查。

// You need to specify the full package when creating functions based on
// the classes in your library.
package com.mycompany.multiparamexample;
// Import the entire Vertica SDK
import com.vertica.sdk.*;
// This ScalarFunction accepts two or three integer arguments. It tests
// the number of input columns to determine whether to read two or three
// arguments as input.
public class Add2or3ints extends ScalarFunction
{
    @Override
    public void processBlock(ServerInterface srvInterface,
                             BlockReader argReader,
                             BlockWriter resWriter)
                throws UdfException, DestroyInvocation
    {
        // See how many arguments were passed in
        int numCols = argReader.getNumCols();

        // Return an error if less than two or more than 3 aerguments
        // were given. This error only occurs if a Factory class that
        // accepts the wrong number of arguments instantiates this
        // class.
        if (numCols < 2 || numCols > 3) {
            throw new UdfException(0,
                "Must supply 2 or 3 integer arguments");
        }

        // Process all of the rows of input.
        do {
            // Get the first two integer arguments from the BlockReader
            long a = argReader.getLong(0);
            long b = argReader.getLong(1);

            // Assume no third argument.
            long c = 0;

            // Get third argument value if it exists
            if (numCols == 3) {
                c = argReader.getLong(2);
            }

            // Process the arguments and come up with a result. For this
            // example, just add the three arguments together.
            long result = a+b+c;

            // Write the integer output value.
            resWriter.setLong(result);

            // Advance the output BlocKWriter to the next row.
            resWriter.next();

            // Continue processing input rows until there are no more.
        } while (argReader.next());
    }
}

Add2ints 类和 Add2or3ints 类之间的主要区别在于是否包含一个通过调用 BlockReader.getNumCols() 来获取参数个数的节。此类还会测试它从 Vertica 收到的列数,以确保该列数处于它准备好处理的范围内。仅当所创建的 ScalarFunctionFactorygetPrototype() 方法定义了接受少于两个参数或接受多于三个参数的签名时,此测试才会失败。在此简单示例中,实在没有必要执行此测试,但对于更复杂的类,最好测试 Vertica 传递到函数类的列数和数据类型。

do 循环中,如果 Vertica 向 Add2or3ints 类发送两个输入列,则此类会使用默认值(零)。否则,此类会检索第三个值,并将该值与另外两个值相加。您自己的类需要对缺失的输入列使用默认值,或者需要将其处理更改为某种其他方法以处理可变列。

您必须在单独的源文件中定义函数类,而不能将函数类定义为工厂类之一的内部类,因为 Java 不允许从内部类的所属类的外部将内部类实例化。您的工厂类必须可由多个工厂类进行实例化。

创建了一个或多个函数类之后,应为您希望函数类处理的每个签名创建工厂类。这些工厂类可以调用不同的函数类,或者也可以全部调用已准备好接受多个参数集的同一个类。

以下示例的 createScalarFunction() 方法实例化 Add2or3ints 类的成员。

// You will need to specify the full package when creating functions based on
// the classes in your library.
package com.mycompany.multiparamexample;
// Import the entire Vertica SDK
import com.vertica.sdk.*;
public class Add2intsFactory extends ScalarFunctionFactory
{
    @Override
    public void getPrototype(ServerInterface srvInterface,
                             ColumnTypes argTypes,
                             ColumnTypes returnType)
    {
        // Accept two integers as input
        argTypes.addInt();
        argTypes.addInt();
        // writes one integer as output
        returnType.addInt();
    }
    @Override
    public ScalarFunction createScalarFunction(ServerInterface srvInterface)
    {
        // Instantiate the class that can handle either 2 or 3 integers.
        return new Add2or3ints();
    }
}

以下 ScalarFunctionFactory 子类接受三个整数作为输入。此外,它还会将 Add2or3ints 类的成员实例化以处理函数调用:

// You will need to specify the full package when creating functions based on
// the classes in your library.
package com.mycompany.multiparamexample;
// Import the entire Vertica SDK
import com.vertica.sdk.*;
public class Add3intsFactory extends ScalarFunctionFactory
{
    @Override
    public void getPrototype(ServerInterface srvInterface,
                             ColumnTypes argTypes,
                             ColumnTypes returnType)
    {
        // Accepts three integers as input
        argTypes.addInt();
        argTypes.addInt();
        argTypes.addInt();
        // Returns a single integer
        returnType.addInt();
    }
    @Override
    public ScalarFunction createScalarFunction(ServerInterface srvInterface)
    {
        // Instantiates the Add2or3ints ScalarFunction class, which is able to
        // handle eitehr 2 or 3 integers as arguments.
        return new Add2or3ints();
    }
}

必须将工厂类及其调用的一个或多个函数类打包到同一个 JAR 文件中(有关详细信息,请参阅编译并打包 Java 库)。如果数据库群集中的主机上安装了 JDK,则您可以使用以下命令来编译和打包该示例:

$ cd pathToJavaProject$ javac -classpath /opt/vertica/bin/VerticaSDK.jar \
> com/mycompany/multiparamexample/*.java
$ jar -cvf Add2or3intslib.jar com/vertica/sdk/BuildInfo.class \
> com/mycompany/multiparamexample/*.class
added manifest
adding: com/vertica/sdk/BuildInfo.class(in = 1202) (out= 689)(deflated 42%)
adding: com/mycompany/multiparamexample/Add2intsFactory.class(in = 677) (out= 366)(deflated 45%)
adding: com/mycompany/multiparamexample/Add2or3ints.class(in = 919) (out= 601)(deflated 34%)
adding: com/mycompany/multiparamexample/Add3intsFactory.class(in = 685) (out= 369)(deflated 46%)

打包了已重载的 UDx 之后,应以常规 UDx 的相同方法部署已重载的 UDx,唯一的例外是应多次(每个工厂类各一次)使用 CREATE FUNCTION 语句来定义函数。

=> CREATE LIBRARY add2or3intslib as '/home/dbadmin/Add2or3intslib.jar'
-> language 'Java';
CREATE LIBRARY
=> CREATE FUNCTION add2or3ints as LANGUAGE 'Java' NAME 'com.mycompany.multiparamexample.Add2intsFactory' LIBRARY add2or3intslib;
CREATE FUNCTION
=> CREATE FUNCTION add2or3ints as LANGUAGE 'Java' NAME 'com.mycompany.multiparamexample.Add3intsFactory' LIBRARY add2or3intslib;
CREATE FUNCTION

调用已重载的函数的方法与调用任何其他函数相同。

=> SELECT add2or3ints(2,3);
 add2or3ints
-------------
           5
(1 row)
=> SELECT add2or3ints(2,3,4);
 add2or3ints
-------------
           9
(1 row)
=> SELECT add2or3ints(2,3,4,5);
ERROR 3457:  Function add2or3ints(int, int, int, int) does not exist, or permission is denied for add2or3ints(int, int, int, int)
HINT:  No function matches the given name and argument types. You may need to add explicit type casts

最后一个错误由 Vertica 生成,而非 UDx 代码。如果无法找到签名与函数调用的签名匹配的工厂类,它会返回错误。

如果您希望函数接受有限的一组潜在参数,则创建已重载的 UDx 很有用。如果要创建更灵活的参数,您可以创建多态函数。