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:
- 数据清洗:使用正则或复杂逻辑清洗文本字段,SQL 自带的字符串函数无法满足
- 业务规则编码:将独有业务逻辑(如风险评分、地址标准化)封装为函数
- 性能优化:减少 SQL 和应用程序之间的数据传输,将计算下推到数据库层
- 外部数据集成:通过 UDSource 从非标准数据源加载数据
- 自定义格式解析:解析专有文件格式
支持的开发语言¶
| 语言 | 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 开发环境的详细步骤:
- 创建 Java 项目:File → New → Java Project,填入项目名称(如
VerticaUDx) - 配置 JRE:确保使用 JDK 8 或 JDK 11,Window → Preferences → Java → Installed JREs
- 添加 VerticaSDK.jar:右键项目 → Build Path → Configure Build Path → Libraries → Add External JARs → 选择
VerticaSDK.jar - 添加 BuildInfo.java:粘贴
BuildInfo.java到项目的src目录下,确保无编译错误 - 创建包结构:在
src下创建包(如com.vertica.udx.example),右键src→ New → Package - 创建 Factory 类:在包下新建类(如
CountWordsFactory.java) - 创建 Function 类:在包下新建类(如
CountWords.java) - 实现方法:编写函数逻辑(详见第 2 章)
- 编译检查:Eclipse 自动编译,确保无红叉
- 导出 JAR:右键项目 → Export → JAR File → 选择导出路径 → Next → Next → 选择 Main class(可选)→ Finish
- 传输 JAR:将 JAR 文件复制到 Vertica 服务器节点
- 创建 LIBRARY:在 vsql 中执行
CREATE LIBRARY命令 - 创建 FUNCTION:在 vsql 中执行
CREATE FUNCTION命令 - 测试函数:在 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()声明返回值为 INTEGERcreateScalarFunction()返回 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 部分 |
扩展阅读¶
- Vertica 数据库的启动和关闭 — 开发测试时需要启停 Vertica