本章介绍GSQL语言如何对程序异常进行响应,以及如何让用户自定义异常处理的方式。异常是指运行时错误。 GSQL语言同时支持内置系统异常和用户自定义异常。 内置异常包括GSQL语言异常(例如超出范围的值,错误的数据类型和非法操作),以及其他TigerGraph组件或操作系统中出现的错误。
GSQL 还支持用户自定义异常响应,也称为异常处理。 本节介绍用户自定义异常的方法:
Copy #########################################################
## Exception Statements ##
declExceptStmt := EXCEPTION exceptVarName "(" errorCode ")"
exceptVarName := name
errorCode := integer
raiseStmt := RAISE exceptVarName [errorMsg]
errorMsg := "(" expr ")"
tryStmt := TRY queryBodyStmts EXCEPTION caseExceptBlock+
[elseExceptBlock] END ";"
caseExceptBlock := WHEN exceptVarName THEN queryBodyStmts
elseExceptBlock := ELSE queryBodyStmts
异常响应的默认方式
在执行查询期间发生异常时,默认响应如下:
如果使用的是RUN QUERY命令执行查询,则将显示错误消息。
如果是通过调用GET /query REST++ 接口的方式运行查询,则输出将是一个简单的JSON对象。 一些错误拥有错误代码(code)字段,另一些错误则没有改字段:
Unhandled Exception的输出结果(以REST 接口方式执行)
Copy {
"error": true,
"message": "<errorMsg>"
"code": "<errType><errorCode>"
}
下面的示例演示了两个常见的错误:数据类型错误和分母为零错误。 首先我们定义一个简单的查询,然后用查询的输入的参数除以100.0。
Copy CREATE QUERY excpBuiltin(INT n1) FOR GRAPH minimalNet {
PRINT 100.0/n1;
}
然后我们测试三个运算:
首先,我们使用GSQL接口进行测试。 当查询运行且没有错误时,则输出一个JSON格式的值。 但如果存在异常,则仅显示错误消息。
Copy GSQL > RUN QUERY excpBuiltin(7)
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"100.0/n1": 14.28571}]
}
GSQL > RUN QUERY excpBuiltin("a")
Values of parameter n1 must be INT64 type, invalid value [a] provided.
GSQL > RUN QUERY excpBuiltin(0)
Runtime Error: divider is zero.
将查询作为REST ++接口运行时情况略有不同。 输出的结果始终为JSON格式。
从TigerGraph v1.2开始,GET /query端点的格式发生了变化。 现在必须在/query之后指定图形名称:
/query/{graph_name}/{query_name}
Copy $curl -X GET "http://localhost:9000/query/minimalNet/excpBuiltin?n1=7"
{
"error": false,
"message": "",
"results": [
{
"100.0/n1": 14.28571
}
],
"version": {
"api": "v2",
"schema": 0
}
}
$curl -X GET "http://localhost:9000/query/minimalNet/excpBuiltin?n1=a"
{
"code": "REST-30000",
"error": true,
"message": "Values of parameter n1 must be INT64 type, invalid value [a] provided.",
"version": {
"api": "v2",
"schema": 0
}
}
$curl -X GET "http://localhost:9000/query/minimalNet/excpBuiltin?n1=0"
{
"error": true,
"message": "Runtime Error: divider is zero.",
"version": {
"api": "v2",
"schema": 0
}
}
自定义异常的响应方式
如果某种异常常发生在特定的语句块中,则查询的编写人员可以指定该响应的具体内容。
以下语句类型可用于指定用户自定义异常的条件或响应。
用EXCEPTION声明语句给用户自定义异常命名。
用TRY ... EXCEPTION语句在查询主体层语句中定义和应用一个用户自定义异常处理动作。无论在先前有或没有EXCEPTION和RAISE语句,该语句都能使用。
系统内置异常始终优先于用户自定义异常。因此,用户自定义异常只能用于捕获系统内置异常无法捕获的异常情况。这意味着系统内置异常最好用于检验GSQL图形查询语言的语法和语义是否合规,但对于某些特定用户的应用来说,这并不是一定满足需求。
EXCEPTION 的声明
Copy declExceptStmt := EXCEPTION exceptVarName "(" errorCode ")"
exceptVarName := name
errorCode := integer
要使用用户自定义的异常,我们必须首先声明它。 异常声明语句声明用户自定义异常的类型,名称和id。 自定义异常的id号的errorCode必须大于40,000。 小于40,000的数字预留给系统内置异常。异常语句必须放在累加器声明语句之后,任何查询主体层语句之前。 一个查询中,用户可以声明多个异常类型。
RAISE 语句
Copy raiseStmt := RAISE exceptVarName [errorMsg]
errorMsg := "(" expr ")"
RAISE语句用于判断用户自定义异常的发生。 示例中的exceptVarName必须是之前已经声明过的异常之一。 我们也可以选择是否需要自定义异常发生时报告的错误消息。 执行RAISE语句后,查询动作的工作流会发生变化。 如果RAISE语句不在TRY子句中,则查询以默认的响应结束,返回由该异常类型和该RAISE语句所定义的错误代码和错误消息。 如果RAISE语句在TRY子句中,则查询动作会跳转到TRY子句的EXCEPTION部分。
RAISE语句本身不包括定义异常的发生条件。 通常,用户会使用IF ... THEN语句实现该逻辑,并将RAISE语句放在THEN子句中。
在当前版本中,RAISE语句只能用作查询主体层的语句中,而不能用在DML子句中。 特别的,不能在SELECT语句中使用RAISE语句判断一个异常。
下面的示例定义并检查了两种类型的异常:空输入集(40001)和无匹配边(40002)。 请记住,系统允许的最小代码是40001。
Copy CREATE QUERY excpCountActivity(SET<VERTEX<person>> vSet, STRING eType) FOR GRAPH socialNet {
# Count how many edges there are from each member of the input person set to posts,
# along the specified edge type.
MapAccum<STRING,INT> @@allCount;
EXCEPTION emptyList (40001);
EXCEPTION noEdges (40002);
IF ISEMPTY(vSet) THEN ## Raise 40001
RAISE emptyList ("Error: Input parameter 'vSet' (type SET<VERTEX<person>>) is empty");
END;
Start = vSet;
Results = SELECT s
FROM Start:s -(:e)-> post:t
WHERE e.type == eType
ACCUM @@allCount += (t.subject -> 1);
IF Results.size() == 0 THEN ## Raise 40002
RAISE noEdges ("Error: No '" + eType + "' edges from the vertex set");
END;
PRINT @@allCount;
}
Copy // Valid input: no exceptions
$curl -X GET "http://localhost:9000/query/socialNet/excpCountActivity?vSet=person2&vSet=person6&eType=posted"
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{
"@@allCount": {
"cats": 1,
"tigergraph": 2
}
}]
}
// empty input set (due to spelling error in parameter name)
$curl -X GET "http://localhost:9000/query/socialNet/excpCountActivity?vset=person2&vset=person6&eType=posted"
{
"code": "40001",
"error": true,
"version": {
"schema": 0,
"api": "v2"
},
"message": "Error: Input parameter 'vSet' (type SET<VERTEX<person>>) is empty"
}
// no edges (due to unknown edge type)
$curl -X GET "http://localhost:9000/query/socialNet/excpCountActivity?vSet=person2&vSet=person6&eType=commented"
{
"code": "40002",
"error": true,
"version": {
"schema": 0,
"api": "v2"
},
"message": "Error: No 'commented' edges from the vertex set"
}
用TRY...EXCEPTION 语句自定义异常处理动作
Copy tryStmt := TRY queryBodyStmts EXCEPTION caseExceptBlock [elseExceptBlock] END ";"
caseExceptBlock := WHEN exceptVarName THEN queryBodyStmts+
elseExceptBlock := ELSE queryBodyStmts
TRY ... EXCEPTION语句允许用户自行定义异常处理动作,并将其添加到一个查询主体层语句中的某个语句块中。 TRY ...EXCEPTION语句可以嵌套在TRY语句块或EXCEPTION语句块中。
当前版本的GSQL不支持自定义处理系统内置的异常。 因此,如果发生系统内置的异常,它会忽略TRY..EXCEPTION语句而直接采取默认的处理方式,并将中止该查询。 在将来的更新中,我们有计划使得TRY ... EXCEPTION可以既支持自定义异常(RAISE)的处理,也支持内置异常的处理。
TRY ... EXCEPTION是一个嵌套两个语句块的复合句。第一个语句块(TRY)定义了自定义的错误处理动作,是一个查询主体层语句。第二个 (EXCEPTION)语句块包含了一系列的WHEN ... THEN子句。每个子句都指定一个异常类型,并表明了在发生该异常时需要采取的动作。用户还可以选择添加ELSE子句,用于处理所有其他的异常。下面的文字和流程图详细说明了TRY ...EXCEPTION的详细工作机制。
当TRY语句块中发生异常时,执行流程会跳过TRY的剩余部分并直接跳转到EXCEPTION语句块。这表明 GSQL正在寻求与该异常类型匹配的处理程序。在THEN或ELSE子句中执行完对应的处理后,执行过程将跳过EXCEPTION语句块的其余部分并继续执行END后面的语句。但是,如果没有匹配到合适的WHEN或ELSE处理程序,则向用户通告该异常,即在退出EXCEPTION之后,该查询仍然保持在RAISE的状态。如果该TRY ... EXCEPTION语句块是嵌套在另一个TRY语句块之内的,则该异常处理过程会在上层语句中重复执行,直到该异常最终被处理,或是所有的TRY ... EXCEPTION语句块都处理结束。
最后,如果未处理的异常不包含在一个TRY语句块内,则该查询将中止,输出该异常的默认处理响应。
情况 1: 如果在外层TRY语句中的条件1(cond1)为真,
激活A选项(即RAISE A),并直接跳转到外层EXCEPTION 语句块.
由ELSE HandStmtsZ 语句处理.
情况2: 如果在内层TRY语句中的条件2(cond2)为真,
激活A选项(即RAISE A),并直接跳转到内层的EXCEPTION 语句块.
由handStmtsX语句处理;
情况3: 如果在内层TRY语句中的条件3(cond3)为真,
激活B选项(即RAISE B),并跳转到内部的EXCEPTION语句块。由于此时没有找到合适的处理方法,所以该异常被传播,并同时跳转到外层的EXCEPTION语句块,并在此处被handStmtsY语句处理。
自定义处理的示例:
以下示例是改进过的的最短路径查询。它用于查找计算机网络中从源到目标的所有路径。它使用广度优先算法,当在深度N处找到至少一条路径时,它便停在N处,否则就遍历整个图形。下列三个条件会引发它的异常并使搜索中止:
发现某条边的连接速度极慢(同样的,由已损坏的数据导致)。
图形中未找到符合要求的路径(搜索已经结束,但我们不打印结果)。
注意,情况1和2并不代表负速度或慢速度的边存在于最短路径上,它只是表示该查询在搜索期间发现了损坏的边。另外,因为我们不能在SELECT语句中执行RAISE动作,所以我们采取了一个变通的办法:设置一个带有错误代码的整数变量,并在SELECT过程中实时将该整数变量与需要检测的RAISE异常进行比较。
Copy CREATE QUERY compPathValid (vertex<computer> src, vertex<computer> tgt, BOOL enExcp)
FOR GRAPH computerNet {
# Find valid paths in a computer network from a source to a target.
# Stop search once you have found some paths.
# 3 Exceptions: (1) Negative connection speed, (2) Slow connection speed, (3) No path.
# Set enExcp=true to raise exceptions. enExcp=false will find paths, good or bad.
OrAccum @@reached, @visited;
ListAccum<STRING> @paths;
DOUBLE minSpeed = 0.4;
INT err;
EXCEPTION negSpeed (40001);
EXCEPTION slowSpeed (40002);
EXCEPTION notReached (40003);
TRY
Start = {src};
# Initialize: path to src is itself.
Start = SELECT s
FROM Start:s
ACCUM s.@paths = s.id;
WHILE Start.size() != 0 AND NOT @@reached DO
Start = SELECT t
FROM Start:s -(:e)-> :t
WHERE t.@visited == false
ACCUM CASE
WHEN e.connectionSpeed < 0 THEN err = 1
WHEN e.connectionSpeed < minSpeed THEN err = 2
WHEN t == tgt THEN @@reached += true
END,
# List1 * List2 -> List(each elem of List1 concat w/each elem of List2)
t.@paths += (s.@paths * ["~"]) * [t.id]
POST-ACCUM t.@visited = true;
IF err == 1 AND enExcp THEN
RAISE negSpeed ("Negative Speed");
ELSE IF err == 2 AND enExcp THEN
RAISE slowSpeed ("Slow Speed");
END;
END; # WHILE
IF NOT @@reached AND enExcp THEN
RAISE notReached ("No path to target");
ELSE
Result = {tgt};
PRINT Result[Result.@paths]; // api v2
END;
EXCEPTION
WHEN negSpeed THEN PRINT "bad path: negative speed";
WHEN slowSpeed THEN PRINT "bad path: slow speed";
WHEN notReached THEN PRINT "no path from source to target";
END;
}
如附录D中的数据所示:
下面所示的5种情况的结果中:包含1个有效搜索和3种异常情况各一个。第5种与第4种情况相同,只不过第5种未启用异常处理。
Copy GSQL > RUN QUERY compPathValid("c10","c12",true)
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"Result": [{
"v_id": "c12",
"attributes": {"Result.@paths": ["c10~c11~c12"]},
"v_type": "computer"
}]}]
}
GSQL > RUN QUERY compPathValid("c1","c12",true)
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"bad path: negative speed": "bad path: negative speed"}]
}
GSQL > RUN QUERY compPathValid("c10","c13",true)
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"bad path: slow speed": "bad path: slow speed"}]
}
GSQL > RUN QUERY compPathValid("c24","c25",true)
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"no path from source to target": "no path from source to target"}]
}
GSQL > RUN QUERY compPathValid("c24","c25",false)
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"Result": [{
"v_id": "c25",
"attributes": {"Result.@paths": []},
"v_type": "computer"
}]}]
}
异常处理的流程图
以下的流程图详述了触发和处理异常的所有情况,同时包含了用户自定义异常和系统内置异常: