SDK 为 UDx 提供了多种报告错误、警告和其他消息的方法。对于使用 C++ 或 Python 编写的 UDx,请使用发送消息中描述的消息传递 API。如处理错误中所述,所有语言的 UDx 都可能因出错而停止执行。
此外,UDx 还可以将消息写入 Vertica 日志,而使用 C++ 编写的 UDx 可以将消息写入系统表。
SDK 为 UDx 提供了多种报告错误、警告和其他消息的方法。对于使用 C++ 或 Python 编写的 UDx,请使用发送消息中描述的消息传递 API。如处理错误中所述,所有语言的 UDx 都可能因出错而停止执行。
此外,UDx 还可以将消息写入 Vertica 日志,而使用 C++ 编写的 UDx 可以将消息写入系统表。
UDx 可以通过报告错误并终止执行来处理问题,但在某些情况下,您可能希望发送警告并继续。例如,UDx 可能会忽略或使用意外输入的默认值,并报告它已经这样做了。C++ 和 Python 消息传递 API 支持报告不同严重性级别的消息。
Udx 对 ServerInterface
实例具有访问权限。此类具有以下按严重性顺序报告消息的方法:
reportError
(也会终止执行)
reportWarning
reportNotice
reportInfo
每种方法都会生成包含以下组件的消息:
ID 代码:标识码,为任意整数。此代码不会与 Vertica 错误代码交互。
消息字符串:对问题的简要描述。
可选详细信息字符串:提供更多上下文信息。
可选提示字符串:提供其他指导。
如果重复的消息具有相同的代码和消息字符串,则即使详细信息和提示字符串不同,它们也会被压缩到一个报告中。
UDx 通常应在进程调用期间立即报告错误。对于所有其他消息类型,请在处理期间记录信息并从 UDx 的 destroy
方法调用报告方法。如果在处理期间调用其他报告方法,则不会生成输出。
构造消息的过程特定于语言。
每个 ServerInterface
报告方法均使用 ClientMessage
实参。ClientMessage
类具有以下用于设置代码和消息、详细信息及提示的方法:
makeMessage:
设置 ID 代码和消息字符串。
setDetail:
设置可选的详细信息字符串。
setHint:
设置可选的提示字符串。
这些方法调用可以链接起来以简化消息的创建和传递。
所有字符串都支持 printf
样式的实参和格式。
在以下示例中,函数在 processBlock
中记录问题并在 destroy
中报告它们:
class PositiveIdentity : public Vertica::ScalarFunction
{
public:
using ScalarFunction::destroy;
bool hitNotice = false;
virtual void processBlock(Vertica::ServerInterface &srvInterface,
Vertica::BlockReader &arg_reader,
Vertica::BlockWriter &res_writer)
{
do {
const Vertica::vint a = arg_reader.getIntRef(0);
if (a < 0 && a != vint_null) {
hitNotice = true;
res_writer.setInt(null);
} else {
res_writer.setInt(a);
}
res_writer.next();
} while (arg_reader.next());
}
virtual void destroy(ServerInterface &srvInterface,
const SizedColumnTypes &argTypes) override
{
if (hitNotice) {
ClientMessage msg = ClientMessage::makeMessage(100, "Passed negative argument")
.setDetail("Value set to null");
srvInterface.reportNotice(msg);
}
}
}
每个 ServerInterface
报告方法都包含以下位置实参和关键字实参:
idCode:整数 ID 代码,为位置实参。
message:消息文本,为位置实参。
hint:可选提示文本,为关键字实参。
detail:可选的详细信息文本,为关键字实参。
所有实参都支持 str.format()
和 f-string
格式。
在以下示例中,函数在 processBlock
中记录问题并在 destroy
中报告它们:
class PositiveIdentity(vertica_sdk.ScalarFunction):
def __init__(self):
self.hitNotice = False
def processBlock(self, server_interface, arg_reader, res_writer):
while True:
arg = arg_reader.getInt(0)
if arg < 0 and arg is not None:
self.hitNotice = True
res_writer.setNull()
else:
res_writer.setInt(arg)
res_writer.next()
if not arg_reader.next():
break
def destroy(self, srv, argType):
if self.hitNotice:
srv.reportNotice(100, "Passed negative arguement", detail="Value set to null")
return
在调用 ServerInterface 报告方法之前,请使用 ClientMessage 类构造并填充消息。
ServerInterface API 会提供以下报告消息的方法:
// ClientMessage methods
template<typename... Argtypes>
static ClientMessage makeMessage(int errorcode, const char *fmt, Argtypes&&... args);
template <typename... Argtypes>
ClientMessage & setDetail(const char *fmt, Argtypes&&... args);
template <typename... Argtypes>
ClientMessage & setHint(const char *fmt, Argtypes&&... args);
// ServerInterface reporting methods
virtual void reportError(ClientMessage msg);
virtual void reportInfo(ClientMessage msg);
virtual void reportNotice(ClientMessage msg);
virtual void reportWarning(ClientMessage msg);
ServerInterface API 会提供以下报告消息的方法:
def reportError(self, code, text, hint='', detail=''):
def reportInfo(self, code, text, hint='', detail=''):
def reportNotice(self, code, text, hint='', detail=''):
def reportWarning(self, code, text, hint='', detail=''):
如果 UDx 遇到无法恢复的错误,它应报告错误并终止。如何做到这一点取决于编写时所用的语言:
C++:考虑使用发送消息中所述的 API,它比本主题中描述的错误处理更具表现力。或者,您可以使用 vt_report_error
宏来报告错误并退出。该宏使用以下两个参数:错误号和错误消息字符串。错误号和消息都会显示在 Vertica 向用户报告的错误中。错误号并不由 Vertica 定义。您可以随意使用任何值。
Java:实例化并引发 UdfException
,它会将数字代码和消息字符串报告给用户。
Python:考虑使用发送消息中所述的 API,它比本主题中描述的错误处理更具表现力。或者,引发内置到 Python 语言中的异常;SDK 不包含特定于 UDx 的异常。
R:使用 stop
来停止执行并显示一条消息。
异常或停止会导致包含函数调用的事务被回滚。
以下示例演示了错误处理:
以下函数会将两个整数相除。为了防止除数为零,它会测试第二个参数并在为零时设为失败:
class Div2ints : public ScalarFunction
{
public:
virtual void processBlock(ServerInterface &srvInterface,
BlockReader &arg_reader,
BlockWriter &res_writer)
{
// While we have inputs to process
do
{
const vint a = arg_reader.getIntRef(0);
const vint b = arg_reader.getIntRef(1);
if (b == 0)
{
vt_report_error(1,"Attempted divide by zero");
}
res_writer.setInt(a/b);
res_writer.next();
}
while (arg_reader.next());
}
};
加载和调用函数演示了用户所看到的错误。隔离和非隔离模式使用不同的错误号。
=> CREATE LIBRARY Div2IntsLib AS '/home/dbadmin/Div2ints.so';
CREATE LIBRARY
=> CREATE FUNCTION div2ints AS LANGUAGE 'C++' NAME 'Div2intsInfo' LIBRARY Div2IntsLib;
CREATE FUNCTION
=> SELECT div2ints(25, 5);
div2ints
----------
5
(1 row)
=> SELECT * FROM MyTable;
a | b
----+---
12 | 6
7 | 0
12 | 2
18 | 9
(4 rows)
=> SELECT * FROM MyTable WHERE div2ints(a, b) > 2;
ERROR 3399: Error in calling processBlock() for User Defined Scalar Function
div2ints at Div2ints.cpp:21, error code: 1, message: Attempted divide by zero
在以下示例中,如果任一实参为 NULL,则 processBlock()
方法将引发异常:
@Override
public void processBlock(ServerInterface srvInterface,
BlockReader argReader,
BlockWriter resWriter)
throws UdfException, DestroyInvocation
{
do {
// Test for NULL value. Throw exception if one occurs.
if (argReader.isLongNull(0) || argReader.isLongNull(1) ) {
// No nulls allowed. Throw exception
throw new UdfException(1234, "Cannot add a NULL value");
}
当 UDx 引发异常时,正在运行 UDx 的从属进程会将错误报告回 Vertica 并退出。Vertica 将向用户显示包含于异常中的错误消息和堆栈跟踪:
=> SELECT add2ints(2, NULL);
ERROR 3399: Failure in UDx RPC call InvokeProcessBlock(): Error in User Defined Object [add2ints], error code: 1234
com.vertica.sdk.UdfException: Cannot add a NULL value
at com.example.Add2intsFactory$Add2ints.processBlock(Add2intsFactory.java:37)
at com.vertica.udxfence.UDxExecContext.processBlock(UDxExecContext.java:700)
at com.vertica.udxfence.UDxExecContext.run(UDxExecContext.java:173)
at java.lang.Thread.run(Thread.java:662)
在此示例中,如果其中一个实参小于 100,则 Python UDx 会引发错误:
while(True):
# Example of error checking best practices.
product_id = block_reader.getInt(2)
if product_id < 100:
raise ValueError("Invalid Product ID")
错误会生成如下消息:
=> SELECT add2ints(prod_cost, sale_price, product_id) FROM bunch_of_numbers;
ERROR 3399: Failure in UDx RPC call InvokeProcessBlock(): Error calling processBlock() in User Defined Object [add2ints]
at [/udx/PythonInterface.cpp:168], error code: 0,
message: Error [/udx/PythonInterface.cpp:385] function ['call_method']
(Python error type [<class 'ValueError'>])
Traceback (most recent call last):
File "/home/dbadmin/py_db/v_py_db_node0001_catalog/Libraries/02fc4af0ace6f91eefa74baecf3ef76000a0000000004fc4/pylib_02fc4af0ace6f91eefa74baecf3ef76000a0000000004fc4.py",
line 13, in processBlock
raise ValueError("Invalid Product ID")
ValueError: Invalid Product ID
在此示例中,如果数据帧的第三列与指定的产品 ID 不匹配,则 R UDx 会引发错误:
Calculate_Cost_w_Tax <- function(input.data.frame) {
# Must match the Product ID 11444
if ( !is.numeric(input.data.frame[, 3]) == 11444 ) {
stop("Invalid Product ID!")
} else {
cost_w_tax <- data.frame(input.data.frame[, 1] * input.data.frame[, 2])
}
return(cost_w_tax)
}
Calculate_Cost_w_TaxFactory <- function() {
list(name=Calculate_Cost_w_Tax,
udxtype=c("scalar"),
intype=c("float","float", "float"),
outtype=c("float"))
}
错误会生成如下消息:
=> SELECT Calculate_Cost_w_Tax(item_price, tax_rate, prod_id) FROM Inventory_Sales_Data;
vsql:sql_test_multiply.sql:21: ERROR 3399: Failure in UDx RPC call InvokeProcessBlock():
Error calling processBlock() in User Defined Object [mul] at
[/udx/RInterface.cpp:1308],
error code: 0, message: Exception in processBlock :Invalid Product ID!
要报告有关错误的其他诊断信息,您可以在引发异常之前将消息写入日志文件(请参阅日志)。
您的 UDx 不得使用其未引发的异常。拦截服务器异常可能会导致数据库不稳定。
每个使用 C++、Java 或 Python 编写的 UDx 都有一个关联的 ServerInterface
实例。ServerInterface
类提供了写入 Vertica 日志的功能,C++ 实施还提供了在系统表中记录事件的功能。
可以使用 ServerInterface.log()
函数将消息写入到日志文件。此函数的工作方式与 printf()
相似,它使用已设置格式的字符串和一个可选值集,并将字符串写入到日志文件。将消息写入到的位置取决于函数在隔离模式还是非隔离模式下运行。
在非隔离模式下运行的函数会将其消息写入到编录目录中的
vertica.log
文件。
在隔离模式下运行的函数会将其消息写入到编录目录中名为 UDxLogs/UDxFencedProcesses.log
的日志文件。
为了帮助标识函数的输出,Vertica 会将绑定到 UDx 的 SQL 函数名称添加到日志消息中。
以下示例将记录 UDx 的输入值:
virtual void processBlock(ServerInterface &srvInterface,
BlockReader &argReader,
BlockWriter &resWriter)
{
try {
// While we have inputs to process
do {
if (argReader.isNull(0) || argReader.isNull(1)) {
resWriter.setNull();
} else {
const vint a = argReader.getIntRef(0);
const vint b = argReader.getIntRef(1);
<span class="code-input">srvInterface.log("got a: %d and b: %d", (int) a, (int) b);</span>
resWriter.setInt(a+b);
}
resWriter.next();
} while (argReader.next());
} catch(std::exception& e) {
// Standard exception. Quit.
vt_report_error(0, "Exception while processing block: [%s]", e.what());
}
}
@Override
public void processBlock(ServerInterface srvInterface,
BlockReader argReader,
BlockWriter resWriter)
throws UdfException, DestroyInvocation
{
do {
// Get the two integer arguments from the BlockReader
long a = argReader.getLong(0);
long b = argReader.getLong(1);
// Log the input values
<span class="code-input">srvInterface.log("Got values a=%d and b=%d", a, b);</span>
long result = a+b;
resWriter.setLong(result);
resWriter.next();
} while (argReader.next());
}
}
def processBlock(self, server_interface, arg_reader, res_writer):
server_interface.log("Python UDx - Adding 2 ints!")
while(True):
first_int = block_reader.getInt(0)
second_int = block_reader.getInt(1)
block_writer.setInt(first_int + second_int)
<span class="code-input">server_interface.log("Values: first_int is {} second_int is {}".format(first_int, second_int))</span>
block_writer.next()
if not block_reader.next():
break
log()
函数在日志文件中生成条目,如下所示:
$ tail /home/dbadmin/py_db/v_py_db_node0001_catalog/UDxLogs/UDxFencedProcesses.log
07:52:12.862 [Python-v_py_db_node0001-7524:0x206c-40575] 0x7f70eee2f780 PythonExecContext::processBlock
07:52:12.862 [Python-v_py_db_node0001-7524:0x206c-40575] 0x7f70eee2f780 [UserMessage] add2ints - Python UDx - Adding 2 ints!
07:52:12.862 [Python-v_py_db_node0001-7524:0x206c-40575] 0x7f70eee2f780 [UserMessage] add2ints - Values: first_int is 100 second_int is 100
有关查看 Vertica 日志文件的详细信息,请参阅监控日志文件。
在 C++ API 中,除了写入日志之外,您还可以将消息写入 UDX_EVENTS 系统表。写入系统表之后,您可以将来自所有节点的事件收集至一个位置中。
可以使用 ServerInterface.logEvent()
函数将消息写入此表。该函数将提取一个实参和一个映射。映射作为 Flex VMap 写入表的 RAW 列。以下示例显示 Parquet 导出器如何创建和记录此映射。
// Log exported parquet file details to v_monitor.udx_events
std::map<std::string, std::string> details;
details["file"] = escapedPath;
details["created"] = create_timestamp_;
details["closed"] = close_timestamp_;
details["rows"] = std::to_string(num_rows_in_file);
details["row_groups"] = std::to_string(num_row_groups_in_file);
details["size_mb"] = std::to_string((double)outputStream->Tell()/(1024*1024));
srvInterface.logEvent(details);
您可以从 VMap 中选择单个字段,如下例所示。
=> SELECT __RAW__['file'] FROM UDX_EVENTS;
__RAW__
-----------------------------------------------------------------------------
/tmp/export_tmpzLkrKq3a/450c4213-v_vmart_node0001-139770732459776-0.parquet
/tmp/export_tmpzLkrKq3a/9df1c797-v_vmart_node0001-139770860660480-0.parquet
(2 rows)
或者,您可以定义一个视图,以便像列一样更轻松地直接查询字段。有关示例,请参阅 监控导出。