C++ 示例:字符串分词器

以下示例显示了 TransformFunction 的子类,它名为 StringTokenizer。此子类定义的 UDTF 可读取包含 INTEGER ID 列和 VARCHAR 列的表。此子类可将 VARCHAR 列中的文本拆分为标记(各个词)。此子类将返回一个表,表中包含每个标记、标记所出现在的行以及标记在字符串中的位置。

加载和使用示例

以下示例显示了如何将函数加载至 Vertica 中。假设包含该函数的 TransformFunctions.so 库已复制到启动程序节点上数据库管理员用户的主目录中。

=> CREATE LIBRARY TransformFunctions AS
   '/home/dbadmin/TransformFunctions.so';
CREATE LIBRARY
=> CREATE TRANSFORM FUNCTION tokenize
   AS LANGUAGE 'C++' NAME 'TokenFactory' LIBRARY TransformFunctions;
CREATE TRANSFORM FUNCTION

然后,您可以通过 SQL 语句使用该函数,例如:


=> CREATE TABLE T (url varchar(30), description varchar(2000));
CREATE TABLE
=> INSERT INTO T VALUES ('www.amazon.com','Online retail merchant and provider of cloud services');
 OUTPUT
--------
      1
(1 row)
=> INSERT INTO T VALUES ('www.vertica.com','World''s fastest analytic database');
 OUTPUT
--------
      1
(1 row)
=> COMMIT;
COMMIT

=> -- Invoke the UDTF
=> SELECT url, tokenize(description) OVER (partition by url) FROM T;
       url       |   words
-----------------+-----------
 www.amazon.com  | Online
 www.amazon.com  | retail
 www.amazon.com  | merchant
 www.amazon.com  | and
 www.amazon.com  | provider
 www.amazon.com  | of
 www.amazon.com  | cloud
 www.amazon.com  | services
 www.vertica.com | World's
 www.vertica.com | fastest
 www.vertica.com | analytic
 www.vertica.com | database
(12 rows)

请注意,结果表中的行数和列数与输入表中不同。这是 UDTF 的优势之一。

TransformFunction 实施

以下代码显示了 StringTokenizer 类。

class StringTokenizer : public TransformFunction
{
  virtual void processPartition(ServerInterface &srvInterface,
                                PartitionReader &inputReader,
                                PartitionWriter &outputWriter)
  {
    try {
      if (inputReader.getNumCols() != 1)
        vt_report_error(0, "Function only accepts 1 argument, but %zu provided", inputReader.getNumCols());

      do {
        const VString &sentence = inputReader.getStringRef(0);

        // If input string is NULL, then output is NULL as well
        if (sentence.isNull())
          {
            VString &word = outputWriter.getStringRef(0);
            word.setNull();
            outputWriter.next();
          }
        else
          {
            // Otherwise, let's tokenize the string and output the words
            std::string tmp = sentence.str();
            std::istringstream ss(tmp);

            do
              {
                std::string buffer;
                ss >> buffer;

                // Copy to output
                if (!buffer.empty()) {
                  VString &word = outputWriter.getStringRef(0);
                  word.copy(buffer);
                  outputWriter.next();
                }
              } while (ss);
          }
      } while (inputReader.next() && !isCanceled());
    } catch(std::exception& e) {
      // Standard exception. Quit.
      vt_report_error(0, "Exception while processing partition: [%s]", e.what());
    }
  }
};

此示例中的 processPartition() 函数将遵循您在自己的 UDTF 中遵循的相同模式:遍历 Vertica 向其发送的表分区中的所有行,以处理每个行并在前进之前检查是否已取消查询。对于 UDTF,您实际上不必处理每个行。即使退出函数而不读取所有输入,也不会出现任何问题。如果 UDTF 在执行某种搜索或其他某项操作后确定其余资源是不需要的,您可以选择执行此操作。

在此示例中,processPartition() 会先从 PartitionReader 对象中提取包含文本的 VStringVString 类代表 Vertica 字符串值(VARCHAR 或 CHAR)。如果存在输入,它会对其进行字符串标记并使用 PartitionWriter 对象将其添加到输出中。

与读取输入列类似,PartitionWriter 类具有将每种类型的数据写入输出行的函数。在这种情况下,该示例会调用 PartitionWriter 对象的 getStringRef() 函数来分配新的 VString 对象以保存第一列的输出标记,然后将标记的值复制到 VString 中。

TransformFunctionFactory 实施

以下代码显示了该工厂类。

class TokenFactory : public TransformFunctionFactory
{
  // Tell Vertica that we take in a row with 1 string, and return a row with 1 string
  virtual void getPrototype(ServerInterface &srvInterface, ColumnTypes &argTypes, ColumnTypes &returnType)
  {
    argTypes.addVarchar();
    returnType.addVarchar();
  }

  // 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");
  }

  virtual TransformFunction *createTransformFunction(ServerInterface &srvInterface)
  { return vt_createFuncObject<StringTokenizer>(srvInterface.allocator); }

};

在此示例中:

  • UDTF 会将 VARCHAR 列作为输入。为了定义输入列,getPrototype() 会在表示输入表的 ColumnTypes 对象上调用 addVarchar()

  • UDTF 将返回 VARCHAR 作为输出。getPrototype() 函数会调用 addVarchar() 以定义输出表。

此示例必须返回 VARCHAR 输出列的最大长度。它会将长度设置为输入字符串的长度。这是一个安全值,因为输出长度永远不会超过输入字符串。此示例还会将 VARCHAR 输出列的名称设置为“words”。

此示例中的 createTransformFunction() 函数实施是样板代码。该实施仅使用与此工厂类关联的 TransformFunction 类的名称来调用 vt_returnFuncObj 宏。此宏负责将 TransformFunction 类的副本实例化,Vertica 可以使用该副本来处理数据。

RegisterFactory 宏

创建 UDTF 的最后一步是调用 RegisterFactory 宏。此宏可确保在 Vertica 加载包含 UDTF 的共享库时,工厂类已实例化。只有将工厂类初始化,Vertica 才能找到 UDTF 并确定其输入和输出,除此之外没有任何其他方法。

RegisterFactory 宏仅使用工厂类的名称:

RegisterFactory(TokenFactory);