From ebd0d3c8133c62b5359100de5f1eec541e43da3b Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Tue, 6 Sep 2016 21:12:03 +0300 Subject: [PATCH] More context variables for error handlers, see CORE-1132 and CORE-2040. (#46) * Implement some parts of CORE-1132 and CORE-2040. * Change per Adriano's suggestion. --- doc/sql.extensions/README.context_variables | 104 +++++++++++++++++-- doc/sql.extensions/README.exception_handling | 19 ---- src/dsql/ExprNodes.cpp | 51 +++++++++ src/dsql/parse.y | 8 +- src/jrd/constants.h | 5 + src/jrd/exe.cpp | 24 +++++ src/jrd/exe.h | 2 + src/yvalve/keywords.cpp | 1 + 8 files changed, 187 insertions(+), 27 deletions(-) diff --git a/doc/sql.extensions/README.context_variables b/doc/sql.extensions/README.context_variables index 6a0b1dfe28..6ce54c6d09 100644 --- a/doc/sql.extensions/README.context_variables +++ b/doc/sql.extensions/README.context_variables @@ -12,7 +12,7 @@ CURRENT_CONNECTION / CURRENT_TRANSACTION (FB 1.5) statement is executed. Author: - Dmitry Yemanov + Dmitry Yemanov Syntax rules: CURRENT_CONNECTION / CURRENT_TRANSACTION @@ -40,7 +40,7 @@ ROW_COUNT (FB 1.5) Returns number of rows, affected by the last SQL statement. Author: - Dmitry Yemanov + Dmitry Yemanov Syntax rules: ROW_COUNT @@ -75,7 +75,7 @@ SQLCODE / GDSCODE (FB 1.5) Returns numeric error code for the active exception. Author: - Dmitry Yemanov + Dmitry Yemanov Syntax rules: SQLCODE / GDSCODE @@ -112,9 +112,6 @@ SQLCODE / GDSCODE (FB 1.5) variables contain zero, regardless of the exception handling block type. - See also: - README.exception_handling - INSERTING / UPDATING / DELETING (FB 1.5) ---------------------------------------- @@ -123,7 +120,7 @@ INSERTING / UPDATING / DELETING (FB 1.5) Determines type of row operation being executed. Author: - Dmitry Yemanov + Dmitry Yemanov Syntax rules: INSERTING / UPDATING / DELETING @@ -140,3 +137,96 @@ INSERTING / UPDATING / DELETING (FB 1.5) See also: README.universal_triggers + + +SQLSTATE (FB 2.5) +----------------- + + Function: + Returns character-encoded state for the active exception. + + Author: + Dmitry Yemanov + + Syntax rules: + SQLSTATE + + Type: + CHAR(5) CHARACTER SET ASCII + + Scope: + PSQL, context of the exception handling block. + + Example(s): + BEGIN + ... + WHEN SQLSTATE '44000' DO + EXCEPTION E_CHECK_VIOLATION; + WHEN ANY DO + EXECUTE PROCEDURE P_ANY_EXCEPTION(SQLSTATE); + END + + Note(s): + 1. SQLSTATE always evaluates to NULL outside the exception handling block. + + +EXCEPTION (FB 4.0) +------------------ + + Function: + Returns name of the active user-defined exception. + + Author: + Dmitry Yemanov + + Syntax rules: + EXCEPTION + + Type: + VARCHAR(63) CHARACTER SET UTF8 + + Scope: + PSQL, context of the exception handling block. + + Example(s): + BEGIN + ... + WHEN ANY DO + IF (EXCEPTION IS NOT NULL) + EXECUTE PROCEDURE P_USR_EXCEPTION(EXCEPTION); + ELSE + EXECUTE PROCEDURE P_SYS_EXCEPTION; + END + + Note(s): + 1. EXCEPTION always contains NULL outside the exception handling block. + 2. If system exception is thrown, EXCEPTION also contains NULL. + + +ERROR_MESSAGE (FB 4.0) +---------------------- + + Function: + Returns interpreted text for the active exception. + + Author: + Dmitry Yemanov + + Syntax rules: + ERROR_MESSAGE + + Type: + VARCHAR(1024) CHARACTER SET UTF8 + + Scope: + PSQL, context of the exception handling block. + + Example(s): + BEGIN + ... + WHEN ANY DO + EXECUTE PROCEDURE P_LOG_EXCEPTION(ERROR_MESSAGE); + END + + Note(s): + 1. ERROR_MESSAGE always contains NULL outside the exception handling block. diff --git a/doc/sql.extensions/README.exception_handling b/doc/sql.extensions/README.exception_handling index 70f8a47620..09c0cd2146 100644 --- a/doc/sql.extensions/README.exception_handling +++ b/doc/sql.extensions/README.exception_handling @@ -58,25 +58,6 @@ Exception re-raise semantics (FB 1.5) Evaluates to no-op if used outside the exception handling block. -Run-time error codes (FB 1.5) ------------------------------ - - Function: - Allows to get a numeric error code for the catched exception. - - Author: - Dmitry Yemanov - - Syntax rules: - SQLCODE / GDSCODE; - - Scope: - PSQL, context of the exception handling block - - See also: - README.context_variables - - Parameterized exceptions (FB 3.0) --------------------------------- diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index b82ed3315f..d8d15252ae 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -6037,6 +6037,14 @@ void InternalInfoNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc) desc->makeText(FB_SQLSTATE_LENGTH, ttype_ascii); break; + case INFO_TYPE_EXCEPTION: + desc->makeVarying(MAX_SQL_IDENTIFIER_LEN, ttype_metadata); + break; + + case INFO_TYPE_ERROR_MSG: + desc->makeVarying(MAX_ERROR_MSG_LENGTH, ttype_utf8); + break; + case INFO_TYPE_CONNECTION_ID: case INFO_TYPE_TRANSACTION_ID: case INFO_TYPE_ROWS_AFFECTED: @@ -6070,6 +6078,14 @@ void InternalInfoNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) desc->makeText(FB_SQLSTATE_LENGTH, ttype_ascii); break; + case INFO_TYPE_EXCEPTION: + desc->makeVarying(MAX_SQL_IDENTIFIER_LEN, ttype_metadata); + break; + + case INFO_TYPE_ERROR_MSG: + desc->makeVarying(MAX_ERROR_MSG_LENGTH, ttype_utf8); + break; + case INFO_TYPE_CONNECTION_ID: case INFO_TYPE_TRANSACTION_ID: case INFO_TYPE_ROWS_AFFECTED: @@ -6129,6 +6145,41 @@ dsc* InternalInfoNode::execute(thread_db* tdbb, jrd_req* request) const return &impure->vlu_desc; } + else if (infoType == INFO_TYPE_EXCEPTION) + { + if (request->req_last_xcp.success()) + return NULL; + + const SLONG xcpCode = request->req_last_xcp.as_xcpcode(); + + if (!xcpCode) + return NULL; + + MetaName xcpName; + MET_lookup_exception(tdbb, xcpCode, xcpName, NULL); + + if (xcpName.isEmpty()) + return NULL; + + dsc desc; + desc.makeText(xcpName.length(), ttype_metadata, (UCHAR*) xcpName.c_str()); + EVL_make_value(tdbb, &desc, impure); + + return &impure->vlu_desc; + } + else if (infoType == INFO_TYPE_ERROR_MSG) + { + if (request->req_last_xcp.success()) + return NULL; + + const string errorText = request->req_last_xcp.as_text(); + + dsc desc; + desc.makeText(errorText.length(), ttype_utf8, (UCHAR*) errorText.c_str()); + EVL_make_value(tdbb, &desc, impure); + + return &impure->vlu_desc; + } SLONG result32 = 0; SINT64 result64 = 0; diff --git a/src/dsql/parse.y b/src/dsql/parse.y index e27ec415c3..7d6ef8f5c4 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -592,6 +592,7 @@ using namespace Firebird; // tokens added for Firebird 4.0 %token CUME_DIST +%token ERROR_MESSAGE %token EXCLUDE %token FOLLOWING %token NTILE @@ -6838,6 +6839,10 @@ internal_info { $$ = newNode(MAKE_const_slong(INFO_TYPE_SQLSTATE)); } | ROW_COUNT { $$ = newNode(MAKE_const_slong(INFO_TYPE_ROWS_AFFECTED)); } + | EXCEPTION + { $$ = newNode(MAKE_const_slong(INFO_TYPE_EXCEPTION)); } + | ERROR_MESSAGE + { $$ = newNode(MAKE_const_slong(INFO_TYPE_ERROR_MSG)); } ; %type sql_string @@ -7969,8 +7974,9 @@ non_reserved_word | RDB_ROLE_IN_USE | RDB_SYSTEM_PRIVILEGE | SYSTEM + | ERROR_MESSAGE | TIES - ; +; %% diff --git a/src/jrd/constants.h b/src/jrd/constants.h index 822edd268b..739cd7e7c5 100644 --- a/src/jrd/constants.h +++ b/src/jrd/constants.h @@ -287,6 +287,8 @@ enum InfoType INFO_TYPE_ROWS_AFFECTED = 5, INFO_TYPE_TRIGGER_ACTION = 6, INFO_TYPE_SQLSTATE = 7, + INFO_TYPE_EXCEPTION = 8, + INFO_TYPE_ERROR_MSG = 9, MAX_INFO_TYPE }; @@ -452,4 +454,7 @@ const int OPT_STATIC_ITEMS = 64; const int WITH_GRANT_OPTION = 1; const int WITH_ADMIN_OPTION = 2; +// Max length of the string returned by ERROR_TEXT context variable +const USHORT MAX_ERROR_MSG_LENGTH = 1024 * METADATA_BYTES_PER_CHAR; // 1024 UTF-8 characters + #endif // JRD_CONSTANTS_H diff --git a/src/jrd/exe.cpp b/src/jrd/exe.cpp index eda1a78baf..faf09c1fac 100644 --- a/src/jrd/exe.cpp +++ b/src/jrd/exe.cpp @@ -192,6 +192,30 @@ void StatusXcp::as_sqlstate(char* sqlstate) const fb_sqlstate(sqlstate, status->getErrors()); } +SLONG StatusXcp::as_xcpcode() const +{ + return (status->getErrors()[1] == isc_except) ? (SLONG) status->getErrors()[3] : 0; +} + +string StatusXcp::as_text() const +{ + const ISC_STATUS* status_ptr = status->getErrors(); + + string errorText; + + TEXT buffer[BUFFER_LARGE]; + while (fb_interpret(buffer, sizeof(buffer), &status_ptr)) + { + if (errorText.hasData()) + errorText += "\n"; + + errorText += buffer; + } + + return errorText; +} + + static void execute_looper(thread_db*, jrd_req*, jrd_tra*, const StmtNode*, jrd_req::req_s); static void looper_seh(thread_db*, jrd_req*, const StmtNode*); static void release_blobs(thread_db*, jrd_req*); diff --git a/src/jrd/exe.h b/src/jrd/exe.h index ec29359e5b..46ccbcc2af 100644 --- a/src/jrd/exe.h +++ b/src/jrd/exe.h @@ -631,6 +631,8 @@ public: SLONG as_gdscode() const; SLONG as_sqlcode() const; void as_sqlstate(char*) const; + SLONG as_xcpcode() const; + Firebird::string as_text() const; }; // must correspond to the declared size of RDB$EXCEPTIONS.RDB$MESSAGE diff --git a/src/yvalve/keywords.cpp b/src/yvalve/keywords.cpp index 8be74b9b89..7c6f25e90c 100644 --- a/src/yvalve/keywords.cpp +++ b/src/yvalve/keywords.cpp @@ -181,6 +181,7 @@ static const TOK tokens[] = {END, "END", false}, {ENGINE, "ENGINE", true}, {ENTRY_POINT, "ENTRY_POINT", false}, + {ERROR_MESSAGE, "ERROR_MESSAGE", true}, {ESCAPE, "ESCAPE", false}, {EXCEPTION, "EXCEPTION", false}, {EXCLUDE, "EXCLUDE", false},