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