diff --git a/builds/posix/make.shared.variables b/builds/posix/make.shared.variables index cccded7b52..bc4dfc5684 100644 --- a/builds/posix/make.shared.variables +++ b/builds/posix/make.shared.variables @@ -87,7 +87,8 @@ AllObjects += $(Profiler_Objects) # Engine Engine_Objects:= $(call dirObjects,jrd) $(call dirObjects,dsql) $(call dirObjects,jrd/extds) \ - $(call dirObjects,jrd/optimizer) $(call dirObjects,jrd/recsrc) $(call dirObjects,jrd/replication) $(call dirObjects,jrd/trace) \ + $(call dirObjects,jrd/optimizer) $(call dirObjects,jrd/recsrc) $(call dirObjects,jrd/replication) \ + $(call dirObjects,jrd/sys-packages) $(call dirObjects,jrd/trace) \ $(call makeObjects,lock,lock.cpp) Engine_Test_Objects:= $(call dirObjects,jrd/tests) diff --git a/builds/win32/msvc15/engine_static.vcxproj b/builds/win32/msvc15/engine_static.vcxproj index 1481b1cb79..cf2b6e60f1 100644 --- a/builds/win32/msvc15/engine_static.vcxproj +++ b/builds/win32/msvc15/engine_static.vcxproj @@ -161,6 +161,7 @@ + @@ -345,6 +346,7 @@ + @@ -513,4 +515,4 @@ - \ No newline at end of file + diff --git a/builds/win32/msvc15/engine_static.vcxproj.filters b/builds/win32/msvc15/engine_static.vcxproj.filters index 3bf420ac6d..d74ab8297a 100644 --- a/builds/win32/msvc15/engine_static.vcxproj.filters +++ b/builds/win32/msvc15/engine_static.vcxproj.filters @@ -378,6 +378,9 @@ JRD files + + JRD files + JRD files @@ -1073,6 +1076,9 @@ Header files + + Header files + Header files diff --git a/doc/README.isql_enhancements.txt b/doc/README.isql_enhancements.txt index 9350cf2e57..33d70a8720 100644 --- a/doc/README.isql_enhancements.txt +++ b/doc/README.isql_enhancements.txt @@ -324,3 +324,32 @@ RDB$RELATIONS | 59| | | | -- turn per-table stats off, using shortened name SQL> SET PER_TAB OFF; + + + +Isql enhancements in Firebird v6. +--------------------------------- + +EXPLAIN statement. + +Author: Adriano dos Santos Fernandes + +A new ISQL statement was created to easily show a query plan without execute it. + +Note: If SET STATS is ON, stats are still shown. + +Examples: + +SQL> explain select * from employees where id = ?; + +SQL> set term !; +SQL> +SQL> explain +CON> execute block +CON> as +CON> declare id integer; +CON> begin +CON> select id from employees where id = ? into id; +CON> end! +SQL> +SQL> set term ;! diff --git a/doc/sql.extensions/README.sql_package.md b/doc/sql.extensions/README.sql_package.md new file mode 100644 index 0000000000..b9a3dc6d95 --- /dev/null +++ b/doc/sql.extensions/README.sql_package.md @@ -0,0 +1,46 @@ +# RDB$SQL package (FB 6.0) + +`RDB$SQL` is a package with utility routines to work with dynamic SQL. + +## Procedure `EXPLAIN` + +`RDB$SQL.EXPLAIN` returns tabular information of a query's plan, without execute the query. + +Since `SQL` text generally is multi-line string and have quotes, you may use `` +(strings prefixed by `Q`) as a way to make escape easy. + +Input parameters: +- `SQL` type `BLOB SUB_TYPE TEXT CHARACTER SET UTF8 NOT NULL` - query statement + +Output parameters: +- `PLAN_LINE` type `INTEGER NOT NULL` - plan's line order +- `RECORD_SOURCE_ID` type `BIGINT NOT NULL` - record source id +- `PARENT_RECORD_SOURCE_ID` type `BIGINT` - parent record source id +- `LEVEL` type `INTEGER NOT NULL` - indentation level (may have gaps in relation to parent's level) +- `PACKAGE_NAME` type `RDB$PACKAGE_NAME` - package name of a stored procedure +- `OBJECT_NAME` type `RDB$RELATION_NAME` - object (table, procedure) name +- `ALIAS` type `RDB$RELATION_NAME` - alias name +- `RECORD_LENGTH` type `INTEGER` - record length for the record source +- `KEY_LENGTH` type `INTEGER` - key length for the record source +- `ACCESS_PATH` type `VARCHAR(255) CHARACTER SET UTF8 NOT NULL` - friendly plan description + +``` +select * + from rdb$sql.explain('select * from employees where id = ?'); +``` + +``` +select * + from rdb$sql.explain(q'{ + select * + from ( + select name from employees + union all + select name from customers + ) + where name = ? + }'); +``` + +# Authors +- Adriano dos Santos Fernandes diff --git a/src/common/classes/objects_array.h b/src/common/classes/objects_array.h index cfa34ff764..449173d09b 100644 --- a/src/common/classes/objects_array.h +++ b/src/common/classes/objects_array.h @@ -306,10 +306,16 @@ namespace Firebird return iterator(this, getCount()); } - iterator back() + T& front() { fb_assert(getCount() > 0); - return iterator(this, getCount() - 1); + return *begin(); + } + + T& back() + { + fb_assert(getCount() > 0); + return *iterator(this, getCount() - 1); } const_iterator begin() const diff --git a/src/common/unicode_util.cpp b/src/common/unicode_util.cpp index 40871dcfdb..41931e6489 100644 --- a/src/common/unicode_util.cpp +++ b/src/common/unicode_util.cpp @@ -1708,16 +1708,16 @@ UnicodeUtil::Utf16Collation* UnicodeUtil::Utf16Collation::create( ++secondKeyDataIt; } - unsigned backSize = commonKeys.back()->getCount(); + unsigned backSize = commonKeys.back().getCount(); if (common > backSize) - commonKeys.back()->append(secondKeyIt->begin() + backSize, common - backSize); + commonKeys.back().append(secondKeyIt->begin() + backSize, common - backSize); else if (common < backSize) { if (common == 0) commonKeys.push(*secondKeyIt); else - commonKeys.back()->resize(common); + commonKeys.back().resize(common); } if (++secondKeyIt != keySet.end()) diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 17e0b6f7e0..3d087ae1e4 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -2505,7 +2505,7 @@ column_constraint_def($addColumnClause) : constraint_name_opt column_constraint($addColumnClause) { if ($1) - $addColumnClause->constraints.back()->name = *$1; + $addColumnClause->constraints.back().name = *$1; } ; diff --git a/src/include/firebird/impl/msg/isql.h b/src/include/firebird/impl/msg/isql.h index bd62ec7947..9dda2f1003 100644 --- a/src/include/firebird/impl/msg/isql.h +++ b/src/include/firebird/impl/msg/isql.h @@ -201,3 +201,4 @@ FB_IMPL_MSG_SYMBOL(ISQL, 201, NO_PUBLICATION, "There is no publication @1 in thi FB_IMPL_MSG_SYMBOL(ISQL, 202, NO_PUBLICATIONS, "There is no publications in this database") FB_IMPL_MSG_SYMBOL(ISQL, 203, MSG_PUBLICATIONS, "Publications:") FB_IMPL_MSG_SYMBOL(ISQL, 204, MSG_PROCEDURES, "Procedures:") +FB_IMPL_MSG_SYMBOL(ISQL, 205, HLP_EXPLAIN, "EXPLAIN -- explain a query access plan") diff --git a/src/isql/isql.epp b/src/isql/isql.epp index 41f50c909e..22f514b68a 100644 --- a/src/isql/isql.epp +++ b/src/isql/isql.epp @@ -492,6 +492,7 @@ static processing_state get_statement(string&, const TEXT*); static bool get_numeric(const UCHAR*, USHORT, SSHORT*, SINT64*); static void print_set(const char* str, bool v); static processing_state print_sets(); +static processing_state explain(const TEXT*); static processing_state help(const TEXT*); static bool isyesno(const TEXT*); static processing_state newdb(TEXT*, const TEXT*, const TEXT*, int, const TEXT*, bool); @@ -587,6 +588,7 @@ public: KeepTranParams = true; TranParams->assign(DEFAULT_DML_TRANS_SQL); PerTableStats = false; + ExplainCommand = false; } ColList global_Cols; @@ -611,6 +613,7 @@ public: SCHAR ISQL_charset[MAXCHARSET_SIZE]; bool KeepTranParams; bool PerTableStats; + bool ExplainCommand; }; static SetValues setValues; @@ -5009,7 +5012,7 @@ static processing_state frontend(const TEXT* statement) { show, add, copy, blobview, output, shell, set, create, drop, connect, - edit, input, quit, exit, help, + edit, input, quit, exit, explain, help, #ifdef DEV_BUILD passthrough, #endif @@ -5039,6 +5042,7 @@ static processing_state frontend(const TEXT* statement) {FrontOptions::input, "INPUT", 2}, {FrontOptions::quit, "QUIT", 0}, {FrontOptions::exit, "EXIT", 0}, + {FrontOptions::explain, "EXPLAIN", 0}, {FrontOptions::help, "?", 0}, {FrontOptions::help, "HELP", 0} #ifdef DEV_BUILD @@ -5236,6 +5240,10 @@ static processing_state frontend(const TEXT* statement) ret = EXIT; break; + case FrontOptions::explain: + ret = explain(cmd + 7); + break; + case FrontOptions::help: ret = help(parms[1]); break; @@ -6501,6 +6509,20 @@ static processing_state print_sets() } +static processing_state explain(const TEXT* command) +{ + Firebird::AutoSetRestore autoExplainCommand(&setValues.ExplainCommand, true); + Firebird::AutoSetRestore autoPlanonly(&setValues.Planonly, true); + Firebird::AutoSetRestore autoPlan(&setValues.Plan, true); + Firebird::AutoSetRestore autoExplainPlan(&setValues.ExplainPlan, true); + Firebird::AutoSetRestore autoSqldaDisplay(&setValues.Sqlda_display, false); + + process_statement(command); + + return SKIP; +} + + static processing_state help(const TEXT* what) { /************************************** @@ -6523,6 +6545,7 @@ static processing_state help(const TEXT* what) HLP_BLOBED, // BLOBVIEW -- view BLOB in text editor HLP_EDIT, // EDIT [] -- edit SQL script file and execute HLP_EDIT2, // EDIT -- edit current command buffer and execute + HLP_EXPLAIN, // EXPLAIN -- explain a query access plan HLP_HELP, // HELP -- display this menu HLP_INPUT, // INput -- take input from the named SQL file HLP_OUTPUT, // OUTput [] -- write output to named file @@ -9121,10 +9144,18 @@ static processing_state process_statement(const TEXT* str2) statement_type != isc_info_sql_stmt_set_generator) { process_plan(); - if (setValues.Planonly && !is_selectable) + + if (setValues.ExplainCommand) { - return ret; // do not execute + if (setValues.Stats && (print_performance(perf_before) == ps_ERR)) + ret = ps_ERR; + + if (setValues.PerTableStats) + perTableStats->getStats(DB, false); } + + if (setValues.Planonly && (!is_selectable || setValues.ExplainCommand)) + return ret; // do not execute } if (setValues.ExecPathDisplay[0]) diff --git a/src/jrd/ProfilerManager.cpp b/src/jrd/ProfilerManager.cpp index 20a6d1a4be..f2c796cbc9 100644 --- a/src/jrd/ProfilerManager.cpp +++ b/src/jrd/ProfilerManager.cpp @@ -498,80 +498,31 @@ void ProfilerManager::prepareRecSource(thread_db* tdbb, Request* request, const fb_assert(profileStatement->definedCursors.exist(recordSource->getCursorId())); - struct PlanItem : PermanentStorage - { - explicit PlanItem(MemoryPool& p) - : PermanentStorage(p) - { - } + PlanEntry rootEntry; + recordSource->getPlan(tdbb, rootEntry, 0, true); - const AccessPath* recordSource = nullptr; - const AccessPath* parentRecordSource = nullptr; - string accessPath{getPool()}; - unsigned level = 0; - }; - - ObjectsArray planItems; - planItems.add().recordSource = recordSource; - - for (unsigned pos = 0; pos < planItems.getCount(); ++pos) - { - auto& planItem = planItems[pos]; - const auto thisRsb = planItem.recordSource; - - string& accessPath = planItem.accessPath; - thisRsb->print(tdbb, accessPath, true, 0, false); - - constexpr auto INDENT_MARKER = "\n "; - constexpr unsigned INDENT_COUNT = 4; - - if (accessPath.find(INDENT_MARKER) == 0) - { - unsigned pos = 0; - - do { - accessPath.erase(pos + 1, 4); - } while ((pos = accessPath.find(INDENT_MARKER, pos + 1)) != string::npos); - } - - if (accessPath.hasData() && accessPath[0] == '\n') - accessPath.erase(0, 1); - - Array children; - thisRsb->getChildren(children); - - unsigned level = planItem.level; - - if (const auto lastLinePos = accessPath.find_last_of('\n'); lastLinePos != string::npos) - level += (accessPath.find_first_not_of(' ', lastLinePos + 1) - lastLinePos + 1) / INDENT_COUNT; - - unsigned childPos = pos; - - for (const auto child : children) - { - auto& inserted = planItems.insert(++childPos); - inserted.recordSource = child; - inserted.parentRecordSource = thisRsb; - inserted.level = level + 1; - } - } + Array> flatPlan; + rootEntry.asFlatList(flatPlan); NonPooledMap idSequenceMap; auto sequencePtr = profileStatement->cursorNextSequence.getOrPut(recordSource->getCursorId()); - for (const auto& planItem : planItems) + for (const auto& [planEntry, parentPlanEntry] : flatPlan) { - const auto cursorId = planItem.recordSource->getCursorId(); - const auto recSourceId = planItem.recordSource->getRecSourceId(); + const auto cursorId = planEntry->accessPath->getCursorId(); + const auto recSourceId = planEntry->accessPath->getRecSourceId(); idSequenceMap.put(recSourceId, ++*sequencePtr); ULONG parentSequence = 0; - if (planItem.parentRecordSource) - parentSequence = *idSequenceMap.get(planItem.parentRecordSource->getRecSourceId()); + if (parentPlanEntry) + parentSequence = *idSequenceMap.get(parentPlanEntry->accessPath->getRecSourceId()); + + string accessPath; + planEntry->getDescriptionAsString(accessPath); currentSession->pluginSession->defineRecordSource(profileStatement->id, cursorId, - *sequencePtr, planItem.level, planItem.accessPath.c_str(), parentSequence); + *sequencePtr, planEntry->level, accessPath.c_str(), parentSequence); profileStatement->recSourceSequence.put(recSourceId, *sequencePtr); } diff --git a/src/jrd/RecordNumber.h b/src/jrd/RecordNumber.h index 642fe72405..61ea4d8a88 100644 --- a/src/jrd/RecordNumber.h +++ b/src/jrd/RecordNumber.h @@ -239,51 +239,33 @@ struct bid ULONG& bid_temp_id() { - // Make sure that compiler packed structure like we wanted - fb_assert(sizeof(*this) == 8); - return bid_internal.bid_temp_id(); } ULONG bid_temp_id() const { - // Make sure that compiler packed structure like we wanted - fb_assert(sizeof(*this) == 8); - return bid_internal.bid_temp_id(); } bool isEmpty() const { - // Make sure that compiler packed structure like we wanted - fb_assert(sizeof(*this) == 8); - return bid_quad.bid_quad_high == 0 && bid_quad.bid_quad_low == 0; } void clear() { - // Make sure that compiler packed structure like we wanted - fb_assert(sizeof(*this) == 8); - bid_quad.bid_quad_high = 0; bid_quad.bid_quad_low = 0; } void set_temporary(ULONG temp_id) { - // Make sure that compiler packed structure like we wanted - fb_assert(sizeof(*this) == 8); - clear(); bid_temp_id() = temp_id; } void set_permanent(USHORT relation_id, RecordNumber num) { - // Make sure that compiler packed structure like we wanted - fb_assert(sizeof(*this) == 8); - clear(); bid_internal.bid_relation_id = relation_id; num.bid_encode(&bid_internal); @@ -291,19 +273,18 @@ struct bid RecordNumber get_permanent_number() const { - // Make sure that compiler packed structure like we wanted - fb_assert(sizeof(*this) == 8); - RecordNumber temp; temp.bid_decode(&bid_internal); return temp; } + operator ISC_QUAD() const + { + return {ISC_LONG(bid_quad.bid_quad_high), bid_quad.bid_quad_low}; + } + bool operator == (const bid& other) const { - // Make sure that compiler packed structure like we wanted - fb_assert(sizeof(*this) == 8); - return bid_quad.bid_quad_high == other.bid_quad.bid_quad_high && bid_quad.bid_quad_low == other.bid_quad.bid_quad_low; } @@ -316,6 +297,8 @@ struct bid } }; +static_assert(sizeof(bid) == 8); // make sure that compiler packed structure like we wanted + } // namespace Jrd diff --git a/src/jrd/Relation.h b/src/jrd/Relation.h index 84ac8a22eb..ab721d5bc2 100644 --- a/src/jrd/Relation.h +++ b/src/jrd/Relation.h @@ -275,6 +275,11 @@ public: bool isVirtual() const; bool isView() const; + ObjectType getObjectType() const + { + return isView() ? obj_view : obj_relation; + } + bool isReplicating(thread_db* tdbb); // global temporary relations attributes diff --git a/src/jrd/Statement.cpp b/src/jrd/Statement.cpp index 71aaacb840..77cf4245b4 100644 --- a/src/jrd/Statement.cpp +++ b/src/jrd/Statement.cpp @@ -730,6 +730,15 @@ string Statement::getPlan(thread_db* tdbb, bool detailed) const return plan; } +void Statement::getPlan(thread_db* tdbb, PlanEntry& planEntry) const +{ + planEntry.className = "Statement"; + planEntry.level = 0; + + for (const auto select : fors) + select->getPlan(tdbb, planEntry.children.add(), 0, true); +} + // Check that we have enough rights to access all resources this list of triggers touches. void Statement::verifyTriggerAccess(thread_db* tdbb, jrd_rel* ownerRelation, TrigVector* triggers, MetaName userName) diff --git a/src/jrd/Statement.h b/src/jrd/Statement.h index e046537778..da27d6ff88 100644 --- a/src/jrd/Statement.h +++ b/src/jrd/Statement.h @@ -28,6 +28,8 @@ namespace Jrd { +class PlanEntry; + // Compiled statement. class Statement : public pool_alloc { @@ -78,6 +80,7 @@ public: void release(thread_db* tdbb); Firebird::string getPlan(thread_db* tdbb, bool detailed) const; + void getPlan(thread_db* tdbb, PlanEntry& planEntry) const; private: static void verifyTriggerAccess(thread_db* tdbb, jrd_rel* ownerRelation, TrigVector* triggers, diff --git a/src/jrd/SystemPackages.cpp b/src/jrd/SystemPackages.cpp index bc4fd6a929..e61e2c2e43 100644 --- a/src/jrd/SystemPackages.cpp +++ b/src/jrd/SystemPackages.cpp @@ -25,6 +25,7 @@ #include "../jrd/BlobUtil.h" #include "../jrd/TimeZone.h" #include "../jrd/ProfilerManager.h" +#include "../jrd/sys-packages/SqlPackage.h" using namespace Firebird; using namespace Jrd; @@ -40,6 +41,7 @@ namespace list->add(TimeZonePackage(pool)); list->add(ProfilerPackage(pool)); list->add(BlobUtilPackage(pool)); + list->add(SqlPackage(pool)); } static InitInstance INSTANCE; diff --git a/src/jrd/ods.h b/src/jrd/ods.h index 5ede9100dc..1f5af9a1fa 100644 --- a/src/jrd/ods.h +++ b/src/jrd/ods.h @@ -146,6 +146,7 @@ const USHORT ODS_11_2 = ENCODE_ODS(ODS_VERSION11, 2); const USHORT ODS_12_0 = ENCODE_ODS(ODS_VERSION12, 0); const USHORT ODS_13_0 = ENCODE_ODS(ODS_VERSION13, 0); const USHORT ODS_13_1 = ENCODE_ODS(ODS_VERSION13, 1); +const USHORT ODS_13_2 = ODS_13_1; //// FIXME: Review - ODS for v6. const USHORT ODS_FIREBIRD_FLAG = 0x8000; diff --git a/src/jrd/optimizer/Optimizer.cpp b/src/jrd/optimizer/Optimizer.cpp index 4c09fc8fb0..6edfe205a0 100644 --- a/src/jrd/optimizer/Optimizer.cpp +++ b/src/jrd/optimizer/Optimizer.cpp @@ -1970,7 +1970,7 @@ unsigned Optimizer::distributeEqualities(BoolExprNodeStack& orgStack, unsigned b ValueExprNodeStack& s = classes.add(); s.push(node1); s.push(node2); - eq_class = classes.back(); + eq_class = --classes.end(); } } diff --git a/src/jrd/recsrc/AggregatedStream.cpp b/src/jrd/recsrc/AggregatedStream.cpp index 4cfd13a374..03c7585fb1 100644 --- a/src/jrd/recsrc/AggregatedStream.cpp +++ b/src/jrd/recsrc/AggregatedStream.cpp @@ -375,21 +375,23 @@ AggregatedStream::AggregatedStream(thread_db* tdbb, CompilerScratch* csb, Stream fb_assert(map); } -void AggregatedStream::getChildren(Array& children) const +void AggregatedStream::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { - children.add(m_next); + m_next->getLegacyPlan(tdbb, plan, level); } -void AggregatedStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void AggregatedStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) - { - plan += printIndent(++level) + "Aggregate"; - printOptInfo(plan); - } + planEntry.className = "AggregatedStream"; + + planEntry.description.add() = "Aggregate"; + printOptInfo(planEntry.description); if (recurse) - m_next->print(tdbb, plan, detailed, level, recurse); + { + ++level; + m_next->getPlan(tdbb, planEntry.children.add(), level, recurse); + } } bool AggregatedStream::internalGetRecord(thread_db* tdbb) const diff --git a/src/jrd/recsrc/BitmapTableScan.cpp b/src/jrd/recsrc/BitmapTableScan.cpp index acbf119175..43d330e555 100644 --- a/src/jrd/recsrc/BitmapTableScan.cpp +++ b/src/jrd/recsrc/BitmapTableScan.cpp @@ -122,32 +122,32 @@ bool BitmapTableScan::internalGetRecord(thread_db* tdbb) const return false; } -void BitmapTableScan::getChildren(Array& children) const +void BitmapTableScan::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { + if (!level) + plan += "("; + + plan += printName(tdbb, m_alias, false) + " INDEX ("; + string indices; + printInversion(tdbb, m_inversion, indices, false, level); + plan += indices + ")"; + + if (!level) + plan += ")"; } -void BitmapTableScan::print(thread_db* tdbb, string& plan, - bool detailed, unsigned level, bool recurse) const +void BitmapTableScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) - { - plan += printIndent(++level) + "Table " + - printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Access By ID"; + planEntry.className = "BitmapTableScan"; - printOptInfo(plan); - printInversion(tdbb, m_inversion, plan, true, level); - } - else - { - if (!level) - plan += "("; + planEntry.description.add() = "Table " + printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Access By ID"; + printOptInfo(planEntry.description); - plan += printName(tdbb, m_alias, false) + " INDEX ("; - string indices; - printInversion(tdbb, m_inversion, indices, false, level); - plan += indices + ")"; + printInversion(tdbb, m_inversion, planEntry.description, true); - if (!level) - plan += ")"; - } + planEntry.objectType = m_relation->getObjectType(); + planEntry.objectName = m_relation->rel_name; + + if (m_alias.hasData() && m_relation->rel_name != m_alias) + planEntry.alias = m_alias; } diff --git a/src/jrd/recsrc/BufferedStream.cpp b/src/jrd/recsrc/BufferedStream.cpp index 9711567199..05c371bad7 100644 --- a/src/jrd/recsrc/BufferedStream.cpp +++ b/src/jrd/recsrc/BufferedStream.cpp @@ -314,24 +314,25 @@ WriteLockResult BufferedStream::lockRecord(thread_db* tdbb, bool skipLocked) con return m_next->lockRecord(tdbb, skipLocked); } -void BufferedStream::getChildren(Array& children) const +void BufferedStream::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { - children.add(m_next); + m_next->getLegacyPlan(tdbb, plan, level); } -void BufferedStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void BufferedStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) - { - string extras; - extras.printf(" (record length: %" ULONGFORMAT")", m_format->fmt_length); + planEntry.className = "BufferedStream"; - plan += printIndent(++level) + "Record Buffer" + extras; - printOptInfo(plan); - } + string extras; + extras.printf(" (record length: %" ULONGFORMAT")", m_format->fmt_length); + + planEntry.description.add() = "Record Buffer" + extras; + printOptInfo(planEntry.description); + + planEntry.recordLength = m_format->fmt_length; if (recurse) - m_next->print(tdbb, plan, detailed, level, recurse); + m_next->getPlan(tdbb, planEntry.children.add(), ++level, recurse); } void BufferedStream::markRecursive() diff --git a/src/jrd/recsrc/ConditionalStream.cpp b/src/jrd/recsrc/ConditionalStream.cpp index 0af771986a..2465e921e0 100644 --- a/src/jrd/recsrc/ConditionalStream.cpp +++ b/src/jrd/recsrc/ConditionalStream.cpp @@ -114,38 +114,33 @@ WriteLockResult ConditionalStream::lockRecord(thread_db* tdbb, bool skipLocked) return impure->irsb_next->lockRecord(tdbb, skipLocked); } -void ConditionalStream::getChildren(Array& children) const +void ConditionalStream::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { - children.add(m_first); - children.add(m_second); + if (!level) + plan += "("; + + m_first->getLegacyPlan(tdbb, plan, level + 1); + + plan += ", "; + + m_second->getLegacyPlan(tdbb, plan, level + 1); + + if (!level) + plan += ")"; } -void ConditionalStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void ConditionalStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) + planEntry.className = "ConditionalStream"; + + planEntry.description.add() = "Condition"; + printOptInfo(planEntry.description); + + if (recurse) { - plan += printIndent(++level) + "Condition"; - printOptInfo(plan); - - if (recurse) - { - m_first->print(tdbb, plan, true, level, recurse); - m_second->print(tdbb, plan, true, level, recurse); - } - } - else - { - if (!level) - plan += "("; - - m_first->print(tdbb, plan, false, level + 1, recurse); - - plan += ", "; - - m_second->print(tdbb, plan, false, level + 1, recurse); - - if (!level) - plan += ")"; + ++level; + m_first->getPlan(tdbb, planEntry.children.add(), level, recurse); + m_second->getPlan(tdbb, planEntry.children.add(), level, recurse); } } diff --git a/src/jrd/recsrc/Cursor.cpp b/src/jrd/recsrc/Cursor.cpp index 4523889751..2f5b9381f3 100644 --- a/src/jrd/recsrc/Cursor.cpp +++ b/src/jrd/recsrc/Cursor.cpp @@ -107,48 +107,42 @@ void Select::initializeInvariants(Request* request) const } } -void Select::print(thread_db* tdbb, Firebird::string& plan, bool detailed, unsigned level, bool recurse) const +void Select::getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const { - if (detailed) + plan += "\nPLAN "; + m_root->getLegacyPlan(tdbb, plan, level); +} + +void Select::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const +{ + planEntry.className = "Select"; + + if (m_rse->isSubQuery()) { - if (m_rse->isSubQuery()) - { - plan += "\nSub-query"; + planEntry.description.add() = "Sub-query"; - if (m_rse->isInvariant()) - plan += " (invariant)"; - } - else if (m_cursorName.hasData()) - { - plan += "\nCursor \"" + string(m_cursorName) + "\""; + if (m_rse->isInvariant()) + planEntry.description.back() += " (invariant)"; + } + else if (m_cursorName.hasData()) + { + planEntry.description.add() = "Cursor \"" + string(m_cursorName) + "\""; - if (m_rse->isScrollable()) - plan += " (scrollable)"; - } - else - plan += "\nSelect Expression"; - - if (m_line || m_column) - { - string pos; - pos.printf(" (line %u, column %u)", m_line, m_column); - plan += pos; - } + if (m_rse->isScrollable()) + planEntry.description.back() += " (scrollable)"; } else - { - if (m_line || m_column) - { - string pos; - pos.printf("\n-- line %u, column %u", m_line, m_column); - plan += pos; - } + planEntry.description.add() = "Select Expression"; - plan += "\nPLAN "; + if (m_line || m_column) + { + string pos; + pos.printf(" (line %u, column %u)", m_line, m_column); + planEntry.description.back() += pos; } if (recurse) - m_root->print(tdbb, plan, detailed, level, true); + m_root->getPlan(tdbb, planEntry.children.add(), level + 1, recurse); } // --------------------- diff --git a/src/jrd/recsrc/Cursor.h b/src/jrd/recsrc/Cursor.h index 68d3d14261..886427dba2 100644 --- a/src/jrd/recsrc/Cursor.h +++ b/src/jrd/recsrc/Cursor.h @@ -68,22 +68,27 @@ namespace Jrd void initializeInvariants(Request* request) const; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; + void printPlan(thread_db* tdbb, Firebird::string& plan, bool detailed) const { - print(tdbb, plan, detailed, 0, true); - } - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; - - void getChildren(Firebird::Array& children) const override - { - children.add(m_root); + if (detailed) + { + PlanEntry planEntry; + getPlan(tdbb, planEntry, 0, true); + planEntry.asString(plan); + } + else + getLegacyPlan(tdbb, plan, 0); } virtual void open(thread_db* tdbb) const = 0; virtual void close(thread_db* tdbb) const = 0; + protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, + unsigned level, bool recurse) const override; + protected: const RecordSource* const m_root; const RseNode* const m_rse; diff --git a/src/jrd/recsrc/ExternalTableScan.cpp b/src/jrd/recsrc/ExternalTableScan.cpp index 4137a7a5e0..3d3a5e2115 100644 --- a/src/jrd/recsrc/ExternalTableScan.cpp +++ b/src/jrd/recsrc/ExternalTableScan.cpp @@ -115,27 +115,27 @@ WriteLockResult ExternalTableScan::lockRecord(thread_db* tdbb, bool skipLocked) status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); } -void ExternalTableScan::getChildren(Array& children) const +void ExternalTableScan::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { + if (!level) + plan += "("; + + plan += printName(tdbb, m_alias, false) + " NATURAL"; + + if (!level) + plan += ")"; } -void ExternalTableScan::print(thread_db* tdbb, string& plan, - bool detailed, unsigned level, bool recurse) const +void ExternalTableScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) - { - plan += printIndent(++level) + "Table " + - printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Full Scan"; - printOptInfo(plan); - } - else - { - if (!level) - plan += "("; + planEntry.className = "ExternalTableScan"; - plan += printName(tdbb, m_alias, false) + " NATURAL"; + planEntry.description.add() = "Table " + printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Full Scan"; + printOptInfo(planEntry.description); - if (!level) - plan += ")"; - } + planEntry.objectType = m_relation->getObjectType(); + planEntry.objectName = m_relation->rel_name; + + if (m_alias.hasData() && m_relation->rel_name != m_alias) + planEntry.alias = m_alias; } diff --git a/src/jrd/recsrc/FilteredStream.cpp b/src/jrd/recsrc/FilteredStream.cpp index aaa98aa8fc..51f1d84487 100644 --- a/src/jrd/recsrc/FilteredStream.cpp +++ b/src/jrd/recsrc/FilteredStream.cpp @@ -116,25 +116,24 @@ WriteLockResult FilteredStream::lockRecord(thread_db* tdbb, bool skipLocked) con return m_next->lockRecord(tdbb, skipLocked); } -void FilteredStream::getChildren(Array& children) const +void FilteredStream::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { - children.add(m_next); + m_next->getLegacyPlan(tdbb, plan, level); } -void FilteredStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void FilteredStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) - { - plan += printIndent(++level) + "Filter"; + planEntry.className = "FilteredStream"; - if (m_invariant) - plan += " (preliminary)"; + planEntry.description.add() = "Filter"; - printOptInfo(plan); - } + if (m_invariant) + planEntry.description.back() += " (preliminary)"; + + printOptInfo(planEntry.description); if (recurse) - m_next->print(tdbb, plan, detailed, level, recurse); + m_next->getPlan(tdbb, planEntry.children.add(), ++level, recurse); } void FilteredStream::markRecursive() diff --git a/src/jrd/recsrc/FirstRowsStream.cpp b/src/jrd/recsrc/FirstRowsStream.cpp index 19df50092f..0c6dbb915b 100644 --- a/src/jrd/recsrc/FirstRowsStream.cpp +++ b/src/jrd/recsrc/FirstRowsStream.cpp @@ -120,21 +120,23 @@ WriteLockResult FirstRowsStream::lockRecord(thread_db* tdbb, bool skipLocked) co return m_next->lockRecord(tdbb, skipLocked); } -void FirstRowsStream::getChildren(Array& children) const +void FirstRowsStream::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { - children.add(m_next); + m_next->getLegacyPlan(tdbb, plan, level); } -void FirstRowsStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void FirstRowsStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) - { - plan += printIndent(++level) + "First N Records"; - printOptInfo(plan); - } + planEntry.className = "FirstRowsStream"; + + planEntry.description.add() = "First N Records"; + printOptInfo(planEntry.description); if (recurse) - m_next->print(tdbb, plan, detailed, level, recurse); + { + ++level; + m_next->getPlan(tdbb, planEntry.children.add(), level, recurse); + } } void FirstRowsStream::markRecursive() diff --git a/src/jrd/recsrc/FullOuterJoin.cpp b/src/jrd/recsrc/FullOuterJoin.cpp index ae87b72db2..e6c7a4c435 100644 --- a/src/jrd/recsrc/FullOuterJoin.cpp +++ b/src/jrd/recsrc/FullOuterJoin.cpp @@ -112,32 +112,28 @@ WriteLockResult FullOuterJoin::lockRecord(thread_db* tdbb, bool skipLocked) cons status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); } -void FullOuterJoin::getChildren(Array& children) const +void FullOuterJoin::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { - children.add(m_arg1); - children.add(m_arg2); + level++; + plan += "JOIN ("; + m_arg1->getLegacyPlan(tdbb, plan, level); + plan += ", "; + m_arg2->getLegacyPlan(tdbb, plan, level); + plan += ")"; } -void FullOuterJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void FullOuterJoin::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) - { - plan += printIndent(++level) + "Full Outer Join"; + planEntry.className = "FullOuterJoin"; - if (recurse) - { - m_arg1->print(tdbb, plan, true, level, recurse); - m_arg2->print(tdbb, plan, true, level, recurse); - } - } - else + planEntry.description.add() = "Full Outer Join"; + printOptInfo(planEntry.description); + + if (recurse) { - level++; - plan += "JOIN ("; - m_arg1->print(tdbb, plan, false, level, recurse); - plan += ", "; - m_arg2->print(tdbb, plan, false, level, recurse); - plan += ")"; + ++level; + m_arg1->getPlan(tdbb, planEntry.children.add(), level, recurse); + m_arg2->getPlan(tdbb, planEntry.children.add(), level, recurse); } } diff --git a/src/jrd/recsrc/FullTableScan.cpp b/src/jrd/recsrc/FullTableScan.cpp index 3cdc94f450..05b1f5da98 100644 --- a/src/jrd/recsrc/FullTableScan.cpp +++ b/src/jrd/recsrc/FullTableScan.cpp @@ -162,44 +162,45 @@ bool FullTableScan::internalGetRecord(thread_db* tdbb) const return false; } -void FullTableScan::getChildren(Array& children) const +void FullTableScan::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { + if (!level) + plan += "("; + + plan += printName(tdbb, m_alias, false) + " NATURAL"; + + if (!level) + plan += ")"; } -void FullTableScan::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void FullTableScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) + planEntry.className = "FullTableScan"; + + auto lowerBounds = 0, upperBounds = 0; + for (const auto range : m_dbkeyRanges) { - auto lowerBounds = 0, upperBounds = 0; - for (const auto range : m_dbkeyRanges) - { - if (range->lower) - lowerBounds++; + if (range->lower) + lowerBounds++; - if (range->upper) - upperBounds++; - } - - string bounds; - if (lowerBounds && upperBounds) - bounds += " (lower bound, upper bound)"; - else if (lowerBounds) - bounds += " (lower bound)"; - else if (upperBounds) - bounds += " (upper bound)"; - - plan += printIndent(++level) + "Table " + - printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Full Scan" + bounds; - printOptInfo(plan); + if (range->upper) + upperBounds++; } - else - { - if (!level) - plan += "("; - plan += printName(tdbb, m_alias, false) + " NATURAL"; + string bounds; + if (lowerBounds && upperBounds) + bounds += " (lower bound, upper bound)"; + else if (lowerBounds) + bounds += " (lower bound)"; + else if (upperBounds) + bounds += " (upper bound)"; - if (!level) - plan += ")"; - } + planEntry.description.add() = "Table " + printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Full Scan" + bounds; + printOptInfo(planEntry.description); + + planEntry.objectType = m_relation->getObjectType(); + planEntry.objectName = m_relation->rel_name; + + if (m_alias.hasData() && m_relation->rel_name != m_alias) + planEntry.alias = m_alias; } diff --git a/src/jrd/recsrc/HashJoin.cpp b/src/jrd/recsrc/HashJoin.cpp index e3d21b608b..67c65b6407 100644 --- a/src/jrd/recsrc/HashJoin.cpp +++ b/src/jrd/recsrc/HashJoin.cpp @@ -454,43 +454,37 @@ WriteLockResult HashJoin::lockRecord(thread_db* /*tdbb*/, bool /*skipLocked*/) c status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); } -void HashJoin::getChildren(Array& children) const +void HashJoin::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { - children.add(m_leader.source); - + level++; + plan += "HASH ("; + m_leader.source->getLegacyPlan(tdbb, plan, level); + plan += ", "; for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) - children.add(m_args[i].source); + { + if (i) + plan += ", "; + + m_args[i].source->getLegacyPlan(tdbb, plan, level); + } + plan += ")"; } -void HashJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void HashJoin::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) + planEntry.className = "HashJoin"; + + planEntry.description.add() = "Hash Join (inner)"; + printOptInfo(planEntry.description); + + if (recurse) { - plan += printIndent(++level) + "Hash Join (inner)"; - printOptInfo(plan); + ++level; - if (recurse) - { - m_leader.source->print(tdbb, plan, true, level, recurse); + m_leader.source->getPlan(tdbb, planEntry.children.add(), level, recurse); - for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) - m_args[i].source->print(tdbb, plan, true, level, recurse); - } - } - else - { - level++; - plan += "HASH ("; - m_leader.source->print(tdbb, plan, false, level, recurse); - plan += ", "; - for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) - { - if (i) - plan += ", "; - - m_args[i].source->print(tdbb, plan, false, level, recurse); - } - plan += ")"; + for (const auto& arg : m_args) + arg.source->getPlan(tdbb, planEntry.children.add(), level, recurse); } } diff --git a/src/jrd/recsrc/IndexTableScan.cpp b/src/jrd/recsrc/IndexTableScan.cpp index 53020e4814..d3172ac8a9 100644 --- a/src/jrd/recsrc/IndexTableScan.cpp +++ b/src/jrd/recsrc/IndexTableScan.cpp @@ -352,44 +352,45 @@ bool IndexTableScan::internalGetRecord(thread_db* tdbb) const return false; } -void IndexTableScan::getChildren(Array& children) const +void IndexTableScan::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { + if (!level) + plan += "("; + + plan += printName(tdbb, m_alias, false) + " ORDER "; + string index; + printInversion(tdbb, m_index, index, false, level); + plan += index; + + if (m_inversion) + { + plan += " INDEX ("; + string indices; + printInversion(tdbb, m_inversion, indices, false, level); + plan += indices + ")"; + } + + if (!level) + plan += ")"; } -void IndexTableScan::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void IndexTableScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) - { - plan += printIndent(++level) + "Table " + - printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Access By ID"; + planEntry.className = "IndexTableScan"; - printOptInfo(plan); - printInversion(tdbb, m_index, plan, true, level, true); + planEntry.description.add() = "Table " + printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Access By ID"; + printOptInfo(planEntry.description); - if (m_inversion) - printInversion(tdbb, m_inversion, plan, true, ++level); - } - else - { - if (!level) - plan += "("; + printInversion(tdbb, m_index, planEntry.description, true, true); - plan += printName(tdbb, m_alias, false) + " ORDER "; - string index; - printInversion(tdbb, m_index, index, false, level); - plan += index; + planEntry.objectType = m_relation->getObjectType(); + planEntry.objectName = m_relation->rel_name; - if (m_inversion) - { - plan += " INDEX ("; - string indices; - printInversion(tdbb, m_inversion, indices, false, level); - plan += indices + ")"; - } + if (m_alias.hasData() && m_relation->rel_name != m_alias) + planEntry.alias = m_alias; - if (!level) - plan += ")"; - } + if (m_inversion) + printInversion(tdbb, m_inversion, planEntry.description, true); } int IndexTableScan::compareKeys(const index_desc* idx, diff --git a/src/jrd/recsrc/LocalTableStream.cpp b/src/jrd/recsrc/LocalTableStream.cpp index 86e2df624e..64e739aeee 100644 --- a/src/jrd/recsrc/LocalTableStream.cpp +++ b/src/jrd/recsrc/LocalTableStream.cpp @@ -71,10 +71,6 @@ void LocalTableStream::close(thread_db* tdbb) const impure->irsb_flags &= ~irsb_open; } -void LocalTableStream::getChildren(Array& children) const -{ -} - bool LocalTableStream::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -113,24 +109,26 @@ WriteLockResult LocalTableStream::lockRecord(thread_db* tdbb, bool skipLocked) c status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); } -void LocalTableStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void LocalTableStream::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { //// TODO: Use Local Table name/alias. - if (detailed) - { - plan += printIndent(++level) + "Local Table Full Scan"; - printOptInfo(plan); - } - else - { - if (!level) - plan += "("; + if (!level) + plan += "("; - plan += "Local_Table"; - plan += " NATURAL"; + plan += "Local_Table"; + plan += " NATURAL"; - if (!level) - plan += ")"; - } + if (!level) + plan += ")"; +} + +void LocalTableStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const +{ + planEntry.className = "LocalTableStream"; + + //// TODO: Use Local Table name/alias. + + planEntry.description.add() = "Local Table Full Scan"; + printOptInfo(planEntry.description); } diff --git a/src/jrd/recsrc/LockedStream.cpp b/src/jrd/recsrc/LockedStream.cpp index e392c93021..21a2d9e15f 100644 --- a/src/jrd/recsrc/LockedStream.cpp +++ b/src/jrd/recsrc/LockedStream.cpp @@ -111,21 +111,23 @@ WriteLockResult LockedStream::lockRecord(thread_db* tdbb, bool skipLocked) const return m_next->lockRecord(tdbb, skipLocked); } -void LockedStream::getChildren(Array& children) const +void LockedStream::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { - children.add(m_next); + m_next->getLegacyPlan(tdbb, plan, level); } -void LockedStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void LockedStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) - { - plan += printIndent(++level) + "Write Lock"; - printOptInfo(plan); - } + planEntry.className = "LockedStream"; + + planEntry.description.add() = "Write Lock"; + printOptInfo(planEntry.description); if (recurse) - m_next->print(tdbb, plan, detailed, level, recurse); + { + ++level; + m_next->getPlan(tdbb, planEntry.children.add(), level, recurse); + } } void LockedStream::markRecursive() diff --git a/src/jrd/recsrc/MergeJoin.cpp b/src/jrd/recsrc/MergeJoin.cpp index ddafb39ebf..bc5e1f663d 100644 --- a/src/jrd/recsrc/MergeJoin.cpp +++ b/src/jrd/recsrc/MergeJoin.cpp @@ -345,37 +345,33 @@ WriteLockResult MergeJoin::lockRecord(thread_db* /*tdbb*/, bool /*skipLocked*/) status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); } -void MergeJoin::getChildren(Array& children) const +void MergeJoin::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { + level++; + plan += "MERGE ("; for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) - children.add(m_args[i]); + { + if (i) + plan += ", "; + + m_args[i]->getLegacyPlan(tdbb, plan, level); + } + plan += ")"; } -void MergeJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void MergeJoin::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) - { - plan += printIndent(++level) + "Merge Join (inner)"; - printOptInfo(plan); + planEntry.className = "MergeJoin"; - if (recurse) - { - for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) - m_args[i]->print(tdbb, plan, true, level, recurse); - } - } - else - { - level++; - plan += "MERGE ("; - for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) - { - if (i) - plan += ", "; + planEntry.description.add() = "Merge Join (inner)"; + printOptInfo(planEntry.description); - m_args[i]->print(tdbb, plan, false, level, recurse); - } - plan += ")"; + if (recurse) + { + ++level; + + for (const auto arg : m_args) + arg->getPlan(tdbb, planEntry.children.add(), level, recurse); } } diff --git a/src/jrd/recsrc/NestedLoopJoin.cpp b/src/jrd/recsrc/NestedLoopJoin.cpp index b267c5b366..93a8b8ec81 100644 --- a/src/jrd/recsrc/NestedLoopJoin.cpp +++ b/src/jrd/recsrc/NestedLoopJoin.cpp @@ -208,63 +208,59 @@ WriteLockResult NestedLoopJoin::lockRecord(thread_db* /*tdbb*/, bool /*skipLocke status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); } -void NestedLoopJoin::getChildren(Array& children) const -{ - for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) - children.add(m_args[i]); -} - -void NestedLoopJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void NestedLoopJoin::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { if (m_args.hasData()) { - if (detailed) + level++; + plan += "JOIN ("; + for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) { - plan += printIndent(++level) + "Nested Loop Join "; + if (i) + plan += ", "; - switch (m_joinType) - { - case INNER_JOIN: - plan += "(inner)"; - break; - - case OUTER_JOIN: - plan += "(outer)"; - break; - - case SEMI_JOIN: - plan += "(semi)"; - break; - - case ANTI_JOIN: - plan += "(anti)"; - break; - - default: - fb_assert(false); - } - - printOptInfo(plan); - - if (recurse) - { - for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) - m_args[i]->print(tdbb, plan, true, level, recurse); - } + m_args[i]->getLegacyPlan(tdbb, plan, level); } - else - { - level++; - plan += "JOIN ("; - for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) - { - if (i) - plan += ", "; + plan += ")"; + } +} - m_args[i]->print(tdbb, plan, false, level, recurse); - } - plan += ")"; - } +void NestedLoopJoin::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const +{ + planEntry.className = "NestedLoopJoin"; + + planEntry.description.add() = "Nested Loop Join "; + + switch (m_joinType) + { + case INNER_JOIN: + planEntry.description.back() += "(inner)"; + break; + + case OUTER_JOIN: + planEntry.description.back() += "(outer)"; + break; + + case SEMI_JOIN: + planEntry.description.back() += "(semi)"; + break; + + case ANTI_JOIN: + planEntry.description.back() += "(anti)"; + break; + + default: + fb_assert(false); + } + + printOptInfo(planEntry.description); + + if (recurse) + { + ++level; + + for (const auto arg : m_args) + arg->getPlan(tdbb, planEntry.children.add(), level, recurse); } } diff --git a/src/jrd/recsrc/ProcedureScan.cpp b/src/jrd/recsrc/ProcedureScan.cpp index fce5434a95..b204bece88 100644 --- a/src/jrd/recsrc/ProcedureScan.cpp +++ b/src/jrd/recsrc/ProcedureScan.cpp @@ -249,28 +249,30 @@ WriteLockResult ProcedureScan::lockRecord(thread_db* /*tdbb*/, bool /*skipLocked status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); } -void ProcedureScan::getChildren(Array& children) const +void ProcedureScan::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { + if (!level) + plan += "("; + + plan += printName(tdbb, m_alias, false) + " NATURAL"; + + if (!level) + plan += ")"; } -void ProcedureScan::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void ProcedureScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) - { - plan += printIndent(++level) + "Procedure " + - printName(tdbb, m_procedure->getName().toString(), m_alias) + " Scan"; - printOptInfo(plan); - } - else - { - if (!level) - plan += "("; + planEntry.className = "ProcedureScan"; - plan += printName(tdbb, m_alias, false) + " NATURAL"; + planEntry.description.add() = "Procedure " + printName(tdbb, m_procedure->getName().toString(), m_alias) + " Scan"; + printOptInfo(planEntry.description); - if (!level) - plan += ")"; - } + planEntry.objectType = obj_procedure; + planEntry.packageName = m_procedure->getName().package; + planEntry.objectName = m_procedure->getName().identifier; + + if (m_alias.hasData() && m_procedure->getName().toString() != m_alias) + planEntry.alias = m_alias; } void ProcedureScan::assignParams(thread_db* tdbb, diff --git a/src/jrd/recsrc/RecordSource.cpp b/src/jrd/recsrc/RecordSource.cpp index cdc96663de..9d7f17010a 100644 --- a/src/jrd/recsrc/RecordSource.cpp +++ b/src/jrd/recsrc/RecordSource.cpp @@ -54,6 +54,62 @@ AccessPath::AccessPath(CompilerScratch* csb) { } +void AccessPath::getPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const +{ + planEntry.accessPath = this; + planEntry.level = level; + + internalGetPlan(tdbb, planEntry, level, recurse); +} + + +// PlanEntry class +// ------------------- + +void PlanEntry::getDescriptionAsString(string& str, bool initialIndentation) const +{ + auto indentLevel = initialIndentation ? level : 0; + + for (const auto& line : description) + { + const string indent(indentLevel * 4, ' '); + + if (initialIndentation || indentLevel) + str += "\n" + indent; + + if (level) + str += "-> "; + + str += line; + + ++indentLevel; + } +} + +void PlanEntry::asFlatList(Array>& list) const +{ + list.clear(); + list.add({this, nullptr}); + + for (unsigned pos = 0; pos < list.getCount(); ++pos) + { + const auto thisEntry = list[pos].first; + unsigned childPos = pos; + + for (const auto& child : thisEntry->children) + list.insert(++childPos, {&child, thisEntry}); + } +} + +void PlanEntry::asString(string& str) const +{ + Array> list; + asFlatList(list); + + for (const auto& pair : list) + pair.first->getDescriptionAsString(str, true); +} + // Record source class // ------------------- @@ -98,40 +154,32 @@ string RecordSource::printName(thread_db* tdbb, const string& name, const string return result; } -string RecordSource::printIndent(unsigned level) -{ - fb_assert(level); - - const string indent(level * 4, ' '); - return string("\n" + indent + "-> "); -} - void RecordSource::printInversion(thread_db* tdbb, const InversionNode* inversion, - string& plan, bool detailed, unsigned level, bool navigation) + ObjectsArray& planLines, bool detailed, bool navigation) { - if (detailed) - plan += printIndent(++level); + const bool wasEmpty = planLines.isEmpty(); + auto plan = &planLines.add(); switch (inversion->type) { case InversionNode::TYPE_AND: if (detailed) - plan += "Bitmap And"; - printInversion(tdbb, inversion->node1, plan, detailed, level); - printInversion(tdbb, inversion->node2, plan, detailed, level); + *plan += "Bitmap And"; + printInversion(tdbb, inversion->node1, planLines, detailed); + printInversion(tdbb, inversion->node2, planLines, detailed); break; case InversionNode::TYPE_OR: case InversionNode::TYPE_IN: if (detailed) - plan += "Bitmap Or"; - printInversion(tdbb, inversion->node1, plan, detailed, level); - printInversion(tdbb, inversion->node2, plan, detailed, level); + *plan += "Bitmap Or"; + printInversion(tdbb, inversion->node1, planLines, detailed); + printInversion(tdbb, inversion->node2, planLines, detailed); break; case InversionNode::TYPE_DBKEY: if (detailed) - plan += "DBKEY"; + *plan += "DBKEY"; break; case InversionNode::TYPE_INDEX: @@ -147,7 +195,10 @@ void RecordSource::printInversion(thread_db* tdbb, const InversionNode* inversio if (detailed) { if (!navigation) - plan += "Bitmap" + printIndent(++level); + { + *plan += "Bitmap"; + plan = &planLines.add(); + } const index_desc& idx = retrieval->irb_desc; const bool uniqueIdx = (idx.idx_flags & idx_unique); @@ -194,13 +245,11 @@ void RecordSource::printInversion(thread_db* tdbb, const InversionNode* inversio } } - plan += "Index " + printName(tdbb, indexName.c_str()) + + *plan += "Index " + printName(tdbb, indexName.c_str()) + (fullscan ? " Full" : unique ? " Unique" : list ? " List" : " Range") + " Scan" + bounds; } else - { - plan += (plan.hasData() ? ", " : "") + printName(tdbb, indexName.c_str(), false); - } + *plan += (wasEmpty ? "" : ", ") + printName(tdbb, indexName.c_str(), false); } break; @@ -209,13 +258,28 @@ void RecordSource::printInversion(thread_db* tdbb, const InversionNode* inversio } } -void RecordSource::printOptInfo(string& plan) const +void RecordSource::printInversion(thread_db* tdbb, const InversionNode* inversion, + string& plan, bool detailed, unsigned level, bool navigation) +{ + ObjectsArray planLines; + printInversion(tdbb, inversion, planLines, detailed, navigation); + + for (const auto& line : planLines) + { + if (detailed) + plan += '\n' + string(++level * 4, ' '); + plan += line; + } +} + +void RecordSource::printOptInfo(ObjectsArray& planLines) const { #ifdef PRINT_OPT_INFO + fb_assert(planLines.hasData()); string info; // Add 0.5 to convert double->int truncation into rounding info.printf(" [rows: %" UQUADFORMAT "]", (FB_UINT64) (m_cardinality + 0.5)); - plan += info; + planLines.back() += info; #endif } diff --git a/src/jrd/recsrc/RecordSource.h b/src/jrd/recsrc/RecordSource.h index 24e9bf86c3..66cf19cd18 100644 --- a/src/jrd/recsrc/RecordSource.h +++ b/src/jrd/recsrc/RecordSource.h @@ -23,6 +23,7 @@ #ifndef JRD_RECORD_SOURCE_H #define JRD_RECORD_SOURCE_H +#include #include "../common/classes/array.h" #include "../common/classes/objects_array.h" #include "../common/classes/NestConst.h" @@ -50,6 +51,7 @@ namespace Jrd struct win; class BaseBufferedStream; class BufferedStream; + class PlanEntry; enum JoinType { INNER_JOIN, OUTER_JOIN, SEMI_JOIN, ANTI_JOIN }; @@ -71,16 +73,56 @@ namespace Jrd return m_recSourceId; } - virtual void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const = 0; + double getCardinality() const + { + return m_cardinality; + } - virtual void getChildren(Firebird::Array& children) const = 0; + void getPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const; + + virtual void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const = 0; + + protected: + virtual void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, + unsigned level, bool recurse) const = 0; + + protected: + double m_cardinality = 0.0; private: const ULONG m_cursorId; const ULONG m_recSourceId; }; + class PlanEntry final : public Firebird::AutoStorage + { + public: + explicit PlanEntry(MemoryPool& pool) + : AutoStorage(pool) + { + } + + PlanEntry() = default; + + public: + void getDescriptionAsString(Firebird::string& str, bool initialIndentation = false) const; + void asFlatList(Firebird::Array>& list) const; + void asString(Firebird::string& str) const; + + public: + Firebird::string className{getPool()}; + Firebird::ObjectsArray description{getPool()}; + Firebird::ObjectsArray children{getPool()}; + std::optional objectType; + MetaName packageName; + MetaName objectName; + MetaName alias; + const AccessPath* accessPath = nullptr; + ULONG recordLength = 0; + ULONG keyLength = 0; + unsigned level = 0; + }; + // Abstract base class for record sources. class RecordSource : public AccessPath { @@ -106,11 +148,6 @@ namespace Jrd return true; } - double getCardinality() const - { - return m_cardinality; - } - void open(thread_db* tdbb) const; bool getRecord(thread_db* tdbb) const; @@ -134,11 +171,15 @@ namespace Jrd static Firebird::string printName(thread_db* tdbb, const Firebird::string& name, const Firebird::string& alias); - static Firebird::string printIndent(unsigned level); + static void printInversion(thread_db* tdbb, const InversionNode* inversion, + Firebird::ObjectsArray& planLines, bool detailed, + bool navigation = false); + static void printInversion(thread_db* tdbb, const InversionNode* inversion, Firebird::string& plan, bool detailed, unsigned level, bool navigation = false); - void printOptInfo(Firebird::string& plan) const; + + void printOptInfo(Firebird::ObjectsArray& plan) const; static void saveRecord(thread_db* tdbb, record_param* rpb); static void restoreRecord(thread_db* tdbb, record_param* rpb); @@ -146,7 +187,6 @@ namespace Jrd virtual void internalOpen(thread_db* tdbb) const = 0; virtual bool internalGetRecord(thread_db* tdbb) const = 0; - double m_cardinality = 0.0; ULONG m_impure = 0; bool m_recursive = false; }; @@ -191,12 +231,10 @@ namespace Jrd void close(thread_db* tdbb) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -220,12 +258,10 @@ namespace Jrd void close(thread_db* tdbb) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -264,10 +300,7 @@ namespace Jrd void close(thread_db* tdbb) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; void setInversion(InversionNode* inversion, BoolExprNode* condition) { @@ -277,6 +310,7 @@ namespace Jrd } protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -317,14 +351,12 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; protected: void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; private: jrd_rel* const m_relation; @@ -342,12 +374,10 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -378,12 +408,10 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -411,10 +439,7 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; void markRecursive() override; void invalidateRecords(Request* request) const override; @@ -423,6 +448,7 @@ namespace Jrd void nullRecords(thread_db* tdbb) const override; protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -443,10 +469,7 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; void markRecursive() override; void invalidateRecords(Request* request) const override; @@ -455,6 +478,7 @@ namespace Jrd void nullRecords(thread_db* tdbb) const override; protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -478,10 +502,7 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; void markRecursive() override; void invalidateRecords(Request* request) const override; @@ -495,6 +516,7 @@ namespace Jrd } protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -518,10 +540,7 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; void markRecursive() override; void invalidateRecords(Request* request) const override; @@ -535,6 +554,7 @@ namespace Jrd } protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -554,10 +574,7 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; void markRecursive() override; void invalidateRecords(Request* request) const override; @@ -576,6 +593,7 @@ namespace Jrd } protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -675,10 +693,7 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; void markRecursive() override; void invalidateRecords(Request* request) const override; @@ -720,6 +735,7 @@ namespace Jrd } protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -901,12 +917,11 @@ namespace Jrd const NestValueArray* group, MapNode* map, RecordSource* next); public: - void getChildren(Firebird::Array& children) const override; - void print(thread_db* tdbb, Firebird::string& plan, bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; bool internalGetRecord(thread_db* tdbb) const override; - }; class WindowedStream : public RecordSource @@ -971,13 +986,13 @@ namespace Jrd public: void close(thread_db* tdbb) const override; - void getChildren(Firebird::Array& children) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; - void print(thread_db* tdbb, Firebird::string& plan, bool detailed, unsigned level, bool recurse) const override; void findUsedStreams(StreamList& streams, bool expandAll = false) const override; void nullRecords(thread_db* tdbb) const override; protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -1013,10 +1028,7 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; void markRecursive() override; void invalidateRecords(Request* request) const override; @@ -1025,6 +1037,7 @@ namespace Jrd void nullRecords(thread_db* tdbb) const override; protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -1081,10 +1094,7 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; void markRecursive() override; void invalidateRecords(Request* request) const override; @@ -1102,6 +1112,7 @@ namespace Jrd } protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -1125,10 +1136,7 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; void markRecursive() override; void invalidateRecords(Request* request) const override; @@ -1137,6 +1145,7 @@ namespace Jrd void nullRecords(thread_db* tdbb) const override; protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -1158,10 +1167,7 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; void markRecursive() override; void invalidateRecords(Request* request) const override; @@ -1170,6 +1176,7 @@ namespace Jrd void nullRecords(thread_db* tdbb) const override; protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -1212,10 +1219,7 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; void markRecursive() override; void invalidateRecords(Request* request) const override; @@ -1226,6 +1230,7 @@ namespace Jrd static unsigned maxCapacity(); protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -1278,10 +1283,7 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; void markRecursive() override; void invalidateRecords(Request* request) const override; @@ -1290,6 +1292,7 @@ namespace Jrd void nullRecords(thread_db* tdbb) const override; protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -1311,15 +1314,13 @@ namespace Jrd void close(thread_db* tdbb) const override; - void getChildren(Firebird::Array& children) const override; - bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -1344,16 +1345,14 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; void markRecursive() override; void invalidateRecords(Request* request) const override; void findUsedStreams(StreamList& streams, bool expandAll = false) const override; protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -1389,16 +1388,14 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; void markRecursive() override; void invalidateRecords(Request* request) const override; void findUsedStreams(StreamList& streams, bool expandAll = false) const override; protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; @@ -1431,10 +1428,7 @@ namespace Jrd bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; void markRecursive() override; void invalidateRecords(Request* request) const override; @@ -1443,6 +1437,7 @@ namespace Jrd void nullRecords(thread_db* tdbb) const override; protected: + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; diff --git a/src/jrd/recsrc/RecursiveStream.cpp b/src/jrd/recsrc/RecursiveStream.cpp index d40f3b3e24..e5023ef9ee 100644 --- a/src/jrd/recsrc/RecursiveStream.cpp +++ b/src/jrd/recsrc/RecursiveStream.cpp @@ -238,38 +238,33 @@ WriteLockResult RecursiveStream::lockRecord(thread_db* /*tdbb*/, bool /*skipLock status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); } -void RecursiveStream::getChildren(Array& children) const +void RecursiveStream::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { - children.add(m_root); - children.add(m_inner); + if (!level) + plan += "("; + + m_root->getLegacyPlan(tdbb, plan, level + 1); + + plan += ", "; + + m_inner->getLegacyPlan(tdbb, plan, level + 1); + + if (!level) + plan += ")"; } -void RecursiveStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void RecursiveStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) + planEntry.className = "RecursiveStream"; + + planEntry.description.add() = "Recursion"; + printOptInfo(planEntry.description); + + if (recurse) { - plan += printIndent(++level) + "Recursion"; - printOptInfo(plan); - - if (recurse) - { - m_root->print(tdbb, plan, true, level, recurse); - m_inner->print(tdbb, plan, true, level, recurse); - } - } - else - { - if (!level) - plan += "("; - - m_root->print(tdbb, plan, false, level + 1, recurse); - - plan += ", "; - - m_inner->print(tdbb, plan, false, level + 1, recurse); - - if (!level) - plan += ")"; + ++level; + m_root->getPlan(tdbb, planEntry.children.add(), level, recurse); + m_inner->getPlan(tdbb, planEntry.children.add(), level, recurse); } } diff --git a/src/jrd/recsrc/SingularStream.cpp b/src/jrd/recsrc/SingularStream.cpp index 99c0218e1c..1e8115e796 100644 --- a/src/jrd/recsrc/SingularStream.cpp +++ b/src/jrd/recsrc/SingularStream.cpp @@ -146,21 +146,23 @@ WriteLockResult SingularStream::lockRecord(thread_db* tdbb, bool skipLocked) con return m_next->lockRecord(tdbb, skipLocked); } -void SingularStream::getChildren(Array& children) const +void SingularStream::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { - children.add(m_next); + m_next->getLegacyPlan(tdbb, plan, level); } -void SingularStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void SingularStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) - { - plan += printIndent(++level) + "Singularity Check"; - printOptInfo(plan); - } + planEntry.className = "SingularStream"; + + planEntry.description.add() = "Singularity Check"; + printOptInfo(planEntry.description); if (recurse) - m_next->print(tdbb, plan, detailed, level, recurse); + { + ++level; + m_next->getPlan(tdbb, planEntry.children.add(), level, recurse); + } } void SingularStream::markRecursive() diff --git a/src/jrd/recsrc/SkipRowsStream.cpp b/src/jrd/recsrc/SkipRowsStream.cpp index 297db0b7fb..115b950cee 100644 --- a/src/jrd/recsrc/SkipRowsStream.cpp +++ b/src/jrd/recsrc/SkipRowsStream.cpp @@ -116,21 +116,23 @@ WriteLockResult SkipRowsStream::lockRecord(thread_db* tdbb, bool skipLocked) con return m_next->lockRecord(tdbb, skipLocked); } -void SkipRowsStream::getChildren(Array& children) const +void SkipRowsStream::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { - children.add(m_next); + m_next->getLegacyPlan(tdbb, plan, level); } -void SkipRowsStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void SkipRowsStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) - { - plan += printIndent(++level) + "Skip N Records"; - printOptInfo(plan); - } + planEntry.className = "SkipRowsStream"; + + planEntry.description.add() = "Skip N Records"; + printOptInfo(planEntry.description); if (recurse) - m_next->print(tdbb, plan, detailed, level, recurse); + { + ++level; + m_next->getPlan(tdbb, planEntry.children.add(), level, recurse); + } } void SkipRowsStream::markRecursive() diff --git a/src/jrd/recsrc/SortedStream.cpp b/src/jrd/recsrc/SortedStream.cpp index eacd72954b..159fad2a2a 100644 --- a/src/jrd/recsrc/SortedStream.cpp +++ b/src/jrd/recsrc/SortedStream.cpp @@ -121,36 +121,41 @@ WriteLockResult SortedStream::lockRecord(thread_db* tdbb, bool skipLocked) const return m_next->lockRecord(tdbb, skipLocked); } -void SortedStream::getChildren(Array& children) const +void SortedStream::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { - children.add(m_next); + level++; + plan += "SORT ("; + m_next->getLegacyPlan(tdbb, plan, level); + plan += ")"; } -void SortedStream::print(thread_db* tdbb, string& plan, - bool detailed, unsigned level, bool recurse) const +void SortedStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) + planEntry.className = "SortedStream"; + + string extras; + extras.printf(" (record length: %" ULONGFORMAT", key length: %" ULONGFORMAT")", + m_map->length, m_map->keyLength); + + auto planDescription = &planEntry.description.add(); + + if (m_map->flags & FLAG_REFETCH) { - string extras; - extras.printf(" (record length: %" ULONGFORMAT", key length: %" ULONGFORMAT")", - m_map->length, m_map->keyLength); - - if (m_map->flags & FLAG_REFETCH) - plan += printIndent(++level) + "Refetch"; - - plan += printIndent(++level) + - ((m_map->flags & FLAG_PROJECT) ? "Unique Sort" : "Sort") + extras; - printOptInfo(plan); - - if (recurse) - m_next->print(tdbb, plan, true, level, recurse); + *planDescription = "Refetch"; + planDescription = &planEntry.description.add(); + ++level; } - else + + *planDescription += ((m_map->flags & FLAG_PROJECT) ? "Unique Sort" : "Sort") + extras; + printOptInfo(planEntry.description); + + planEntry.recordLength = m_map->length; + planEntry.keyLength = m_map->keyLength; + + if (recurse) { - level++; - plan += "SORT ("; - m_next->print(tdbb, plan, false, level, recurse); - plan += ")"; + ++level; + m_next->getPlan(tdbb, planEntry.children.add(), level, recurse); } } diff --git a/src/jrd/recsrc/Union.cpp b/src/jrd/recsrc/Union.cpp index 804133f2eb..b106ff04d4 100644 --- a/src/jrd/recsrc/Union.cpp +++ b/src/jrd/recsrc/Union.cpp @@ -162,40 +162,36 @@ WriteLockResult Union::lockRecord(thread_db* tdbb, bool skipLocked) const return m_args[impure->irsb_count]->lockRecord(tdbb, skipLocked); } -void Union::getChildren(Array& children) const +void Union::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { + if (!level) + plan += "("; + for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) - children.add(m_args[i]); + { + if (i) + plan += ", "; + + m_args[i]->getLegacyPlan(tdbb, plan, level + 1); + } + + if (!level) + plan += ")"; } -void Union::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void Union::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) + planEntry.className = "Union"; + + planEntry.description.add() = (m_args.getCount() == 1 ? "Materialize" : "Union"); + printOptInfo(planEntry.description); + + if (recurse) { - plan += printIndent(++level) + (m_args.getCount() == 1 ? "Materialize" : "Union"); - printOptInfo(plan); + ++level; - if (recurse) - { - for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) - m_args[i]->print(tdbb, plan, true, level, recurse); - } - } - else - { - if (!level) - plan += "("; - - for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) - { - if (i) - plan += ", "; - - m_args[i]->print(tdbb, plan, false, level + 1, recurse); - } - - if (!level) - plan += ")"; + for (const auto arg : m_args) + arg->getPlan(tdbb, planEntry.children.add(), level, recurse); } } diff --git a/src/jrd/recsrc/VirtualTableScan.cpp b/src/jrd/recsrc/VirtualTableScan.cpp index 82dad5f80d..ae35e173ff 100644 --- a/src/jrd/recsrc/VirtualTableScan.cpp +++ b/src/jrd/recsrc/VirtualTableScan.cpp @@ -109,26 +109,27 @@ WriteLockResult VirtualTableScan::lockRecord(thread_db* /*tdbb*/, bool /*skipLoc status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); } -void VirtualTableScan::getChildren(Array& children) const +void VirtualTableScan::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { + if (!level) + plan += "("; + + plan += printName(tdbb, m_alias, false) + " NATURAL"; + + if (!level) + plan += ")"; } -void VirtualTableScan::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void VirtualTableScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) - { - plan += printIndent(++level) + "Table " + - printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Full Scan"; - printOptInfo(plan); - } - else - { - if (!level) - plan += "("; + planEntry.className = "VirtualTableScan"; - plan += printName(tdbb, m_alias, false) + " NATURAL"; + planEntry.description.add() = "Table " + printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Full Scan"; + printOptInfo(planEntry.description); - if (!level) - plan += ")"; - } + planEntry.objectType = m_relation->getObjectType(); + planEntry.objectName = m_relation->rel_name; + + if (m_alias.hasData() && m_relation->rel_name != m_alias) + planEntry.alias = m_alias; } diff --git a/src/jrd/recsrc/WindowedStream.cpp b/src/jrd/recsrc/WindowedStream.cpp index ce795f76bc..396c1776cc 100644 --- a/src/jrd/recsrc/WindowedStream.cpp +++ b/src/jrd/recsrc/WindowedStream.cpp @@ -52,16 +52,12 @@ namespace public: BufferedStreamWindow(CompilerScratch* csb, BufferedStream* next); - void internalOpen(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool internalGetRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; - void getChildren(Firebird::Array& children) const override; - - void print(thread_db* tdbb, Firebird::string& plan, bool detailed, unsigned level, bool recurse) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; void markRecursive() override; void invalidateRecords(Request* request) const override; @@ -87,6 +83,11 @@ namespace return impure->irsb_position; } + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; + public: NestConst m_next; }; @@ -147,21 +148,23 @@ namespace return m_next->lockRecord(tdbb, skipLocked); } - void BufferedStreamWindow::getChildren(Array& children) const + void BufferedStreamWindow::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { - children.add(m_next); + m_next->getLegacyPlan(tdbb, plan, level); } - void BufferedStreamWindow::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const + void BufferedStreamWindow::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) - { - plan += printIndent(++level) + "Window Buffer"; - printOptInfo(plan); - } + planEntry.className = "BufferedStreamWindow"; + + planEntry.description.add() = "Window Buffer"; + printOptInfo(planEntry.description); if (recurse) - m_next->print(tdbb, plan, detailed, level, recurse); + { + ++level; + m_next->getPlan(tdbb, planEntry.children.add(), level, recurse); + } } void BufferedStreamWindow::markRecursive() @@ -404,21 +407,23 @@ WriteLockResult WindowedStream::lockRecord(thread_db* /*tdbb*/, bool /*skipLocke status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); } -void WindowedStream::getChildren(Array& children) const +void WindowedStream::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { - children.add(m_joinedStream); + m_joinedStream->getLegacyPlan(tdbb, plan, level); } -void WindowedStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +void WindowedStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) - { - plan += printIndent(++level) + "Window"; - printOptInfo(plan); - } + planEntry.className = "WindowedStream"; + + planEntry.description.add() = "Window"; + printOptInfo(planEntry.description); if (recurse) - m_joinedStream->print(tdbb, plan, detailed, level, recurse); + { + ++level; + m_joinedStream->getPlan(tdbb, planEntry.children.add(), level, recurse); + } } void WindowedStream::markRecursive() @@ -899,22 +904,24 @@ bool WindowedStream::WindowStream::internalGetRecord(thread_db* tdbb) const return true; } -void WindowedStream::WindowStream::getChildren(Array& children) const +void WindowedStream::WindowStream::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { - children.add(m_next); + m_next->getLegacyPlan(tdbb, plan, level); } -void WindowedStream::WindowStream::print(thread_db* tdbb, string& plan, bool detailed, - unsigned level, bool recurse) const + +void WindowedStream::WindowStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const { - if (detailed) - { - plan += printIndent(++level) + "Window Partition"; - printOptInfo(plan); - } + planEntry.className = "WindowStream"; + + planEntry.description.add() = "Window Partition"; + printOptInfo(planEntry.description); if (recurse) - m_next->print(tdbb, plan, detailed, level, recurse); + { + ++level; + m_next->getPlan(tdbb, planEntry.children.add(), level, recurse); + } } void WindowedStream::WindowStream::findUsedStreams(StreamList& streams, bool expandAll) const diff --git a/src/jrd/sys-packages/SqlPackage.cpp b/src/jrd/sys-packages/SqlPackage.cpp new file mode 100644 index 0000000000..504081b2a1 --- /dev/null +++ b/src/jrd/sys-packages/SqlPackage.cpp @@ -0,0 +1,201 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2023 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#include "firebird.h" +#include "../jrd/sys-packages/SqlPackage.h" +#include "../dsql/DsqlRequests.h" +#include "../jrd/Statement.h" +#include "../jrd/recsrc/RecordSource.h" +#include "../dsql/dsql_proto.h" +#include "../jrd/mov_proto.h" + +using namespace Jrd; +using namespace Firebird; + + +//-------------------------------------- + + +IExternalResultSet* SqlPackage::explainProcedure(ThrowStatusExceptionWrapper* status, + IExternalContext* context, const ExplainInput::Type* in, ExplainOutput::Type* out) +{ + return FB_NEW ExplainResultSet(status, context, in, out); +} + + +//-------------------------------------- + + +SqlPackage::ExplainResultSet::ExplainResultSet(ThrowStatusExceptionWrapper* status, + IExternalContext* context, const ExplainInput::Type* in, ExplainOutput::Type* aOut) + : out(aOut) +{ + const auto tdbb = JRD_get_thread_data(); + const auto attachment = tdbb->getAttachment(); + const auto transaction = tdbb->getTransaction(); + + dsc sqlDesc; + sqlDesc.makeBlob(isc_blob_text, CS_METADATA, const_cast(&in->sql)); + MoveBuffer sqlBuffer; + UCHAR* sqlAddress; + ULONG sqlLength = MOV_make_string2(tdbb, &sqlDesc, CS_METADATA, &sqlAddress, sqlBuffer, false); + + const auto dsqlRequest = DSQL_prepare(tdbb, attachment, transaction, sqlLength, (const char*) sqlAddress, + SQL_DIALECT_CURRENT, 0, nullptr, nullptr, false); + + Cleanup dsqlRequestCleanup([&] + { + DsqlRequest::destroy(tdbb, dsqlRequest); + }); + + if (!dsqlRequest->getStatement()) + return; + + PlanEntry rootEntry; + dsqlRequest->getStatement()->getPlan(tdbb, rootEntry); + + Array> planList; + rootEntry.asFlatList(planList); + + unsigned planLine = 0; + + for (const auto& [planEntry, parentPlanEntry] : planList) + { + if (planLine == 0) + { + ++planLine; + continue; + } + + auto& resultEntry = resultEntries.add(); + + resultEntry.planLineNull = FB_FALSE; + resultEntry.planLine = planLine++; + + resultEntry.recordSourceIdNull = FB_FALSE; + resultEntry.recordSourceId = planEntry->accessPath->getRecSourceId(); + + resultEntry.parentRecordSourceIdNull = parentPlanEntry->accessPath ? FB_FALSE : FB_TRUE; + if (parentPlanEntry->accessPath) + resultEntry.parentRecordSourceId = parentPlanEntry->accessPath->getRecSourceId(); + + resultEntry.levelNull = FB_FALSE; + resultEntry.level = planEntry->level; + + resultEntry.objectTypeNull = !planEntry->objectType.has_value(); + if (planEntry->objectType.has_value()) + resultEntry.objectType = planEntry->objectType.value(); + + resultEntry.packageNameNull = planEntry->packageName.hasData() ? FB_FALSE : FB_TRUE; + if (planEntry->packageName.hasData()) + resultEntry.packageName.set(planEntry->packageName.c_str(), planEntry->packageName.length()); + + resultEntry.objectNameNull = planEntry->objectName.hasData() ? FB_FALSE : FB_TRUE; + if (planEntry->objectName.hasData()) + resultEntry.objectName.set(planEntry->objectName.c_str(), planEntry->objectName.length()); + + resultEntry.aliasNull = planEntry->alias.hasData() ? FB_FALSE : FB_TRUE; + if (planEntry->alias.hasData()) + resultEntry.alias.set(planEntry->alias.c_str(), planEntry->alias.length()); + + resultEntry.cardinalityNull = planEntry->level > 0 ? FB_FALSE : FB_TRUE; + resultEntry.cardinality = planEntry->accessPath->getCardinality(); + + resultEntry.recordLengthNull = planEntry->recordLength ? FB_FALSE : FB_TRUE; + resultEntry.recordLength = planEntry->recordLength; + + resultEntry.keyLengthNull = planEntry->keyLength ? FB_FALSE : FB_TRUE; + resultEntry.keyLength = planEntry->keyLength; + + string accessPath; + planEntry->getDescriptionAsString(accessPath); + + constexpr UCHAR bpb[] = { + isc_bpb_version1, + isc_bpb_storage, 1, isc_bpb_storage_temp + }; + + bid blobId; + const auto blob = blb::create2(tdbb, transaction, &blobId, sizeof(bpb), bpb); + blob->BLB_put_data(tdbb, (const UCHAR*) accessPath.c_str(), accessPath.length()); + blob->BLB_close(tdbb); + + resultEntry.accessPathNull = FB_FALSE; + resultEntry.accessPath = blobId; + } + + resultIterator = resultEntries.begin(); +} + +FB_BOOLEAN SqlPackage::ExplainResultSet::fetch(ThrowStatusExceptionWrapper* status) +{ + if (resultIterator >= resultEntries.end()) + return false; + + *out = *resultIterator++; + + return true; +} + + +//-------------------------------------- + + +SqlPackage::SqlPackage(MemoryPool& pool) + : SystemPackage( + pool, + "RDB$SQL", + ODS_13_2, + // procedures + { + SystemProcedure( + pool, + "EXPLAIN", + SystemProcedureFactory(), + prc_selectable, + // input parameters + { + {"SQL", fld_description, false} + }, + // output parameters + { + {"PLAN_LINE", fld_integer, false}, + {"RECORD_SOURCE_ID", fld_gen_val, false}, + {"PARENT_RECORD_SOURCE_ID", fld_gen_val, true}, + {"LEVEL", fld_integer, false}, + {"OBJECT_TYPE", fld_obj_type, true}, + {"PACKAGE_NAME", fld_r_name, true}, + {"OBJECT_NAME", fld_r_name, true}, + {"ALIAS", fld_r_name, true}, + {"CARDINALITY", fld_statistics, true}, + {"RECORD_LENGTH", fld_integer, true}, + {"KEY_LENGTH", fld_integer, true}, + {"ACCESS_PATH", fld_description, false} + } + ), + }, + // functions + { + } + ) +{ +} diff --git a/src/jrd/sys-packages/SqlPackage.h b/src/jrd/sys-packages/SqlPackage.h new file mode 100644 index 0000000000..8ba2fda0ca --- /dev/null +++ b/src/jrd/sys-packages/SqlPackage.h @@ -0,0 +1,99 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2023 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#ifndef JRD_SYS_PACKAGES_SQL_PACKAGE_H +#define JRD_SYS_PACKAGES_SQL_PACKAGE_H + +#include "firebird.h" +#include "firebird/Message.h" +#include "../common/classes/array.h" +#include "../jrd/SystemPackages.h" + +namespace Jrd { + + +class SqlPackage final : public SystemPackage +{ +public: + SqlPackage(Firebird::MemoryPool& pool); + + SqlPackage(const SqlPackage&) = delete; + SqlPackage& operator=(const SqlPackage&) = delete; + +private: + FB_MESSAGE(ExplainInput, Firebird::ThrowStatusExceptionWrapper, + (FB_BLOB, sql) + ); + + FB_MESSAGE(ExplainOutput, Firebird::ThrowStatusExceptionWrapper, + (FB_INTEGER, planLine) + (FB_BIGINT, recordSourceId) + (FB_BIGINT, parentRecordSourceId) + (FB_INTEGER, level) + (FB_SMALLINT, objectType) + (FB_INTL_VARCHAR(METADATA_IDENTIFIER_CHAR_LEN * METADATA_BYTES_PER_CHAR, CS_METADATA), packageName) + (FB_INTL_VARCHAR(METADATA_IDENTIFIER_CHAR_LEN * METADATA_BYTES_PER_CHAR, CS_METADATA), objectName) + (FB_INTL_VARCHAR(METADATA_IDENTIFIER_CHAR_LEN * METADATA_BYTES_PER_CHAR, CS_METADATA), alias) + (FB_DOUBLE, cardinality) + (FB_INTEGER, recordLength) + (FB_INTEGER, keyLength) + (FB_BLOB, accessPath) + ); + + class ExplainResultSet : + public + Firebird::DisposeIface< + Firebird::IExternalResultSetImpl< + ExplainResultSet, + Firebird::ThrowStatusExceptionWrapper + > + > + { + public: + ExplainResultSet(Firebird::ThrowStatusExceptionWrapper* status, Firebird::IExternalContext* context, + const ExplainInput::Type* in, ExplainOutput::Type* out); + + public: + void dispose() override + { + delete this; + } + + public: + FB_BOOLEAN fetch(Firebird::ThrowStatusExceptionWrapper* status) override; + + private: + ExplainOutput::Type* out; + Firebird::Array resultEntries{*getDefaultMemoryPool()}; + Firebird::Array::const_iterator resultIterator = nullptr; + }; + + //---------- + + static Firebird::IExternalResultSet* explainProcedure(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IExternalContext* context, const ExplainInput::Type* in, ExplainOutput::Type* out); +}; + + +} // namespace + +#endif // JRD_SYS_PACKAGES_SQL_PACKAGE_H