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

返回本页常规视图.

创建多态 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 来处理函数调用。

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 - 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();
    }

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)