跳转至

Java UDx 开发完全指南

原文:Developing UDx with Java (Part 1): Set Up the Development Environment & Part 2: Create a Scalar Function

概述

Vertica 提供丰富的内置函数,但在某些复杂业务场景下,用户可能需要自定义数据处理逻辑。用户自定义扩展(UDx, User-Defined Extensions)允许您使用通用编程语言编写函数,部署到 Vertica 中像内置函数一样使用。

UDx 分类

UDx 分为两大类,下图展示了完整的分类层级:

UDx (User-Defined Extensions)
├── UDF (User-Defined Function)
│   ├── UDSF (User-Defined Scalar Function)
│   │   └── 示例:COUNT_WORDS(text) → int
│   ├── UDTF (User-Defined Transform Function)
│   │   └── 示例:SPLIT_URL(url) → (protocol, host, path)
│   └── UDAF (User-Defined Aggregate Function)
│       └── 示例:MEDIAN(value) → double
└── UDL (User-Defined Load)
    ├── UDSource        ── 自定义数据源加载
    ├── UDFilter        ── 自定义数据过滤
    └── UDParser        ── 自定义数据解析

为什么需要创建 UDx

以下典型场景适合创建 UDx:

  1. 数据清洗:使用正则或复杂逻辑清洗文本字段,SQL 自带的字符串函数无法满足
  2. 业务规则编码:将独有业务逻辑(如风险评分、地址标准化)封装为函数
  3. 性能优化:减少 SQL 和应用程序之间的数据传输,将计算下推到数据库层
  4. 外部数据集成:通过 UDSource 从非标准数据源加载数据
  5. 自定义格式解析:解析专有文件格式

支持的开发语言

语言 C++ Java Python R
性能 最高 较高 中等 较低
开发效率 较低 最高 中等
适用场景 核心计算 通用开发 快速原型 统计分析

第 1 章:开发环境搭建

系统要求

组件 最低要求 推荐配置
操作系统 Linux / macOS Linux x86-64
硬件 2 核 CPU, 4GB 内存 4 核 CPU, 8GB 内存
JDK JDK 8 JDK 8 或 JDK 11
Vertica 9.x+ 最新版本

所需文件

开发 Java UDx 需要两个关键文件,它们位于 Vertica 安装目录的 SDK 子目录下:

文件 路径 说明
VerticaSDK.jar /opt/vertica/bin/../SDK/java/VerticaSDK.jar Java UDx SDK 库
BuildInfo.java /opt/vertica/bin/../SDK/java/BuildInfo.java SDK 编译信息

在 Vertica 服务器上找到这些文件:

# 查找 VerticaSDK.jar
find /opt/vertica -name "VerticaSDK.jar"

# 查找 BuildInfo.java
find /opt/vertica -name "BuildInfo.java"

将这两个文件复制到开发机器的项目目录中。

Eclipse 项目配置

以下是使用 Eclipse IDE 配置 Java UDx 开发环境的详细步骤:

  1. 创建 Java 项目:File → New → Java Project,填入项目名称(如 VerticaUDx
  2. 配置 JRE:确保使用 JDK 8 或 JDK 11,Window → Preferences → Java → Installed JREs
  3. 添加 VerticaSDK.jar:右键项目 → Build Path → Configure Build Path → Libraries → Add External JARs → 选择 VerticaSDK.jar
  4. 添加 BuildInfo.java:粘贴 BuildInfo.java 到项目的 src 目录下,确保无编译错误
  5. 创建包结构:在 src 下创建包(如 com.vertica.udx.example),右键 src → New → Package
  6. 创建 Factory 类:在包下新建类(如 CountWordsFactory.java
  7. 创建 Function 类:在包下新建类(如 CountWords.java
  8. 实现方法:编写函数逻辑(详见第 2 章)
  9. 编译检查:Eclipse 自动编译,确保无红叉
  10. 导出 JAR:右键项目 → Export → JAR File → 选择导出路径 → Next → Next → 选择 Main class(可选)→ Finish
  11. 传输 JAR:将 JAR 文件复制到 Vertica 服务器节点
  12. 创建 LIBRARY:在 vsql 中执行 CREATE LIBRARY 命令
  13. 创建 FUNCTION:在 vsql 中执行 CREATE FUNCTION 命令
  14. 测试函数:在 vsql 中执行函数测试

第 2 章:创建标量函数

本章以 COUNT_WORDS 函数为例,完整演示 UDSF 的开发过程。该函数接收一个字符串参数,返回单词数量。

UDSF 架构

每个 UDSF 包含两个 Java 类:

文件名 职责
Factory 类 *Factory.java 定义函数原型(输入输出类型);实例化 Function 类
Function 类 *.java 实现实际的数据处理逻辑(processBlock 方法)

CountWordsFactory.java

Factory 类有两个核心职责:声明函数原型和创建 Function 实例。

package com.vertica.udx.example;

import java.sql.Types;
import com.vertica.sdk.*;

public class CountWordsFactory extends ScalarFunctionFactory {

    // 1. 定义函数原型:接收一个 VARCHAR,返回一个 INTEGER
    @Override
    public void getPrototype(PrototypeGetter getter) {
        getter.addVarchar(65000);   // 输入参数:VARCHAR(65000)
        getter.addInt();             // 返回类型:INTEGER
    }

    // 2. 创建 Function 实例
    @Override
    public ScalarFunction createScalarFunction() {
        return new CountWords();
    }
}

代码说明:

  • getPrototype() 定义函数的输入输出签名。addVarchar(65000) 声明输入参数为最大 65000 字符的 VARCHAR,addInt() 声明返回值为 INTEGER
  • createScalarFunction() 返回 Function 类的实例,Vertica 在执行查询时调用此方法

CountWords.java

Function 类包含实际的数据处理逻辑。

package com.vertica.udx.example;

import com.vertica.sdk.*;

public class CountWords extends ScalarFunction {

    // 核心处理方法:对每一行数据调用一次
    @Override
    public void processBlock(ServerInterface srvInterface,
                             BlockReader argReader,
                             BlockWriter resWriter)
            throws UdfException, DestroyInvocation {

        // 读取输入参数(VARCHAR 类型)
        String text = argReader.getString(0);

        // 如果输入为 NULL,返回 0
        if (text == null || text.isEmpty()) {
            resWriter.setLong(0);
            return;
        }

        // 核心逻辑:按空格分割文本,统计单词数
        String[] words = text.trim().split("\\s+");
        // 处理全空格字符串的特殊情况
        int count = (words.length == 1 && words[0].isEmpty()) ? 0 : words.length;

        // 写入输出结果
        resWriter.setLong(count);
    }
}

processBlock 方法逐步说明:

步骤 代码 说明
1. 读取输入 argReader.getString(0) 读取第一个输入参数(VARCHAR 文本)。索引 0 对应 getPrototype 中添加的第一个参数
2. NULL 处理 if (text == null) 显式检查 NULL 输入,避免 NullPointerException
3. 空字符串处理 text.isEmpty() 对空字符串返回 0
4. 单词分割 text.trim().split("\\s+") 使用正则 \\s+ 按空白字符分割,trim() 去除首尾空格
5. 结果写入 resWriter.setLong(count) 将计算结果写入输出。setLong 对应 getPrototype 中声明的 INTEGER 返回类型

完整代码清单

CountWordsFactory.java

package com.vertica.udx.example;

import java.sql.Types;
import com.vertica.sdk.*;

public class CountWordsFactory extends ScalarFunctionFactory {

    @Override
    public void getPrototype(PrototypeGetter getter) {
        getter.addVarchar(65000);
        getter.addInt();
    }

    @Override
    public ScalarFunction createScalarFunction() {
        return new CountWords();
    }
}

CountWords.java

package com.vertica.udx.example;

import com.vertica.sdk.*;

public class CountWords extends ScalarFunction {

    @Override
    public void processBlock(ServerInterface srvInterface,
                             BlockReader argReader,
                             BlockWriter resWriter)
            throws UdfException, DestroyInvocation {

        String text = argReader.getString(0);

        if (text == null || text.isEmpty()) {
            resWriter.setLong(0);
            return;
        }

        String[] words = text.trim().split("\\s+");
        int count = (words.length == 1 && words[0].isEmpty()) ? 0 : words.length;
        resWriter.setLong(count);
    }
}

第 3 章:部署与测试

步骤 1:导出 JAR 包

在 Eclipse 中:右键项目 → Export → JAR File → 选择导出路径(如 count_words.jar)→ Finish。

步骤 2:创建 LIBRARY

将 JAR 文件上传到 Vertica 服务器的任一节点,然后在 vsql 中执行:

-- 创建 LIBRARY,关联 JAR 文件路径
CREATE LIBRARY CountWordsLib AS '/path/to/count_words.jar'
    LANGUAGE 'Java';

步骤 3:创建 FUNCTION

-- 创建函数,关联 Factory 类
CREATE FUNCTION count_words AS LANGUAGE 'Java'
    NAME 'com.vertica.udx.example.CountWordsFactory'
    LIBRARY CountWordsLib;

步骤 4:测试函数

-- 基本测试
SELECT count_words('Hello World');
-- 预期输出: 2

-- 多空格测试
SELECT count_words('Vertica   UDx   development');
-- 预期输出: 3

-- 空字符串测试
SELECT count_words('');
-- 预期输出: 0

-- NULL 测试
SELECT count_words(NULL);
-- 预期输出: NULL(空值传播)

使用表数据测试

-- 创建测试表
CREATE TABLE test_texts (
    id INT,
    content VARCHAR(65000)
);

-- 插入测试数据
INSERT INTO test_texts VALUES (1, 'Vertica is a columnar database');
INSERT INTO test_texts VALUES (2, 'Java UDx development guide');
INSERT INTO test_texts VALUES (3, '');
INSERT INTO test_texts VALUES (4, NULL);

-- 提交
COMMIT;

-- 查询测试
SELECT id, content, count_words(content) AS word_count
FROM test_texts
ORDER BY id;

NULL 处理讨论

值得注意的是,当输入为 NULL 时,count_words(NULL) 返回的是 NULL 而不是 0。这是因为 Vertica 的标量函数遵循 SQL 标准中的 NULL 传播语义——如果任何输入参数为 NULL,函数通常返回 NULL,而不会调用 processBlock 方法。这与我们在代码中处理输入参数本身为 NULL 字符串的情形不同。


后续主题预览

本文涵盖了 Java UDx 开发环境的搭建和标量函数(UDSF)的创建。Vertica 还支持更多类型的 UDx 扩展:

UDx 类型 描述 适用场景 将在
UDTF(Transform Function) 一行输入,多行输出 数据展开、分词、日志解析 第 3 部分
UDAF(Aggregate Function) 多行输入,一行输出 自定义聚合运算、统计指标 第 4 部分
UDSource 自定义数据源 非标准格式文件加载 第 5 部分
UDFilter 数据过滤 加密文件实时解密加载 第 5 部分
UDParser 自定义解析器 专有文件格式解析 第 5 部分

扩展阅读