diff --git a/doc/README.isc_info_xxx b/doc/README.isc_info_xxx index 9a487be8b88..77909a295d2 100644 --- a/doc/README.isc_info_xxx +++ b/doc/README.isc_info_xxx @@ -96,3 +96,10 @@ New items for isc_transaction_info: 6. isc_info_tra_lock_timeout return lock timeout of current transaction + +New items for isc_dsql_sql_info: + +1. isc_info_sql_exec_path_nodes + return execution nodes tree formatted as XML. + The response can be span over multiple chunks to exceed the response + length limit. diff --git a/doc/README.isql_enhancements.txt b/doc/README.isql_enhancements.txt index b9a81f49a17..d827aa3ae1f 100644 --- a/doc/README.isql_enhancements.txt +++ b/doc/README.isql_enhancements.txt @@ -540,3 +540,11 @@ CON> where true; CONSTANT ============ 1 + +16) SET EXEC_PATH_DISPLAY NODES + +Retrieves the execution nodes tree of a DML statement formatted as XML. + +It requires server v6 or greater to work. + +SET EXEC_PATH_DISPLAY OFF disables both output of BLR and nodes tree. diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index 7d74b33b55f..1091c104739 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -306,7 +306,11 @@ void Printable::print(NodePrinter& printer) const { NodePrinter subPrinter(printer.getIndent() + 1); Firebird::string tag(internalPrint(subPrinter)); - printer.begin(tag); + Firebird::string attr; +#ifdef DEV_BUILD + attr.printf("addr=\"%p\"", this); +#endif + printer.begin(tag, attr.c_str()); printer.append(subPrinter); printer.end(); } diff --git a/src/dsql/NodePrinter.h b/src/dsql/NodePrinter.h index 0f042b6b79d..6eb28da5d7e 100644 --- a/src/dsql/NodePrinter.h +++ b/src/dsql/NodePrinter.h @@ -42,11 +42,16 @@ class NodePrinter } public: - void begin(const Firebird::string& s) + void begin(const Firebird::string& s, const char* attributes = nullptr) { printIndent(); text += "<"; text += s; + if (attributes != nullptr) + { + text += ' '; + text += attributes; + } text += ">\n"; ++indent; diff --git a/src/dsql/dsql.cpp b/src/dsql/dsql.cpp index 198c47e6fb4..2c6b01f0530 100644 --- a/src/dsql/dsql.cpp +++ b/src/dsql/dsql.cpp @@ -1072,6 +1072,34 @@ static void sql_info(thread_db* tdbb, } break; + case isc_info_sql_exec_path_nodes: + if (const Statement* stmt = dsqlRequest->getStatement()) + { + if (stmt->topNode) + { + NodePrinter printer; + stmt->topNode->print(printer); + + const UCHAR* p = reinterpret_cast(printer.getText().c_str()); + ULONG length = printer.getText().size(); + + while (length) + { + ULONG bufferLength = end_info - info - 4; + ULONG maxLength = MIN(bufferLength, MAX_USHORT); + ULONG segmentLength = MIN(length, maxLength); + + info = put_item(item, segmentLength, p, info, end_info); + if (!info) + return; + + p += segmentLength; + length -= segmentLength; + } + } + } + break; + case isc_info_sql_num_variables: case isc_info_sql_describe_vars: if (messageFound) diff --git a/src/include/firebird/impl/inf_pub.h b/src/include/firebird/impl/inf_pub.h index fcc46468873..4cdfea9d9f8 100644 --- a/src/include/firebird/impl/inf_pub.h +++ b/src/include/firebird/impl/inf_pub.h @@ -507,6 +507,7 @@ enum info_db_provider #define isc_info_sql_exec_path_blr_bytes 31 #define isc_info_sql_exec_path_blr_text 32 #define isc_info_sql_relation_schema 33 +#define isc_info_sql_exec_path_nodes 34 /*********************************/ /* SQL information return values */ diff --git a/src/include/firebird/impl/msg/isql.h b/src/include/firebird/impl/msg/isql.h index fc4a9619737..7f08b8cd5f2 100644 --- a/src/include/firebird/impl/msg/isql.h +++ b/src/include/firebird/impl/msg/isql.h @@ -208,3 +208,4 @@ FB_IMPL_MSG_SYMBOL(ISQL, 208, HLP_SETAUTOTERM, " SET AUTOTERM -- to FB_IMPL_MSG_SYMBOL(ISQL, 209, HLP_SETWIRESTATS, " SET WIRE_stats -- toggle display of wire (network) statistics") FB_IMPL_MSG_SYMBOL(ISQL, 210, USAGE_SEARCH_PATH, " -(se)arch_path set schema search path") FB_IMPL_MSG_SYMBOL(ISQL, 211, MSG_SCHEMAS, "Schemas:") +FB_IMPL_MSG_SYMBOL(ISQL, 212, HLP_SETEXECPATHDISPLAY, " SET EXEC_PATH_DISPLAY -- toggle display of query execution path") diff --git a/src/isql/isql.epp b/src/isql/isql.epp index daab2b9d08f..35eba748722 100644 --- a/src/isql/isql.epp +++ b/src/isql/isql.epp @@ -503,6 +503,13 @@ static Firebird::GlobalPtr TranParams; class SetValues { public: + enum ExecPathOptions : unsigned + { + OFF = 0x00, + BLR = 0x01, + NODES = 0x02 + }; + SetValues() { //ColList global_Cols; @@ -510,7 +517,7 @@ public: Echo = false; Time_display = false; Sqlda_display = false; - ExecPathDisplay[0] = 0; + ExecPathDisplay = ExecPathOptions::OFF; Stats = false; Autocommit = true; // Commit ddl Warnings = true; // Print warnings @@ -537,7 +544,7 @@ public: bool Echo; bool Time_display; bool Sqlda_display; - UCHAR ExecPathDisplay[10]; + unsigned ExecPathDisplay; bool Stats; bool Autocommit; // Commit ddl bool Warnings; // Print warnings @@ -4750,13 +4757,13 @@ static processing_state execSetDebugCommand() if (!DB) return SKIP; - const char* stmt = setValues.ExecPathDisplay[0] ? + const char* stmt = setValues.ExecPathDisplay ? "set debug option dsql_keep_blr = true" : "set debug option dsql_keep_blr = false"; DB->execute(fbStatus, nullptr, 0, stmt, isqlGlob.SQL_dialect, nullptr, nullptr, nullptr, nullptr); - if (setValues.ExecPathDisplay[0] && (fbStatus->getState() & Firebird::IStatus::STATE_ERRORS)) + if (setValues.ExecPathDisplay && (fbStatus->getState() & Firebird::IStatus::STATE_ERRORS)) { STDERROUT("SET EXEC_PATH_DISPLAY is not supported in this connection."); return FAIL; @@ -5015,17 +5022,17 @@ static processing_state frontend(const std::string& statement) if (node.arg.empty()) return ps_ERR; else if (node.arg == "OFF") - setValues.ExecPathDisplay[0] = 0; - else + setValues.ExecPathDisplay = SetValues::ExecPathOptions::OFF; + else if (node.arg == "BLR") { - static constexpr UCHAR execPath[] = {isc_info_sql_exec_path_blr_text}; - - if (node.arg != "BLR") - return ps_ERR; - - memcpy(setValues.ExecPathDisplay, execPath, FB_NELEM(execPath)); - setValues.ExecPathDisplay[FB_NELEM(execPath)] = 0; + setValues.ExecPathDisplay |= SetValues::ExecPathOptions::BLR; } + else if (node.arg == "NODES") + { + setValues.ExecPathDisplay |= SetValues::ExecPathOptions::NODES; + } + else + return ps_ERR; return execSetDebugCommand(); }, @@ -5673,7 +5680,7 @@ void ISQL_get_version(bool call_by_create_db) else isqlGlob.db_SQL_dialect = SQL_DIALECT_V5; - if (setValues.ExecPathDisplay[0]) + if (setValues.ExecPathDisplay) execSetDebugCommand(); } @@ -5825,6 +5832,24 @@ static processing_state print_sets() print_set("Access Plan only:", setValues.Planonly); print_set("Explain Access Plan:", setValues.ExplainPlan); + isqlGlob.printf("%-25s", "Execution path display:"); + if (setValues.ExecPathDisplay == SetValues::ExecPathOptions::OFF) + { + isqlGlob.printf("OFF"); + } + else + { + if (setValues.ExecPathDisplay & SetValues::ExecPathOptions::BLR) + { + isqlGlob.printf("BLR "); + } + if (setValues.ExecPathDisplay & SetValues::ExecPathOptions::NODES) + { + isqlGlob.printf("NODES"); + } + } + isqlGlob.printf(NEWLINE); + isqlGlob.printf("%-25s", "Display BLOB type:"); switch (setValues.Doblob) { @@ -5942,6 +5967,7 @@ static processing_state help(const TEXT* what) HLP_SETCOUNT, // SET COUNT -- toggle count of selected rows on/off HLP_SETMAXROWS, // SET MAXROWS [] -- toggle limit of selected rows to , zero is no limit HLP_SETECHO, // SET ECHO -- toggle command echo on/off + HLP_SETEXECPATHDISPLAY, // SET EXEC_PATH_DISPLAY -- toggle display of query execution path HLP_SETEXPLAIN, // SET EXPLAIN -- toggle display of query plan in the explained form HLP_SETHEADING, // SET HEADING -- toggle column titles display on/off HLP_SETKEEPTRAN, // SET KEEP_TRAN_params -- toggle to keep or not to keep text of following successful SET TRANSACTION statement @@ -6377,7 +6403,7 @@ static processing_state newdb(const TEXT* dbname, } } - if (setValues.ExecPathDisplay[0]) + if (setValues.ExecPathDisplay) execSetDebugCommand(); global_Stmt = NULL; @@ -8057,10 +8083,17 @@ static void process_exec_path() return; Firebird::Array pathBuffer; - pathBuffer.getBuffer(MAX_USHORT, false); + // One megabyte is not too much + pathBuffer.getBuffer(1024 * 1024, false); - for (const UCHAR* code = setValues.ExecPathDisplay; *code; ++code) + static constexpr UCHAR execPath[] = { isc_info_sql_exec_path_blr_text, isc_info_sql_exec_path_nodes }; + + for (unsigned i = 0; i < std::size(execPath); ++i) { + if ((setValues.ExecPathDisplay & (1 << i)) == 0) + continue; + + const UCHAR* code = &execPath[i]; global_Stmt->getInfo(fbStatus, 1, code, pathBuffer.getCount(), pathBuffer.begin()); if (ISQL_errmsg(fbStatus)) @@ -8076,24 +8109,25 @@ static void process_exec_path() { const USHORT len = (USHORT) gds__vax_integer(ptr, sizeof(USHORT)); ptr += sizeof(USHORT); - pathString.assign((const char*) ptr, len); + pathString.append((const char*) ptr, len); ptr += len; } else if (tag == isc_info_end) break; else if (tag == isc_info_truncated) { - pathString = "* error: overflow *\n"; + pathString += "* error: overflow *\n"; break; } else - pathString = "* unknown error *\n"; + pathString += "* unknown error *\n"; } if (pathString.hasData()) { IUTILS_printf2(Diag, "%sExecution path (%s):%s%s%s", NEWLINE, (*code == isc_info_sql_exec_path_blr_text ? "BLR" : + *code == isc_info_sql_exec_path_nodes ? "Nodes" : "* unknown *" ), NEWLINE, NEWLINE, @@ -8538,7 +8572,7 @@ static processing_state process_statement(const std::string& str) return ret; // do not execute } - if (setValues.ExecPathDisplay[0]) + if (setValues.ExecPathDisplay) process_exec_path(); // If the statement isn't a select, execute it and be done