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

返回本页常规视图.

实参和返回值

对于除加载 (UDL) 之外的所有 UDx 类型,工厂类会声明关联函数的实参和返回类型。为此,工厂有两种方法:

  • getPrototype() (必需):声明输入和输出类型

  • getReturnType() (有时必需):声明返回类型,包括长度和精度(适用时)

getPrototype() 方法会收到两个 ColumnTypes 参数,一个用于输入,一个用于输出。C++ 示例:字符串分词器 中的工厂接受单个输入字符串并返回字符串:

virtual void getPrototype(ServerInterface &srvInterface,
                          ColumnTypes &argTypes, ColumnTypes &returnType)
{
  argTypes.addVarchar();
  returnType.addVarchar();
}

ColumnTypes 类为每种受支持的类型提供“add”方法,例如 addVarchar()。此类支持具有 addArrayType()addRowType() 方法的复杂类型;请参阅作为实参的复杂类型。如果函数为多态函数,则可以改为调用 addAny()。然后,您负责验证输入和输出。有关实施多态 UDx 的详细信息,请参阅创建多态 UDx

getReturnType() 方法会计算返回值的最大长度。如果 Udx 返回特定大小的列(一种长度可变的返回数据类型,例如 VARCHAR)、需要精度的值或多个值,则需要实施此工厂方法。(某些 UDx 类型要求您实施该方法。)

输入是 SizedColumnTypes,其中包含输入实参类型及其长度。根据输入类型,请将以下值之一添加到输出类型:

  • CHAR、(LONG) VARCHAR、BINARY 和 (LONG) VARBINARY:返回最大长度。

  • NUMERIC 类型:指定精度和小数位数。

  • TIME 和 TIMESTAMP 值(含或不含时区):指定精度。

  • INTERVAL YEAR TO MONTH:指定范围。

  • INTERVAL DAY TO SECOND:指定精度和范围。

  • ARRAY:指定数组元素的最大数量。

使用字符串分词器时,输出为 VARCHAR 且函数可确定其最大长度:

// Tell Vertica what our return string length will be, given the input
// string length
virtual void getReturnType(ServerInterface &srvInterface,
                           const SizedColumnTypes &inputTypes,
                           SizedColumnTypes &outputTypes)
{
  // Error out if we're called with anything but 1 argument
  if (inputTypes.getColumnCount() != 1)
    vt_report_error(0, "Function only accepts 1 argument, but %zu provided", inputTypes.getColumnCount());

  int input_len = inputTypes.getColumnType(0).getStringLength();

  // Our output size will never be more than the input size
  outputTypes.addVarchar(input_len, "words");
}

作为实参的复杂类型和返回值

ColumnTypes 类支持 ARRAYROW 类型。数组包含各种元素,而行包含各种字段,两者均包含您需要描述的类型。要使用复杂类型,您需要为数组或行构建 ColumnTypes 对象,然后将其添加到表示函数输入和输出的 ColumnTypes 对象中。

在以下示例中,转换函数的输入是一个订单数组(即多个行),输出是各个行及其在数组中的位置。订单包含送货地址 (VARCHAR) 和产品 ID 数组 (INT)。

工厂的 getPrototype() 方法会先为数组和行元素创建 ColumnTypes,然后使用它们来调用 addArrayType()addRowType()


void getPrototype(ServerInterface &srv,
            ColumnTypes &argTypes,
            ColumnTypes &retTypes)
    {
        // item ID (int), to be used in an array
        ColumnTypes itemIdProto;
        itemIdProto.addInt();

        // row: order = address (varchar) + array of previously-created item IDs
        ColumnTypes orderProto;
        orderProto.addVarchar();                  /* address */
        orderProto.addArrayType(itemIdProto);     /* array of item ID */

        /* argument (input) is array of orders */
        argTypes.addArrayType(orderProto);

        /* return values: index in the array, order */
        retTypes.addInt();                        /* index of element */
        retTypes.addRowType(orderProto);          /* element return type */
    }

实参包括特定大小的类型 (VARCHAR)。getReturnType() 方法会使用类似的方法,从而使用 Fields 类在订单中构建两个字段。


void getReturnType(ServerInterface &srv,
            const SizedColumnTypes &argTypes,
            SizedColumnTypes &retTypes)
    {
        Fields itemIdElementFields;
        itemIdElementFields.addInt("item_id");

        Fields orderFields;
        orderFields.addVarchar(32, "address");
        orderFields.addArrayType(itemIdElementFields[0], "item_id");
        // optional third arg: max length, default unbounded

        /* declare return type */
        retTypes.addInt("index");
        static_cast<Fields &>(retTypes).addRowType(orderFields, "element");

        /* NOTE: presumably we have verified that the arguments match the prototype, so really we could just do this: */
        retTypes.addInt("index");
        retTypes.addArg(argTypes.getColumnType(0).getElementType(), "element");
    }

要在 UDx 处理方法中访问复杂类型,请使用 ArrayReaderArrayWriterStructReaderStructWriter 类。

请参阅“ C++ 示例:使用复杂类型”,了解使用数组的多态函数。

工厂的 getPrototype() 方法会先使用 makeType()addType() 方法,为行及其元素创建和构造 ColumnTypes。然后,该方法会调用 addType() 方法,以将这些构造的 ColumnTypes 添加到 arg_typesreturn_type 对象:


def getPrototype(self, srv_interface, arg_types, return_type):
    # item ID (int), to be used in an array
    itemIdProto = vertica_sdk.ColumnTypes.makeInt()

    # row (order): address (varchar) + array of previously-created item IDs
    orderProtoFields = vertica_sdk.ColumnTypes.makeEmpty()
    orderProtoFields.addVarchar()  # address
    orderProtoFields.addArrayType(itemIdProto) # array of item ID
    orderProto = vertica_sdk.ColumnTypes.makeRowType(orderProtoFields)

    # argument (input): array of orders
    arg_types.addArrayType(orderProto)

    # return values: index in the array, order
    return_type.addInt();                        # index of element
    return_type.addRowType(orderProto);          # element return type

工厂的 getReturnType() 方法会使用 makeInt()makeEmpty() 方法来创建 SizedColumnTypes,然后使用 addVarchar()addArrayType() 方法构建两个行字段。请注意,addArrayType() 方法会将数组元素的最大数量指定为 1024。 getReturnType() 然后,将这些构造的 SizedColumnTypes 添加到表示返回类型的对象中。


def getReturnType(self, srv_interface, arg_types, return_type):
    itemsIdElementField = vertica_sdk.SizedColumnTypes.makeInt("item_id")

    orderFields = vertica_sdk.SizedColumnTypes.makeEmpty()
    orderFields.addVarchar(32, "address")
    orderFields.addArrayType(itemIdElementField, 1024, "item_ids")

    # declare return type
    return_type.addInt("index")
    return_type.addRowType(orderFields, "element")

    '''
    NOTE: presumably we have verified that the arguments match the prototype, so really we could just do this:
    return_type.addInt("index")
    return_type.addArrayType(argTypes.getColumnType(0).getElementType(), "element")
    '''

要在 UDx 处理方法中访问复杂类型,请使用 ArrayReaderArrayWriterRowReaderRowWriter 类。有关详细信息,请参阅Python SDK

有关使用复杂类型的标量函数,请参阅Python 示例:矩阵乘法

处理不同数量和类型的实参

使用过载或多态性,您可以创建处理多个签名的 UDx,甚至还可以创建接受由用户提供给它们的所有实参的 UDx。

您可以使 UDx 过载,方法是将同一个 SQL 函数名称分配给多个工厂类,每个工厂类会定义一个唯一的函数签名。当用户在查询中使用该函数名称时,Vertica 会尝试将函数调用的签名与工厂的 getPrototype() 方法所声明的签名进行匹配。如果 UDx 需要接受几个不同签名(例如,接受两个必需参数和一个可选参数),则这是可用的最佳技术。

或者,您可以编写一个多态函数,以写入一个工厂方法而非多个工厂方法,并声明它接受任何数量和类型的实参。当用户在查询中使用函数名称时,Vertica 会调用您的函数,而不考虑签名。为换取这种灵活性,UDx 的主要“process”方法必须确定它是否可以接受各种实参并在无法接受时发出错误。

所有 UDx 类型都可以使用多态输入。转换函数和分析函数也可以使用多态输出。这意味着 getPrototype() 可以声明返回类型“any”并在运行时设置实际返回类型。例如,在输入中返回最大值的函数将返回与输入类型相同的类型。

1 - 重载 UDx

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

  • 一个或多个可选参数。

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

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

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

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

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

1.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。

1.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 很有用。如果要创建更灵活的参数,您可以创建多态函数。

2 - 创建多态 UDx

多态 UDx 可接受用户提供的任何数量的参数和任何类型的参数。转换函数 (UDTF)、分析函数 (UDAnF) 和聚合函数 (UDAF) 通常可以在运行时基于输入实参来定义输出返回类型。例如,将两个数字相加的 UDTF 可以返回整数或浮点数,具体取决于输入类型。

Vertica 不会检查用户传递到 UDx 的实参数量或类型,而只会向 UDx 传递用户提供的所有实参。多态 UDx 的主要处理函数(例如,用户定义的标量函数中的 processBlock())负责检查收到的实参数量和类型以及确定是否能够处理这些实参。UDx 最多支持 9800 个实参。

多态 UDx 比使用函数的多个工厂类更灵活(请参阅重载 UDx)。使用它们还可以编写更简洁的代码,而不是为每种数据类型编写不同的代码版本。代价是您的多态函数需要执行更多操作来确定它是否可以处理其实参。

多态 UDx 通过对 ColumnTypes 对象(定义其实参)调用 addAny() 函数,在其工厂的 getPrototype() 函数中声明它接受任何数量的实参,如下所示:

    // C++ example
    void getPrototype(ServerInterface &srvInterface,
                      ColumnTypes &argTypes,
                      ColumnTypes &returnType)
    {
        argTypes.addAny(); // Must be only argument type.
        returnType.addInt(); // or whatever the function returns
    }

您的函数只能声明此“any parameter”参数类型。不能定义必需参数,然后调用 addAny() 以声明其余签名作为可选参数。如果您的函数对其接受的实参有所要求,您的 process() 函数必须强制使用这些实参。

前面显示的 getPrototype() 示例接受任何类型并声明它会返回整数。以下示例显示将解析返回类型推迟到运行时的方法版本。您只能将“any”返回类型用于转换函数和分析函数。

    void getPrototype(ServerInterface &srvInterface,
                      ColumnTypes &argTypes,
                      ColumnTypes &returnType)
    {
        argTypes.addAny();
        returnType.addAny(); // type determined at runtime
    }

如果使用多态返回类型,则还必须在工厂中定义 getReturnType()。在运行时调用此函数可确定实际返回类型。有关示例,请参阅 C++ 示例:PolyNthValue

多态 UDx 和架构搜索路径

如果用户在调用 UDx 时未提供架构名称,Vertica 会在架构搜索路径中的每个架构中搜索其名称和签名与函数调用相匹配的函数。有关架构搜索路径的详细信息,请参阅设置搜索路径

由于多态 UDx 没有与其关联的特定签名,Vertica 最初在搜索函数以处理函数调用时会跳过它们。如果搜索路径中没有架构包含其名称和签名与函数调用相匹配的 UDx,Vertica 会再次在架构搜索路径中搜索其名称与函数调用中的函数名称相匹配的多态 UDx。

此行为会为其签名与函数调用完全匹配的 UDx 赋予优先权。它允许您创建“捕获全部”多态 UDx,仅当没有任何同名非多态 UDx 具有匹配的签名时,Vertica 才调用该多态 UDx。

如果用户期望架构搜索路径中的第一个多态函数处理函数调用,此行为可能会造成混乱。为了避免混乱,您应该:

  • 避免不同 UDx 使用相同的名称。应始终唯一命名 UDx,除非您要创建具有多个签名的过载 UDx。

  • 当无法避免在不同架构中使用同名 UDx 时,请始终在函数调用中提供架构名称。使用架构名称可避免产生歧义,并确保 Vertica 使用正确的 Udx 来处理函数调用。

2.1 - C++ 示例:PolyNthValue

PolyNthValue 示例是一个分析函数,它返回其输入中每个分区的第 N 行中的值。该函数是 FIRST_VALUE [analytic]LAST_VALUE [analytic] 的泛化。

这些值可以是任何基元数据类型。

有关完整的源代码,请参阅示例(位于 /opt/vertica/sdk/examples/AnalyticFunctions/ 中)中的 PolymorphicNthValue.cpp

加载和使用示例

加载库并创建函数,如下所示:

=> CREATE LIBRARY AnalyticFunctions AS '/home/dbadmin/AnalyticFns.so';
CREATE LIBRARY

=> CREATE ANALYTIC FUNCTION poly_nth_value AS LANGUAGE 'C++'
   NAME 'PolyNthValueFactory' LIBRARY AnalyticFunctions;
CREATE ANALYTIC FUNCTION

考虑不同测试组的分数表:

=> SELECT cohort, score FROM trials;
 cohort | score
--------+-------
   1    | 9
   1    | 8
   1    | 7
   3    | 3
   3    | 2
   3    | 1
   2    | 4
   2    | 5
   2    | 6
(9 rows)

在使用 OVER 子句对数据进行分区的查询中调用该函数。此示例返回每个同类群组中的第二高分:

=> SELECT cohort, score, poly_nth_value(score USING PARAMETERS n=2) OVER (PARTITION BY cohort) AS nth_value
FROM trials;
 cohort | score | nth_value
--------+-------+-----------
   1    | 9     |         8
   1    | 8     |         8
   1    | 7     |         8
   3    | 3     |         2
   3    | 2     |         2
   3    | 1     |         2
   2    | 4     |         5
   2    | 5     |         5
   2    | 6     |         5
(9 rows)

工厂实施

工厂先声明类是多态的,然后根据输入类型设置返回类型。两个工厂方法指定实参和返回类型。

使用 getPrototype() 方法声明分析函数接受并返回任何类型:

    void getPrototype(ServerInterface &srvInterface, ColumnTypes &argTypes, ColumnTypes &returnType)
    {
        // This function supports any argument data type
        argTypes.addAny();

        // Output data type will be the same as the argument data type
        // We will specify that in getReturnType()
        returnType.addAny();
    }

在运行时调用 getReturnType() 方法。这是您根据输入类型设置返回类型的地方:

    void getReturnType(ServerInterface &srvInterface, const SizedColumnTypes &inputTypes,
                       SizedColumnTypes &outputTypes)
    {
        // This function accepts only one argument
        // Complain if we find a different number
        std::vector<size_t> argCols;
        inputTypes.getArgumentColumns(argCols); // get argument column indices

        if (argCols.size() != 1)
        {
            vt_report_error(0, "Only one argument is expected but %s provided",
                            argCols.size()? std::to_string(argCols.size()).c_str() : "none");
        }

        // Define output type the same as argument type
        outputTypes.addArg(inputTypes.getColumnType(argCols[0]), inputTypes.getColumnName(argCols[0]));
    }

函数实施

分析函数本身与类型无关:


    void processPartition(ServerInterface &srvInterface, AnalyticPartitionReader &inputReader,
                          AnalyticPartitionWriter &outputWriter)
    {
        try {
            const SizedColumnTypes &inTypes = inputReader.getTypeMetaData();
            std::vector<size_t> argCols; // Argument column indexes.
            inTypes.getArgumentColumns(argCols);

            vint currentRow = 1;
            bool nthRowExists = false;

            // Find the value of the n-th row
            do {
                if (currentRow == this->n) {
                    nthRowExists = true;
                    break;
                } else {
                    currentRow++;
                }
            } while (inputReader.next());

            if (nthRowExists) {
                do {
                    // Return n-th value
                    outputWriter.copyFromInput(0 /*dest column*/, inputReader,
                                               argCols[0] /*source column*/);
                } while (outputWriter.next());
            } else {
                // The partition has less than n rows
                // Return NULL value
                do {
                    outputWriter.setNull(0);
                } while (outputWriter.next());
            }
        } catch(std::exception& e) {
            // Standard exception. Quit.
            vt_report_error(0, "Exception while processing partition: [%s]", e.what());
        }
    }
};

2.2 - Java 示例:AddAnyInts

以下示例显示了将两个或更多整数相加的 Java ScalarFunction 的实施。

有关完整的源代码,请参阅示例(位于 /opt/vertica/sdk/examples/JavaUDx/ScalarFunctions 中)中的 AddAnyIntsInfo.java

加载和使用示例

加载库并创建函数,如下所示:

=> CREATE LIBRARY JavaScalarFunctions AS '/home/dbadmin/JavaScalarLib.jar' LANGUAGE 'JAVA';
CREATE LIBRARY

=> CREATE FUNCTION addAnyInts AS LANGUAGE 'Java' NAME 'com.vertica.JavaLibs.AddAnyIntsInfo'
   LIBRARY JavaScalarFunctions;
CREATE FUNCTION

使用两个或多个整数实参调用函数:

=> SELECT addAnyInts(1,2);
 addAnyInts
------------
          3
(1 row)

=> SELECT addAnyInts(1,2,3,40,50,60,70,80,900);
 addAnyInts
------------
       1206
(1 row)

如果使用太少的实参或使用非整数实参调用函数,则产生由 processBlock() 方法生成的错误。UDx 负责确保用户向函数提供正确的参数个数和类型,如果无法处理参数,它应退出并显示错误。

函数实施

此示例中的大部分工作由 processBlock() 方法执行。该函数将对通过 BlockReader 对象传入的实参执行两次检查:

  • 是否存在至少两个参数。

  • 是否所有实参的数据类型均为整数。

多态 UDx 负责确定传入的所有输入是否有效。

processBlock() 方法验证其参数之后,它会在所有参数之中循环并将其相加。

        @Override
        public void processBlock(ServerInterface srvInterface,
                                 BlockReader arg_reader,
                                 BlockWriter res_writer)
                    throws UdfException, DestroyInvocation
        {
        SizedColumnTypes inTypes = arg_reader.getTypeMetaData();
        ArrayList<Integer> argCols = new ArrayList<Integer>(); // Argument column indexes.
        inTypes.getArgumentColumns(argCols);
        // While we have inputs to process
            do {
        long sum = 0;
        for (int i = 0; i < argCols.size(); ++i){
            long a = arg_reader.getLong(i);
            sum += a;
        }
                res_writer.setLong(sum);
                res_writer.next();
            } while (arg_reader.next());
        }
    }

工厂实施

工厂在 getPrototype() 函数中声明实参的数量和类型。

    @Override
    public void getPrototype(ServerInterface srvInterface,
                             ColumnTypes argTypes,
                             ColumnTypes returnType)
    {
    argTypes.addAny();
        returnType.addInt();
    }

2.3 - R 示例:kmeansPoly

以下示例显示了对一个或多个输入列执行 kmeans 聚类的转换函数 (UDTF) 的实施。

kmeansPoly <- function(v.data.frame,v.param.list) {
  # Computes clusters using the kmeans algorithm.
  #
  # Input: A dataframe and a list of parameters.
  # Output: A dataframe with one column that tells the cluster to which each data
  #         point belongs.
  # Args:
  #  v.data.frame: The data from Vertica cast as an R data frame.
  #  v.param.list: List of function parameters.
  #
  # Returns:
  #  The cluster associated with each data point.
  # Ensure k is not null.
  if(!is.null(v.param.list[['k']])) {
     number_of_clusters <- as.numeric(v.param.list[['k']])
  } else {
    stop("k cannot be NULL! Please use a valid value.")
  }
  # Run the kmeans algorithm.
  kmeans_clusters <- kmeans(v.data.frame, number_of_clusters)
  final.output <- data.frame(kmeans_clusters$cluster)
  return(final.output)
}

kmeansFactoryPoly <- function() {
  # This function tells Vertica the name of the R function,
  # and the polymorphic parameters.
  list(name=kmeansPoly, udxtype=c("transform"), intype=c("any"),
       outtype=c("int"), parametertypecallback=kmeansParameters)
}

kmeansParameters <- function() {
  # Callback function for the parameter types.
  function.parameters <- data.frame(datatype=rep(NA, 1), length=rep(NA,1),
                                    scale=rep(NA,1), name=rep(NA,1))
  function.parameters[1,1] = "int"
  function.parameters[1,4] = "k"
  return(function.parameters)
}

多态 R 函数通过将 "any" 指定为 intype 形参的实参和可选的 outtype 形参,在其工厂函数中声明它可接受任何数量的实参。如果为 intypeouttype 定义 "any" 实参,则函数只能为相应的形参声明该类型。您不能先定义必需实参,然后再调用“any”将其余签名声明为可选实参。如果您的函数对其接受的实参有所要求,您的处理函数必须强制使用这些实参。

outtypecallback 方法用于指示与此方法一起调用的实参类型和数量,并且需要指示函数所返回的类型和数量。outtypecallback 方法还可以用于检查不受支持的实参类型和/或数量。例如,函数可能只需要最多 10 个整数:

您使用与将某个 SQL 名称分配给一个非多态 UDx 相同的语句将一个 SQL 名称分配给您的多态 UDx。以下语句显示了如何从示例中加载和调用多态函数。

=> CREATE LIBRARY rlib2 AS '/home/dbadmin/R_UDx/poly_kmeans.R' LANGUAGE 'R';
CREATE LIBRARY
=> CREATE TRANSFORM FUNCTION kmeansPoly AS LANGUAGE 'R' name 'kmeansFactoryPoly' LIBRARY rlib2;
CREATE FUNCTION
=> SELECT spec, kmeansPoly(sl,sw,pl,pw USING PARAMETERS k = 3)
    OVER(PARTITION BY spec) AS Clusters
      FROM iris;
      spec       | Clusters
-----------------+----------
 Iris-setosa     |        1
 Iris-setosa     |        1
 Iris-setosa     |        1
 Iris-setosa     |        1
.
.
.
(150 rows)