8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-22 16:43:03 +01:00

Feature #7675 - EXPLAIN statement and RDB$SQL package. (#7697)

* Feature #7675 - EXPLAIN statement and RDB$SQL package.

* Change ObjectsArray::back() to match STL semantics and add front() method.

* Fix indentation problem.

* Add OBJECT_TYPE column.

* Add CARDINALITY column.

* Add conversion from bid to ISC_QUAD.

* Replace fb_assert by static_assert.

* Change ACCESS_PATH output parameter to blob.

* Improve docs.
This commit is contained in:
Adriano dos Santos Fernandes 2023-09-19 00:52:20 +00:00 committed by GitHub
parent fc2fe39d3a
commit 39b019574a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 1171 additions and 744 deletions

View File

@ -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)

View File

@ -161,6 +161,7 @@
<ClCompile Include="..\..\..\src\jrd\sqz.cpp" />
<ClCompile Include="..\..\..\src\jrd\Statement.cpp" />
<ClCompile Include="..\..\..\src\jrd\svc.cpp" />
<ClCompile Include="..\..\..\src\jrd\sys-packages\SqlPackage.cpp" />
<ClCompile Include="..\..\..\src\jrd\SysFunction.cpp" />
<ClCompile Include="..\..\..\src\jrd\SystemPackages.cpp" />
<ClCompile Include="..\..\..\src\jrd\TempSpace.cpp" />
@ -345,6 +346,7 @@
<ClInclude Include="..\..\..\src\jrd\status.h" />
<ClInclude Include="..\..\..\src\jrd\svc.h" />
<ClInclude Include="..\..\..\src\jrd\svc_undoc.h" />
<ClInclude Include="..\..\..\src\jrd\sys-packages\SqlPackage.h" />
<ClInclude Include="..\..\..\src\jrd\SysFunction.h" />
<ClInclude Include="..\..\..\src\jrd\SystemPackages.h" />
<ClInclude Include="..\..\..\src\jrd\TempSpace.h" />

View File

@ -378,6 +378,9 @@
<ClCompile Include="..\..\..\src\jrd\svc.cpp">
<Filter>JRD files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\jrd\sys-packages\SqlPackage.cpp">
<Filter>JRD files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\jrd\SysFunction.cpp">
<Filter>JRD files</Filter>
</ClCompile>
@ -1073,6 +1076,9 @@
<ClInclude Include="..\..\..\src\dsql\DsqlBatch.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\jrd\sys-packages\SqlPackage.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\jrd\SystemPackages.h">
<Filter>Header files</Filter>
</ClInclude>

View File

@ -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 ;!

View File

@ -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 `<alternate string literal>`
(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

View File

@ -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

View File

@ -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())

View File

@ -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;
}
;

View File

@ -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")

View File

@ -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 <blobid> -- view BLOB in text editor
HLP_EDIT, // EDIT [<filename>] -- 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 <filename> -- take input from the named SQL file
HLP_OUTPUT, // OUTput [<filename>] -- 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])

View File

@ -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<PlanItem> 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<const RecordSource*> 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<NonPooledPair<const PlanEntry*, const PlanEntry*>> flatPlan;
rootEntry.asFlatList(flatPlan);
NonPooledMap<ULONG, ULONG> 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);
}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -28,6 +28,8 @@
namespace Jrd {
class PlanEntry;
// Compiled statement.
class Statement : public pool_alloc<type_req>
{
@ -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,

View File

@ -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<SystemPackagesInit> INSTANCE;

View File

@ -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;

View File

@ -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();
}
}

View File

@ -375,21 +375,23 @@ AggregatedStream::AggregatedStream(thread_db* tdbb, CompilerScratch* csb, Stream
fb_assert(map);
}
void AggregatedStream::getChildren(Array<const RecordSource*>& 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

View File

@ -122,22 +122,7 @@ bool BitmapTableScan::internalGetRecord(thread_db* tdbb) const
return false;
}
void BitmapTableScan::getChildren(Array<const RecordSource*>& children) const
{
}
void BitmapTableScan::print(thread_db* tdbb, string& plan,
bool detailed, unsigned level, bool recurse) const
{
if (detailed)
{
plan += printIndent(++level) + "Table " +
printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Access By ID";
printOptInfo(plan);
printInversion(tdbb, m_inversion, plan, true, level);
}
else
void BitmapTableScan::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const
{
if (!level)
plan += "(";
@ -150,4 +135,19 @@ void BitmapTableScan::print(thread_db* tdbb, string& plan,
if (!level)
plan += ")";
}
void BitmapTableScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
{
planEntry.className = "BitmapTableScan";
planEntry.description.add() = "Table " + printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Access By ID";
printOptInfo(planEntry.description);
printInversion(tdbb, m_inversion, planEntry.description, true);
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;
}

View File

@ -314,24 +314,25 @@ WriteLockResult BufferedStream::lockRecord(thread_db* tdbb, bool skipLocked) con
return m_next->lockRecord(tdbb, skipLocked);
}
void BufferedStream::getChildren(Array<const RecordSource*>& 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
{
if (detailed)
void BufferedStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
{
planEntry.className = "BufferedStream";
string extras;
extras.printf(" (record length: %" ULONGFORMAT")", m_format->fmt_length);
plan += printIndent(++level) + "Record Buffer" + extras;
printOptInfo(plan);
}
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()

View File

@ -114,39 +114,34 @@ WriteLockResult ConditionalStream::lockRecord(thread_db* tdbb, bool skipLocked)
return impure->irsb_next->lockRecord(tdbb, skipLocked);
}
void ConditionalStream::getChildren(Array<const RecordSource*>& children) const
{
children.add(m_first);
children.add(m_second);
}
void ConditionalStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const
{
if (detailed)
{
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
void ConditionalStream::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const
{
if (!level)
plan += "(";
m_first->print(tdbb, plan, false, level + 1, recurse);
m_first->getLegacyPlan(tdbb, plan, level + 1);
plan += ", ";
m_second->print(tdbb, plan, false, level + 1, recurse);
m_second->getLegacyPlan(tdbb, plan, level + 1);
if (!level)
plan += ")";
}
void ConditionalStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
{
planEntry.className = "ConditionalStream";
planEntry.description.add() = "Condition";
printOptInfo(planEntry.description);
if (recurse)
{
++level;
m_first->getPlan(tdbb, planEntry.children.add(), level, recurse);
m_second->getPlan(tdbb, planEntry.children.add(), level, recurse);
}
}
void ConditionalStream::markRecursive()

View File

@ -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())
{
plan += "\nSub-query";
planEntry.description.add() = "Sub-query";
if (m_rse->isInvariant())
plan += " (invariant)";
planEntry.description.back() += " (invariant)";
}
else if (m_cursorName.hasData())
{
plan += "\nCursor \"" + string(m_cursorName) + "\"";
planEntry.description.add() = "Cursor \"" + string(m_cursorName) + "\"";
if (m_rse->isScrollable())
plan += " (scrollable)";
planEntry.description.back() += " (scrollable)";
}
else
plan += "\nSelect Expression";
planEntry.description.add() = "Select Expression";
if (m_line || m_column)
{
string pos;
pos.printf(" (line %u, column %u)", m_line, m_column);
plan += pos;
}
}
else
{
if (m_line || m_column)
{
string pos;
pos.printf("\n-- line %u, column %u", m_line, m_column);
plan += pos;
}
plan += "\nPLAN ";
planEntry.description.back() += pos;
}
if (recurse)
m_root->print(tdbb, plan, detailed, level, true);
m_root->getPlan(tdbb, planEntry.children.add(), level + 1, recurse);
}
// ---------------------

View File

@ -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<const RecordSource*>& children) const override
if (detailed)
{
children.add(m_root);
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;

View File

@ -115,20 +115,7 @@ WriteLockResult ExternalTableScan::lockRecord(thread_db* tdbb, bool skipLocked)
status_exception::raise(Arg::Gds(isc_record_lock_not_supp));
}
void ExternalTableScan::getChildren(Array<const RecordSource*>& children) const
{
}
void ExternalTableScan::print(thread_db* tdbb, string& plan,
bool detailed, 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
void ExternalTableScan::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const
{
if (!level)
plan += "(";
@ -138,4 +125,17 @@ void ExternalTableScan::print(thread_db* tdbb, string& plan,
if (!level)
plan += ")";
}
void ExternalTableScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
{
planEntry.className = "ExternalTableScan";
planEntry.description.add() = "Table " + printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Full Scan";
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;
}

View File

@ -116,25 +116,24 @@ WriteLockResult FilteredStream::lockRecord(thread_db* tdbb, bool skipLocked) con
return m_next->lockRecord(tdbb, skipLocked);
}
void FilteredStream::getChildren(Array<const RecordSource*>& 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";
planEntry.description.add() = "Filter";
if (m_invariant)
plan += " (preliminary)";
planEntry.description.back() += " (preliminary)";
printOptInfo(plan);
}
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()

View File

@ -120,21 +120,23 @@ WriteLockResult FirstRowsStream::lockRecord(thread_db* tdbb, bool skipLocked) co
return m_next->lockRecord(tdbb, skipLocked);
}
void FirstRowsStream::getChildren(Array<const RecordSource*>& 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()

View File

@ -112,33 +112,29 @@ WriteLockResult FullOuterJoin::lockRecord(thread_db* tdbb, bool skipLocked) cons
status_exception::raise(Arg::Gds(isc_record_lock_not_supp));
}
void FullOuterJoin::getChildren(Array<const RecordSource*>& children) const
{
children.add(m_arg1);
children.add(m_arg2);
}
void FullOuterJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const
{
if (detailed)
{
plan += printIndent(++level) + "Full Outer Join";
if (recurse)
{
m_arg1->print(tdbb, plan, true, level, recurse);
m_arg2->print(tdbb, plan, true, level, recurse);
}
}
else
void FullOuterJoin::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const
{
level++;
plan += "JOIN (";
m_arg1->print(tdbb, plan, false, level, recurse);
m_arg1->getLegacyPlan(tdbb, plan, level);
plan += ", ";
m_arg2->print(tdbb, plan, false, level, recurse);
m_arg2->getLegacyPlan(tdbb, plan, level);
plan += ")";
}
void FullOuterJoin::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
{
planEntry.className = "FullOuterJoin";
planEntry.description.add() = "Full Outer Join";
printOptInfo(planEntry.description);
if (recurse)
{
++level;
m_arg1->getPlan(tdbb, planEntry.children.add(), level, recurse);
m_arg2->getPlan(tdbb, planEntry.children.add(), level, recurse);
}
}
void FullOuterJoin::markRecursive()

View File

@ -162,14 +162,21 @@ bool FullTableScan::internalGetRecord(thread_db* tdbb) const
return false;
}
void FullTableScan::getChildren(Array<const RecordSource*>& 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
{
if (detailed)
void FullTableScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
{
planEntry.className = "FullTableScan";
auto lowerBounds = 0, upperBounds = 0;
for (const auto range : m_dbkeyRanges)
{
@ -188,18 +195,12 @@ void FullTableScan::print(thread_db* tdbb, string& plan, bool detailed, unsigned
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);
}
else
{
if (!level)
plan += "(";
planEntry.description.add() = "Table " + printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Full Scan" + bounds;
printOptInfo(planEntry.description);
plan += printName(tdbb, m_alias, false) + " NATURAL";
planEntry.objectType = m_relation->getObjectType();
planEntry.objectName = m_relation->rel_name;
if (!level)
plan += ")";
}
if (m_alias.hasData() && m_relation->rel_name != m_alias)
planEntry.alias = m_alias;
}

View File

@ -454,44 +454,38 @@ WriteLockResult HashJoin::lockRecord(thread_db* /*tdbb*/, bool /*skipLocked*/) c
status_exception::raise(Arg::Gds(isc_record_lock_not_supp));
}
void HashJoin::getChildren(Array<const RecordSource*>& children) const
{
children.add(m_leader.source);
for (FB_SIZE_T i = 0; i < m_args.getCount(); i++)
children.add(m_args[i].source);
}
void HashJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const
{
if (detailed)
{
plan += printIndent(++level) + "Hash Join (inner)";
printOptInfo(plan);
if (recurse)
{
m_leader.source->print(tdbb, plan, true, level, recurse);
for (FB_SIZE_T i = 0; i < m_args.getCount(); i++)
m_args[i].source->print(tdbb, plan, true, level, recurse);
}
}
else
void HashJoin::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const
{
level++;
plan += "HASH (";
m_leader.source->print(tdbb, plan, false, level, recurse);
m_leader.source->getLegacyPlan(tdbb, plan, level);
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);
m_args[i].source->getLegacyPlan(tdbb, plan, level);
}
plan += ")";
}
void HashJoin::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
{
planEntry.className = "HashJoin";
planEntry.description.add() = "Hash Join (inner)";
printOptInfo(planEntry.description);
if (recurse)
{
++level;
m_leader.source->getPlan(tdbb, planEntry.children.add(), level, recurse);
for (const auto& arg : m_args)
arg.source->getPlan(tdbb, planEntry.children.add(), level, recurse);
}
}
void HashJoin::markRecursive()

View File

@ -352,24 +352,7 @@ bool IndexTableScan::internalGetRecord(thread_db* tdbb) const
return false;
}
void IndexTableScan::getChildren(Array<const RecordSource*>& children) const
{
}
void IndexTableScan::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const
{
if (detailed)
{
plan += printIndent(++level) + "Table " +
printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Access By ID";
printOptInfo(plan);
printInversion(tdbb, m_index, plan, true, level, true);
if (m_inversion)
printInversion(tdbb, m_inversion, plan, true, ++level);
}
else
void IndexTableScan::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const
{
if (!level)
plan += "(";
@ -390,6 +373,24 @@ void IndexTableScan::print(thread_db* tdbb, string& plan, bool detailed, unsigne
if (!level)
plan += ")";
}
void IndexTableScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
{
planEntry.className = "IndexTableScan";
planEntry.description.add() = "Table " + printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Access By ID";
printOptInfo(planEntry.description);
printInversion(tdbb, m_index, planEntry.description, true, true);
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;
if (m_inversion)
printInversion(tdbb, m_inversion, planEntry.description, true);
}
int IndexTableScan::compareKeys(const index_desc* idx,

View File

@ -71,10 +71,6 @@ void LocalTableStream::close(thread_db* tdbb) const
impure->irsb_flags &= ~irsb_open;
}
void LocalTableStream::getChildren(Array<const RecordSource*>& children) const
{
}
bool LocalTableStream::internalGetRecord(thread_db* tdbb) const
{
JRD_reschedule(tdbb);
@ -113,17 +109,10 @@ 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 += "(";
@ -133,4 +122,13 @@ void LocalTableStream::print(thread_db* tdbb, string& plan, bool detailed, unsig
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);
}

View File

@ -111,21 +111,23 @@ WriteLockResult LockedStream::lockRecord(thread_db* tdbb, bool skipLocked) const
return m_next->lockRecord(tdbb, skipLocked);
}
void LockedStream::getChildren(Array<const RecordSource*>& 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()

View File

@ -345,26 +345,7 @@ WriteLockResult MergeJoin::lockRecord(thread_db* /*tdbb*/, bool /*skipLocked*/)
status_exception::raise(Arg::Gds(isc_record_lock_not_supp));
}
void MergeJoin::getChildren(Array<const RecordSource*>& children) const
{
for (FB_SIZE_T i = 0; i < m_args.getCount(); i++)
children.add(m_args[i]);
}
void MergeJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const
{
if (detailed)
{
plan += printIndent(++level) + "Merge Join (inner)";
printOptInfo(plan);
if (recurse)
{
for (FB_SIZE_T i = 0; i < m_args.getCount(); i++)
m_args[i]->print(tdbb, plan, true, level, recurse);
}
}
else
void MergeJoin::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const
{
level++;
plan += "MERGE (";
@ -373,10 +354,25 @@ void MergeJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigned lev
if (i)
plan += ", ";
m_args[i]->print(tdbb, plan, false, level, recurse);
m_args[i]->getLegacyPlan(tdbb, plan, level);
}
plan += ")";
}
void MergeJoin::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
{
planEntry.className = "MergeJoin";
planEntry.description.add() = "Merge Join (inner)";
printOptInfo(planEntry.description);
if (recurse)
{
++level;
for (const auto arg : m_args)
arg->getPlan(tdbb, planEntry.children.add(), level, recurse);
}
}
void MergeJoin::markRecursive()

View File

@ -208,51 +208,9 @@ WriteLockResult NestedLoopJoin::lockRecord(thread_db* /*tdbb*/, bool /*skipLocke
status_exception::raise(Arg::Gds(isc_record_lock_not_supp));
}
void NestedLoopJoin::getChildren(Array<const RecordSource*>& 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)
{
plan += printIndent(++level) + "Nested Loop Join ";
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);
}
}
else
{
level++;
plan += "JOIN (";
@ -261,11 +219,49 @@ void NestedLoopJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigne
if (i)
plan += ", ";
m_args[i]->print(tdbb, plan, false, level, recurse);
m_args[i]->getLegacyPlan(tdbb, plan, level);
}
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);
}
}
void NestedLoopJoin::markRecursive()

View File

@ -249,19 +249,7 @@ WriteLockResult ProcedureScan::lockRecord(thread_db* /*tdbb*/, bool /*skipLocked
status_exception::raise(Arg::Gds(isc_record_lock_not_supp));
}
void ProcedureScan::getChildren(Array<const RecordSource*>& children) const
{
}
void ProcedureScan::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const
{
if (detailed)
{
plan += printIndent(++level) + "Procedure " +
printName(tdbb, m_procedure->getName().toString(), m_alias) + " Scan";
printOptInfo(plan);
}
else
void ProcedureScan::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const
{
if (!level)
plan += "(";
@ -271,6 +259,20 @@ void ProcedureScan::print(thread_db* tdbb, string& plan, bool detailed, unsigned
if (!level)
plan += ")";
}
void ProcedureScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
{
planEntry.className = "ProcedureScan";
planEntry.description.add() = "Procedure " + printName(tdbb, m_procedure->getName().toString(), m_alias) + " Scan";
printOptInfo(planEntry.description);
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,

View File

@ -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<NonPooledPair<const PlanEntry*, const PlanEntry*>>& 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<NonPooledPair<const PlanEntry*, const PlanEntry*>> 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<string>& 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<string> 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<string>& 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
}

View File

@ -23,6 +23,7 @@
#ifndef JRD_RECORD_SOURCE_H
#define JRD_RECORD_SOURCE_H
#include <optional>
#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<const RecordSource*>& 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<Firebird::NonPooledPair<const PlanEntry*, const PlanEntry*>>& list) const;
void asString(Firebird::string& str) const;
public:
Firebird::string className{getPool()};
Firebird::ObjectsArray<Firebird::string> description{getPool()};
Firebird::ObjectsArray<PlanEntry> children{getPool()};
std::optional<ObjectType> 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<Firebird::string>& 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<Firebird::string>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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;

View File

@ -238,39 +238,34 @@ WriteLockResult RecursiveStream::lockRecord(thread_db* /*tdbb*/, bool /*skipLock
status_exception::raise(Arg::Gds(isc_record_lock_not_supp));
}
void RecursiveStream::getChildren(Array<const RecordSource*>& children) const
{
children.add(m_root);
children.add(m_inner);
}
void RecursiveStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const
{
if (detailed)
{
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
void RecursiveStream::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const
{
if (!level)
plan += "(";
m_root->print(tdbb, plan, false, level + 1, recurse);
m_root->getLegacyPlan(tdbb, plan, level + 1);
plan += ", ";
m_inner->print(tdbb, plan, false, level + 1, recurse);
m_inner->getLegacyPlan(tdbb, plan, level + 1);
if (!level)
plan += ")";
}
void RecursiveStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
{
planEntry.className = "RecursiveStream";
planEntry.description.add() = "Recursion";
printOptInfo(planEntry.description);
if (recurse)
{
++level;
m_root->getPlan(tdbb, planEntry.children.add(), level, recurse);
m_inner->getPlan(tdbb, planEntry.children.add(), level, recurse);
}
}
void RecursiveStream::markRecursive()

View File

@ -146,21 +146,23 @@ WriteLockResult SingularStream::lockRecord(thread_db* tdbb, bool skipLocked) con
return m_next->lockRecord(tdbb, skipLocked);
}
void SingularStream::getChildren(Array<const RecordSource*>& 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()

View File

@ -116,21 +116,23 @@ WriteLockResult SkipRowsStream::lockRecord(thread_db* tdbb, bool skipLocked) con
return m_next->lockRecord(tdbb, skipLocked);
}
void SkipRowsStream::getChildren(Array<const RecordSource*>& 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()

View File

@ -121,36 +121,41 @@ WriteLockResult SortedStream::lockRecord(thread_db* tdbb, bool skipLocked) const
return m_next->lockRecord(tdbb, skipLocked);
}
void SortedStream::getChildren(Array<const RecordSource*>& 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
{
if (detailed)
void SortedStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
{
planEntry.className = "SortedStream";
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";
auto planDescription = &planEntry.description.add();
plan += printIndent(++level) +
((m_map->flags & FLAG_PROJECT) ? "Unique Sort" : "Sort") + extras;
printOptInfo(plan);
if (m_map->flags & FLAG_REFETCH)
{
*planDescription = "Refetch";
planDescription = &planEntry.description.add();
++level;
}
*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)
m_next->print(tdbb, plan, true, level, recurse);
}
else
{
level++;
plan += "SORT (";
m_next->print(tdbb, plan, false, level, recurse);
plan += ")";
++level;
m_next->getPlan(tdbb, planEntry.children.add(), level, recurse);
}
}

View File

@ -162,26 +162,7 @@ WriteLockResult Union::lockRecord(thread_db* tdbb, bool skipLocked) const
return m_args[impure->irsb_count]->lockRecord(tdbb, skipLocked);
}
void Union::getChildren(Array<const RecordSource*>& children) const
{
for (FB_SIZE_T i = 0; i < m_args.getCount(); i++)
children.add(m_args[i]);
}
void Union::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const
{
if (detailed)
{
plan += printIndent(++level) + (m_args.getCount() == 1 ? "Materialize" : "Union");
printOptInfo(plan);
if (recurse)
{
for (FB_SIZE_T i = 0; i < m_args.getCount(); i++)
m_args[i]->print(tdbb, plan, true, level, recurse);
}
}
else
void Union::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const
{
if (!level)
plan += "(";
@ -191,12 +172,27 @@ void Union::print(thread_db* tdbb, string& plan, bool detailed, unsigned level,
if (i)
plan += ", ";
m_args[i]->print(tdbb, plan, false, level + 1, recurse);
m_args[i]->getLegacyPlan(tdbb, plan, level + 1);
}
if (!level)
plan += ")";
}
void Union::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
{
planEntry.className = "Union";
planEntry.description.add() = (m_args.getCount() == 1 ? "Materialize" : "Union");
printOptInfo(planEntry.description);
if (recurse)
{
++level;
for (const auto arg : m_args)
arg->getPlan(tdbb, planEntry.children.add(), level, recurse);
}
}
void Union::markRecursive()

View File

@ -109,19 +109,7 @@ WriteLockResult VirtualTableScan::lockRecord(thread_db* /*tdbb*/, bool /*skipLoc
status_exception::raise(Arg::Gds(isc_record_lock_not_supp));
}
void VirtualTableScan::getChildren(Array<const RecordSource*>& children) const
{
}
void VirtualTableScan::print(thread_db* tdbb, string& plan, bool detailed, 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
void VirtualTableScan::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const
{
if (!level)
plan += "(";
@ -131,4 +119,17 @@ void VirtualTableScan::print(thread_db* tdbb, string& plan, bool detailed, unsig
if (!level)
plan += ")";
}
void VirtualTableScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
{
planEntry.className = "VirtualTableScan";
planEntry.description.add() = "Table " + printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Full Scan";
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;
}

View File

@ -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<const RecordSource*>& 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<BufferedStream> m_next;
};
@ -147,21 +148,23 @@ namespace
return m_next->lockRecord(tdbb, skipLocked);
}
void BufferedStreamWindow::getChildren(Array<const RecordSource*>& 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<const RecordSource*>& 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<const RecordSource*>& 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

View File

@ -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 <adrianosf@gmail.com>
* 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<ISC_QUAD*>(&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<NonPooledPair<const PlanEntry*, const PlanEntry*>> 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<ExplainInput, ExplainOutput, explainProcedure>(),
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
{
}
)
{
}

View File

@ -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 <adrianosf@gmail.com>
* 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<ExplainOutput::Type> resultEntries{*getDefaultMemoryPool()};
Firebird::Array<ExplainOutput::Type>::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