异常处理语句

本章介绍GSQL语言如何对程序异常进行响应,以及如何让用户自定义异常处理的方式。异常是指运行时错误。 GSQL语言同时支持内置系统异常和用户自定义异常。 内置异常包括GSQL语言异常(例如超出范围的值,错误的数据类型和非法操作),以及其他TigerGraph组件或操作系统中出现的错误。

GSQL 还支持用户自定义异常响应,也称为异常处理。 本节介绍用户自定义异常的方法:

#########################################################
## 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 接口方式执行)
{
 "error": true,
 "message": "<errorMsg>"
 "code": "<errType><errorCode>"
}

下面的示例演示了两个常见的错误:数据类型错误和分母为零错误。 首先我们定义一个简单的查询,然后用查询的输入的参数除以100.0。

例: query excpBuiltin
CREATE QUERY excpBuiltin(INT n1) FOR GRAPH minimalNet {
 PRINT 100.0/n1;
}

然后我们测试三个运算:

  1. 一个有效的输入 (例如 n1 = 7)

  2. 输入错误的数据类型 (例如n1 = "A")

  3. 除以零 (例如n1 = 0)

首先,我们使用GSQL接口进行测试。 当查询运行且没有错误时,则输出一个JSON格式的值。 但如果存在异常,则仅显示错误消息。

RUN QUERY的异常响应
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}

GET /query请求时的异常响应
$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声明语句给用户自定义异常命名。

  • 用RAISE语句来表示一个用户自定义异常的发生。

  • 用TRY ... EXCEPTION语句在查询主体层语句中定义和应用一个用户自定义异常处理动作。无论在先前有或没有EXCEPTION和RAISE语句,该语句都能使用。

系统内置异常始终优先于用户自定义异常。因此,用户自定义异常只能用于捕获系统内置异常无法捕获的异常情况。这意味着系统内置异常最好用于检验GSQL图形查询语言的语法和语义是否合规,但对于某些特定用户的应用来说,这并不是一定满足需求。

EXCEPTION 的声明

EXCEPTION 的声明
declExceptStmt := EXCEPTION exceptVarName "(" errorCode ")"
exceptVarName := name
errorCode     := integer

要使用用户自定义的异常,我们必须首先声明它。 异常声明语句声明用户自定义异常的类型,名称和id。 自定义异常的id号的errorCode必须大于40,000。 小于40,000的数字预留给系统内置异常。异常语句必须放在累加器声明语句之后,任何查询主体层语句之前。 一个查询中,用户可以声明多个异常类型。

RAISE 语句

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。

例: 未处理的用户自定义异常
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;
}
结果
// 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 语句自定义异常处理动作

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. 发现某条边的连接速度极慢(同样的,由已损坏的数据导致)。

  3. 图形中未找到符合要求的路径(搜索已经结束,但我们不打印结果)。

注意,情况1和2并不代表负速度或慢速度的边存在于最短路径上,它只是表示该查询在搜索期间发现了损坏的边。另外,因为我们不能在SELECT语句中执行RAISE动作,所以我们采取了一个变通的办法:设置一个带有错误代码的整数变量,并在SELECT过程中实时将该整数变量与需要检测的RAISE异常进行比较。

例:带有异常处理的路径搜索
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中的数据所示:

  • 任何通过了c1的搜索都将看到负边。

  • 任何通过了c12的搜索都会看到负边和慢边。

  • 任何通过了c14的搜索都会看到负边。

下面所示的5种情况的结果中:包含1个有效搜索和3种异常情况各一个。第5种与第4种情况相同,只不过第5种未启用异常处理。

compPathValid结果
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"
 }]}]
}

异常处理的流程图

以下的流程图详述了触发和处理异常的所有情况,同时包含了用户自定义异常和系统内置异常:

Last updated