本章介绍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"
}]}]
}
异常处理的流程图
以下的流程图详述了触发和处理异常的所有情况,同时包含了用户自定义异常和系统内置异常: