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
Engine_Objects:= $(call dirObjects,jrd) $(call dirObjects,dsql) $(call dirObjects,jrd/extds) \ 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) $(call makeObjects,lock,lock.cpp)
Engine_Test_Objects:= $(call dirObjects,jrd/tests) Engine_Test_Objects:= $(call dirObjects,jrd/tests)

View File

@ -161,6 +161,7 @@
<ClCompile Include="..\..\..\src\jrd\sqz.cpp" /> <ClCompile Include="..\..\..\src\jrd\sqz.cpp" />
<ClCompile Include="..\..\..\src\jrd\Statement.cpp" /> <ClCompile Include="..\..\..\src\jrd\Statement.cpp" />
<ClCompile Include="..\..\..\src\jrd\svc.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\SysFunction.cpp" />
<ClCompile Include="..\..\..\src\jrd\SystemPackages.cpp" /> <ClCompile Include="..\..\..\src\jrd\SystemPackages.cpp" />
<ClCompile Include="..\..\..\src\jrd\TempSpace.cpp" /> <ClCompile Include="..\..\..\src\jrd\TempSpace.cpp" />
@ -345,6 +346,7 @@
<ClInclude Include="..\..\..\src\jrd\status.h" /> <ClInclude Include="..\..\..\src\jrd\status.h" />
<ClInclude Include="..\..\..\src\jrd\svc.h" /> <ClInclude Include="..\..\..\src\jrd\svc.h" />
<ClInclude Include="..\..\..\src\jrd\svc_undoc.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\SysFunction.h" />
<ClInclude Include="..\..\..\src\jrd\SystemPackages.h" /> <ClInclude Include="..\..\..\src\jrd\SystemPackages.h" />
<ClInclude Include="..\..\..\src\jrd\TempSpace.h" /> <ClInclude Include="..\..\..\src\jrd\TempSpace.h" />
@ -513,4 +515,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">
</ImportGroup> </ImportGroup>
</Project> </Project>

View File

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

View File

@ -324,3 +324,32 @@ RDB$RELATIONS | 59| | | |
-- turn per-table stats off, using shortened name -- turn per-table stats off, using shortened name
SQL> SET PER_TAB OFF; 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()); return iterator(this, getCount());
} }
iterator back() T& front()
{ {
fb_assert(getCount() > 0); 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 const_iterator begin() const

View File

@ -1708,16 +1708,16 @@ UnicodeUtil::Utf16Collation* UnicodeUtil::Utf16Collation::create(
++secondKeyDataIt; ++secondKeyDataIt;
} }
unsigned backSize = commonKeys.back()->getCount(); unsigned backSize = commonKeys.back().getCount();
if (common > backSize) if (common > backSize)
commonKeys.back()->append(secondKeyIt->begin() + backSize, common - backSize); commonKeys.back().append(secondKeyIt->begin() + backSize, common - backSize);
else if (common < backSize) else if (common < backSize)
{ {
if (common == 0) if (common == 0)
commonKeys.push(*secondKeyIt); commonKeys.push(*secondKeyIt);
else else
commonKeys.back()->resize(common); commonKeys.back().resize(common);
} }
if (++secondKeyIt != keySet.end()) if (++secondKeyIt != keySet.end())

View File

@ -2505,7 +2505,7 @@ column_constraint_def($addColumnClause)
: constraint_name_opt column_constraint($addColumnClause) : constraint_name_opt column_constraint($addColumnClause)
{ {
if ($1) 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, 202, NO_PUBLICATIONS, "There is no publications in this database")
FB_IMPL_MSG_SYMBOL(ISQL, 203, MSG_PUBLICATIONS, "Publications:") FB_IMPL_MSG_SYMBOL(ISQL, 203, MSG_PUBLICATIONS, "Publications:")
FB_IMPL_MSG_SYMBOL(ISQL, 204, MSG_PROCEDURES, "Procedures:") 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 bool get_numeric(const UCHAR*, USHORT, SSHORT*, SINT64*);
static void print_set(const char* str, bool v); static void print_set(const char* str, bool v);
static processing_state print_sets(); static processing_state print_sets();
static processing_state explain(const TEXT*);
static processing_state help(const TEXT*); static processing_state help(const TEXT*);
static bool isyesno(const TEXT*); static bool isyesno(const TEXT*);
static processing_state newdb(TEXT*, const TEXT*, const TEXT*, int, const TEXT*, bool); static processing_state newdb(TEXT*, const TEXT*, const TEXT*, int, const TEXT*, bool);
@ -587,6 +588,7 @@ public:
KeepTranParams = true; KeepTranParams = true;
TranParams->assign(DEFAULT_DML_TRANS_SQL); TranParams->assign(DEFAULT_DML_TRANS_SQL);
PerTableStats = false; PerTableStats = false;
ExplainCommand = false;
} }
ColList global_Cols; ColList global_Cols;
@ -611,6 +613,7 @@ public:
SCHAR ISQL_charset[MAXCHARSET_SIZE]; SCHAR ISQL_charset[MAXCHARSET_SIZE];
bool KeepTranParams; bool KeepTranParams;
bool PerTableStats; bool PerTableStats;
bool ExplainCommand;
}; };
static SetValues setValues; static SetValues setValues;
@ -5009,7 +5012,7 @@ static processing_state frontend(const TEXT* statement)
{ {
show, add, copy, show, add, copy,
blobview, output, shell, set, create, drop, connect, blobview, output, shell, set, create, drop, connect,
edit, input, quit, exit, help, edit, input, quit, exit, explain, help,
#ifdef DEV_BUILD #ifdef DEV_BUILD
passthrough, passthrough,
#endif #endif
@ -5039,6 +5042,7 @@ static processing_state frontend(const TEXT* statement)
{FrontOptions::input, "INPUT", 2}, {FrontOptions::input, "INPUT", 2},
{FrontOptions::quit, "QUIT", 0}, {FrontOptions::quit, "QUIT", 0},
{FrontOptions::exit, "EXIT", 0}, {FrontOptions::exit, "EXIT", 0},
{FrontOptions::explain, "EXPLAIN", 0},
{FrontOptions::help, "?", 0}, {FrontOptions::help, "?", 0},
{FrontOptions::help, "HELP", 0} {FrontOptions::help, "HELP", 0}
#ifdef DEV_BUILD #ifdef DEV_BUILD
@ -5236,6 +5240,10 @@ static processing_state frontend(const TEXT* statement)
ret = EXIT; ret = EXIT;
break; break;
case FrontOptions::explain:
ret = explain(cmd + 7);
break;
case FrontOptions::help: case FrontOptions::help:
ret = help(parms[1]); ret = help(parms[1]);
break; 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) 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_BLOBED, // BLOBVIEW <blobid> -- view BLOB in text editor
HLP_EDIT, // EDIT [<filename>] -- edit SQL script file and execute HLP_EDIT, // EDIT [<filename>] -- edit SQL script file and execute
HLP_EDIT2, // EDIT -- edit current command buffer 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_HELP, // HELP -- display this menu
HLP_INPUT, // INput <filename> -- take input from the named SQL file HLP_INPUT, // INput <filename> -- take input from the named SQL file
HLP_OUTPUT, // OUTput [<filename>] -- write output to named 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) statement_type != isc_info_sql_stmt_set_generator)
{ {
process_plan(); 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]) 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())); fb_assert(profileStatement->definedCursors.exist(recordSource->getCursorId()));
struct PlanItem : PermanentStorage PlanEntry rootEntry;
{ recordSource->getPlan(tdbb, rootEntry, 0, true);
explicit PlanItem(MemoryPool& p)
: PermanentStorage(p)
{
}
const AccessPath* recordSource = nullptr; Array<NonPooledPair<const PlanEntry*, const PlanEntry*>> flatPlan;
const AccessPath* parentRecordSource = nullptr; rootEntry.asFlatList(flatPlan);
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;
}
}
NonPooledMap<ULONG, ULONG> idSequenceMap; NonPooledMap<ULONG, ULONG> idSequenceMap;
auto sequencePtr = profileStatement->cursorNextSequence.getOrPut(recordSource->getCursorId()); 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 cursorId = planEntry->accessPath->getCursorId();
const auto recSourceId = planItem.recordSource->getRecSourceId(); const auto recSourceId = planEntry->accessPath->getRecSourceId();
idSequenceMap.put(recSourceId, ++*sequencePtr); idSequenceMap.put(recSourceId, ++*sequencePtr);
ULONG parentSequence = 0; ULONG parentSequence = 0;
if (planItem.parentRecordSource) if (parentPlanEntry)
parentSequence = *idSequenceMap.get(planItem.parentRecordSource->getRecSourceId()); parentSequence = *idSequenceMap.get(parentPlanEntry->accessPath->getRecSourceId());
string accessPath;
planEntry->getDescriptionAsString(accessPath);
currentSession->pluginSession->defineRecordSource(profileStatement->id, cursorId, 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); profileStatement->recSourceSequence.put(recSourceId, *sequencePtr);
} }

View File

@ -239,51 +239,33 @@ struct bid
ULONG& bid_temp_id() ULONG& bid_temp_id()
{ {
// Make sure that compiler packed structure like we wanted
fb_assert(sizeof(*this) == 8);
return bid_internal.bid_temp_id(); return bid_internal.bid_temp_id();
} }
ULONG bid_temp_id() const 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(); return bid_internal.bid_temp_id();
} }
bool isEmpty() const 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; return bid_quad.bid_quad_high == 0 && bid_quad.bid_quad_low == 0;
} }
void clear() 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_high = 0;
bid_quad.bid_quad_low = 0; bid_quad.bid_quad_low = 0;
} }
void set_temporary(ULONG temp_id) void set_temporary(ULONG temp_id)
{ {
// Make sure that compiler packed structure like we wanted
fb_assert(sizeof(*this) == 8);
clear(); clear();
bid_temp_id() = temp_id; bid_temp_id() = temp_id;
} }
void set_permanent(USHORT relation_id, RecordNumber num) void set_permanent(USHORT relation_id, RecordNumber num)
{ {
// Make sure that compiler packed structure like we wanted
fb_assert(sizeof(*this) == 8);
clear(); clear();
bid_internal.bid_relation_id = relation_id; bid_internal.bid_relation_id = relation_id;
num.bid_encode(&bid_internal); num.bid_encode(&bid_internal);
@ -291,19 +273,18 @@ struct bid
RecordNumber get_permanent_number() const RecordNumber get_permanent_number() const
{ {
// Make sure that compiler packed structure like we wanted
fb_assert(sizeof(*this) == 8);
RecordNumber temp; RecordNumber temp;
temp.bid_decode(&bid_internal); temp.bid_decode(&bid_internal);
return temp; 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 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 && return bid_quad.bid_quad_high == other.bid_quad.bid_quad_high &&
bid_quad.bid_quad_low == other.bid_quad.bid_quad_low; 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 } // namespace Jrd

View File

@ -275,6 +275,11 @@ public:
bool isVirtual() const; bool isVirtual() const;
bool isView() const; bool isView() const;
ObjectType getObjectType() const
{
return isView() ? obj_view : obj_relation;
}
bool isReplicating(thread_db* tdbb); bool isReplicating(thread_db* tdbb);
// global temporary relations attributes // global temporary relations attributes

View File

@ -730,6 +730,15 @@ string Statement::getPlan(thread_db* tdbb, bool detailed) const
return plan; 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. // Check that we have enough rights to access all resources this list of triggers touches.
void Statement::verifyTriggerAccess(thread_db* tdbb, jrd_rel* ownerRelation, void Statement::verifyTriggerAccess(thread_db* tdbb, jrd_rel* ownerRelation,
TrigVector* triggers, MetaName userName) TrigVector* triggers, MetaName userName)

View File

@ -28,6 +28,8 @@
namespace Jrd { namespace Jrd {
class PlanEntry;
// Compiled statement. // Compiled statement.
class Statement : public pool_alloc<type_req> class Statement : public pool_alloc<type_req>
{ {
@ -78,6 +80,7 @@ public:
void release(thread_db* tdbb); void release(thread_db* tdbb);
Firebird::string getPlan(thread_db* tdbb, bool detailed) const; Firebird::string getPlan(thread_db* tdbb, bool detailed) const;
void getPlan(thread_db* tdbb, PlanEntry& planEntry) const;
private: private:
static void verifyTriggerAccess(thread_db* tdbb, jrd_rel* ownerRelation, TrigVector* triggers, static void verifyTriggerAccess(thread_db* tdbb, jrd_rel* ownerRelation, TrigVector* triggers,

View File

@ -25,6 +25,7 @@
#include "../jrd/BlobUtil.h" #include "../jrd/BlobUtil.h"
#include "../jrd/TimeZone.h" #include "../jrd/TimeZone.h"
#include "../jrd/ProfilerManager.h" #include "../jrd/ProfilerManager.h"
#include "../jrd/sys-packages/SqlPackage.h"
using namespace Firebird; using namespace Firebird;
using namespace Jrd; using namespace Jrd;
@ -40,6 +41,7 @@ namespace
list->add(TimeZonePackage(pool)); list->add(TimeZonePackage(pool));
list->add(ProfilerPackage(pool)); list->add(ProfilerPackage(pool));
list->add(BlobUtilPackage(pool)); list->add(BlobUtilPackage(pool));
list->add(SqlPackage(pool));
} }
static InitInstance<SystemPackagesInit> INSTANCE; 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_12_0 = ENCODE_ODS(ODS_VERSION12, 0);
const USHORT ODS_13_0 = ENCODE_ODS(ODS_VERSION13, 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_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; const USHORT ODS_FIREBIRD_FLAG = 0x8000;

View File

@ -1970,7 +1970,7 @@ unsigned Optimizer::distributeEqualities(BoolExprNodeStack& orgStack, unsigned b
ValueExprNodeStack& s = classes.add(); ValueExprNodeStack& s = classes.add();
s.push(node1); s.push(node1);
s.push(node2); 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); 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) planEntry.className = "AggregatedStream";
{
plan += printIndent(++level) + "Aggregate"; planEntry.description.add() = "Aggregate";
printOptInfo(plan); printOptInfo(planEntry.description);
}
if (recurse) 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 bool AggregatedStream::internalGetRecord(thread_db* tdbb) const

View File

@ -122,32 +122,32 @@ bool BitmapTableScan::internalGetRecord(thread_db* tdbb) const
return false; return false;
} }
void BitmapTableScan::getChildren(Array<const RecordSource*>& 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, void BitmapTableScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
bool detailed, unsigned level, bool recurse) const
{ {
if (detailed) planEntry.className = "BitmapTableScan";
{
plan += printIndent(++level) + "Table " +
printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Access By ID";
printOptInfo(plan); planEntry.description.add() = "Table " + printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Access By ID";
printInversion(tdbb, m_inversion, plan, true, level); printOptInfo(planEntry.description);
}
else
{
if (!level)
plan += "(";
plan += printName(tdbb, m_alias, false) + " INDEX ("; printInversion(tdbb, m_inversion, planEntry.description, true);
string indices;
printInversion(tdbb, m_inversion, indices, false, level);
plan += indices + ")";
if (!level) planEntry.objectType = m_relation->getObjectType();
plan += ")"; 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); 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 void BufferedStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
{ {
if (detailed) planEntry.className = "BufferedStream";
{
string extras;
extras.printf(" (record length: %" ULONGFORMAT")", m_format->fmt_length);
plan += printIndent(++level) + "Record Buffer" + extras; string extras;
printOptInfo(plan); 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) if (recurse)
m_next->print(tdbb, plan, detailed, level, recurse); m_next->getPlan(tdbb, planEntry.children.add(), ++level, recurse);
} }
void BufferedStream::markRecursive() void BufferedStream::markRecursive()

View File

@ -114,38 +114,33 @@ WriteLockResult ConditionalStream::lockRecord(thread_db* tdbb, bool skipLocked)
return impure->irsb_next->lockRecord(tdbb, skipLocked); return impure->irsb_next->lockRecord(tdbb, skipLocked);
} }
void ConditionalStream::getChildren(Array<const RecordSource*>& children) const void ConditionalStream::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const
{ {
children.add(m_first); if (!level)
children.add(m_second); 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"; ++level;
printOptInfo(plan); m_first->getPlan(tdbb, planEntry.children.add(), level, recurse);
m_second->getPlan(tdbb, planEntry.children.add(), level, recurse);
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 += ")";
} }
} }

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())
{ {
if (m_rse->isSubQuery()) planEntry.description.add() = "Sub-query";
{
plan += "\nSub-query";
if (m_rse->isInvariant()) if (m_rse->isInvariant())
plan += " (invariant)"; planEntry.description.back() += " (invariant)";
} }
else if (m_cursorName.hasData()) else if (m_cursorName.hasData())
{ {
plan += "\nCursor \"" + string(m_cursorName) + "\""; planEntry.description.add() = "Cursor \"" + string(m_cursorName) + "\"";
if (m_rse->isScrollable()) if (m_rse->isScrollable())
plan += " (scrollable)"; planEntry.description.back() += " (scrollable)";
}
else
plan += "\nSelect Expression";
if (m_line || m_column)
{
string pos;
pos.printf(" (line %u, column %u)", m_line, m_column);
plan += pos;
}
} }
else else
{ planEntry.description.add() = "Select Expression";
if (m_line || m_column)
{
string pos;
pos.printf("\n-- line %u, column %u", m_line, m_column);
plan += pos;
}
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) 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 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 void printPlan(thread_db* tdbb, Firebird::string& plan, bool detailed) const
{ {
print(tdbb, plan, detailed, 0, true); if (detailed)
} {
PlanEntry planEntry;
void print(thread_db* tdbb, Firebird::string& plan, getPlan(tdbb, planEntry, 0, true);
bool detailed, unsigned level, bool recurse) const override; planEntry.asString(plan);
}
void getChildren(Firebird::Array<const RecordSource*>& children) const override else
{ getLegacyPlan(tdbb, plan, 0);
children.add(m_root);
} }
virtual void open(thread_db* tdbb) const = 0; virtual void open(thread_db* tdbb) const = 0;
virtual void close(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: protected:
const RecordSource* const m_root; const RecordSource* const m_root;
const RseNode* const m_rse; const RseNode* const m_rse;

View File

@ -115,27 +115,27 @@ WriteLockResult ExternalTableScan::lockRecord(thread_db* tdbb, bool skipLocked)
status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); status_exception::raise(Arg::Gds(isc_record_lock_not_supp));
} }
void ExternalTableScan::getChildren(Array<const RecordSource*>& 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, void ExternalTableScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
bool detailed, unsigned level, bool recurse) const
{ {
if (detailed) planEntry.className = "ExternalTableScan";
{
plan += printIndent(++level) + "Table " +
printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Full Scan";
printOptInfo(plan);
}
else
{
if (!level)
plan += "(";
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) planEntry.objectType = m_relation->getObjectType();
plan += ")"; 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); 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) planEntry.className = "FilteredStream";
{
plan += printIndent(++level) + "Filter";
if (m_invariant) planEntry.description.add() = "Filter";
plan += " (preliminary)";
printOptInfo(plan); if (m_invariant)
} planEntry.description.back() += " (preliminary)";
printOptInfo(planEntry.description);
if (recurse) if (recurse)
m_next->print(tdbb, plan, detailed, level, recurse); m_next->getPlan(tdbb, planEntry.children.add(), ++level, recurse);
} }
void FilteredStream::markRecursive() void FilteredStream::markRecursive()

View File

@ -120,21 +120,23 @@ WriteLockResult FirstRowsStream::lockRecord(thread_db* tdbb, bool skipLocked) co
return m_next->lockRecord(tdbb, skipLocked); 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) planEntry.className = "FirstRowsStream";
{
plan += printIndent(++level) + "First N Records"; planEntry.description.add() = "First N Records";
printOptInfo(plan); printOptInfo(planEntry.description);
}
if (recurse) if (recurse)
m_next->print(tdbb, plan, detailed, level, recurse); {
++level;
m_next->getPlan(tdbb, planEntry.children.add(), level, recurse);
}
} }
void FirstRowsStream::markRecursive() void FirstRowsStream::markRecursive()

View File

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

View File

@ -162,44 +162,45 @@ bool FullTableScan::internalGetRecord(thread_db* tdbb) const
return false; 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 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; if (range->lower)
for (const auto range : m_dbkeyRanges) lowerBounds++;
{
if (range->lower)
lowerBounds++;
if (range->upper) if (range->upper)
upperBounds++; 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);
} }
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) planEntry.description.add() = "Table " + printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Full Scan" + bounds;
plan += ")"; 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

@ -454,43 +454,37 @@ WriteLockResult HashJoin::lockRecord(thread_db* /*tdbb*/, bool /*skipLocked*/) c
status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); status_exception::raise(Arg::Gds(isc_record_lock_not_supp));
} }
void HashJoin::getChildren(Array<const RecordSource*>& 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++) 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)"; ++level;
printOptInfo(plan);
if (recurse) m_leader.source->getPlan(tdbb, planEntry.children.add(), level, recurse);
{
m_leader.source->print(tdbb, plan, true, level, recurse);
for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) for (const auto& arg : m_args)
m_args[i].source->print(tdbb, plan, true, level, recurse); arg.source->getPlan(tdbb, planEntry.children.add(), 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 += ")";
} }
} }

View File

@ -352,44 +352,45 @@ bool IndexTableScan::internalGetRecord(thread_db* tdbb) const
return false; return false;
} }
void IndexTableScan::getChildren(Array<const RecordSource*>& 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) planEntry.className = "IndexTableScan";
{
plan += printIndent(++level) + "Table " +
printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Access By ID";
printOptInfo(plan); planEntry.description.add() = "Table " + printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Access By ID";
printInversion(tdbb, m_index, plan, true, level, true); printOptInfo(planEntry.description);
if (m_inversion) printInversion(tdbb, m_index, planEntry.description, true, true);
printInversion(tdbb, m_inversion, plan, true, ++level);
}
else
{
if (!level)
plan += "(";
plan += printName(tdbb, m_alias, false) + " ORDER "; planEntry.objectType = m_relation->getObjectType();
string index; planEntry.objectName = m_relation->rel_name;
printInversion(tdbb, m_index, index, false, level);
plan += index;
if (m_inversion) if (m_alias.hasData() && m_relation->rel_name != m_alias)
{ planEntry.alias = m_alias;
plan += " INDEX (";
string indices;
printInversion(tdbb, m_inversion, indices, false, level);
plan += indices + ")";
}
if (!level) if (m_inversion)
plan += ")"; printInversion(tdbb, m_inversion, planEntry.description, true);
}
} }
int IndexTableScan::compareKeys(const index_desc* idx, 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; impure->irsb_flags &= ~irsb_open;
} }
void LocalTableStream::getChildren(Array<const RecordSource*>& children) const
{
}
bool LocalTableStream::internalGetRecord(thread_db* tdbb) const bool LocalTableStream::internalGetRecord(thread_db* tdbb) const
{ {
JRD_reschedule(tdbb); 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)); 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. //// TODO: Use Local Table name/alias.
if (detailed) if (!level)
{ plan += "(";
plan += printIndent(++level) + "Local Table Full Scan";
printOptInfo(plan);
}
else
{
if (!level)
plan += "(";
plan += "Local_Table"; plan += "Local_Table";
plan += " NATURAL"; plan += " NATURAL";
if (!level) if (!level)
plan += ")"; 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); 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) planEntry.className = "LockedStream";
{
plan += printIndent(++level) + "Write Lock"; planEntry.description.add() = "Write Lock";
printOptInfo(plan); printOptInfo(planEntry.description);
}
if (recurse) if (recurse)
m_next->print(tdbb, plan, detailed, level, recurse); {
++level;
m_next->getPlan(tdbb, planEntry.children.add(), level, recurse);
}
} }
void LockedStream::markRecursive() void LockedStream::markRecursive()

View File

@ -345,37 +345,33 @@ WriteLockResult MergeJoin::lockRecord(thread_db* /*tdbb*/, bool /*skipLocked*/)
status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); status_exception::raise(Arg::Gds(isc_record_lock_not_supp));
} }
void MergeJoin::getChildren(Array<const RecordSource*>& 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++) 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) planEntry.className = "MergeJoin";
{
plan += printIndent(++level) + "Merge Join (inner)";
printOptInfo(plan);
if (recurse) planEntry.description.add() = "Merge Join (inner)";
{ printOptInfo(planEntry.description);
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 += ", ";
m_args[i]->print(tdbb, plan, false, level, recurse); if (recurse)
} {
plan += ")"; ++level;
for (const auto arg : m_args)
arg->getPlan(tdbb, planEntry.children.add(), level, recurse);
} }
} }

View File

@ -208,63 +208,59 @@ WriteLockResult NestedLoopJoin::lockRecord(thread_db* /*tdbb*/, bool /*skipLocke
status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); status_exception::raise(Arg::Gds(isc_record_lock_not_supp));
} }
void NestedLoopJoin::getChildren(Array<const RecordSource*>& children) const void NestedLoopJoin::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) 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
{ {
if (m_args.hasData()) 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) m_args[i]->getLegacyPlan(tdbb, plan, level);
{
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 plan += ")";
{ }
level++; }
plan += "JOIN (";
for (FB_SIZE_T i = 0; i < m_args.getCount(); i++)
{
if (i)
plan += ", ";
m_args[i]->print(tdbb, plan, false, level, recurse); void NestedLoopJoin::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
} {
plan += ")"; 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);
} }
} }

View File

@ -249,28 +249,30 @@ WriteLockResult ProcedureScan::lockRecord(thread_db* /*tdbb*/, bool /*skipLocked
status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); status_exception::raise(Arg::Gds(isc_record_lock_not_supp));
} }
void ProcedureScan::getChildren(Array<const RecordSource*>& 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) planEntry.className = "ProcedureScan";
{
plan += printIndent(++level) + "Procedure " +
printName(tdbb, m_procedure->getName().toString(), m_alias) + " Scan";
printOptInfo(plan);
}
else
{
if (!level)
plan += "(";
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) planEntry.objectType = obj_procedure;
plan += ")"; 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, 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 // Record source class
// ------------------- // -------------------
@ -98,40 +154,32 @@ string RecordSource::printName(thread_db* tdbb, const string& name, const string
return result; 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, 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) const bool wasEmpty = planLines.isEmpty();
plan += printIndent(++level); auto plan = &planLines.add();
switch (inversion->type) switch (inversion->type)
{ {
case InversionNode::TYPE_AND: case InversionNode::TYPE_AND:
if (detailed) if (detailed)
plan += "Bitmap And"; *plan += "Bitmap And";
printInversion(tdbb, inversion->node1, plan, detailed, level); printInversion(tdbb, inversion->node1, planLines, detailed);
printInversion(tdbb, inversion->node2, plan, detailed, level); printInversion(tdbb, inversion->node2, planLines, detailed);
break; break;
case InversionNode::TYPE_OR: case InversionNode::TYPE_OR:
case InversionNode::TYPE_IN: case InversionNode::TYPE_IN:
if (detailed) if (detailed)
plan += "Bitmap Or"; *plan += "Bitmap Or";
printInversion(tdbb, inversion->node1, plan, detailed, level); printInversion(tdbb, inversion->node1, planLines, detailed);
printInversion(tdbb, inversion->node2, plan, detailed, level); printInversion(tdbb, inversion->node2, planLines, detailed);
break; break;
case InversionNode::TYPE_DBKEY: case InversionNode::TYPE_DBKEY:
if (detailed) if (detailed)
plan += "DBKEY"; *plan += "DBKEY";
break; break;
case InversionNode::TYPE_INDEX: case InversionNode::TYPE_INDEX:
@ -147,7 +195,10 @@ void RecordSource::printInversion(thread_db* tdbb, const InversionNode* inversio
if (detailed) if (detailed)
{ {
if (!navigation) if (!navigation)
plan += "Bitmap" + printIndent(++level); {
*plan += "Bitmap";
plan = &planLines.add();
}
const index_desc& idx = retrieval->irb_desc; const index_desc& idx = retrieval->irb_desc;
const bool uniqueIdx = (idx.idx_flags & idx_unique); 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; (fullscan ? " Full" : unique ? " Unique" : list ? " List" : " Range") + " Scan" + bounds;
} }
else else
{ *plan += (wasEmpty ? "" : ", ") + printName(tdbb, indexName.c_str(), false);
plan += (plan.hasData() ? ", " : "") + printName(tdbb, indexName.c_str(), false);
}
} }
break; 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 #ifdef PRINT_OPT_INFO
fb_assert(planLines.hasData());
string info; string info;
// Add 0.5 to convert double->int truncation into rounding // Add 0.5 to convert double->int truncation into rounding
info.printf(" [rows: %" UQUADFORMAT "]", (FB_UINT64) (m_cardinality + 0.5)); info.printf(" [rows: %" UQUADFORMAT "]", (FB_UINT64) (m_cardinality + 0.5));
plan += info; planLines.back() += info;
#endif #endif
} }

View File

@ -23,6 +23,7 @@
#ifndef JRD_RECORD_SOURCE_H #ifndef JRD_RECORD_SOURCE_H
#define JRD_RECORD_SOURCE_H #define JRD_RECORD_SOURCE_H
#include <optional>
#include "../common/classes/array.h" #include "../common/classes/array.h"
#include "../common/classes/objects_array.h" #include "../common/classes/objects_array.h"
#include "../common/classes/NestConst.h" #include "../common/classes/NestConst.h"
@ -50,6 +51,7 @@ namespace Jrd
struct win; struct win;
class BaseBufferedStream; class BaseBufferedStream;
class BufferedStream; class BufferedStream;
class PlanEntry;
enum JoinType { INNER_JOIN, OUTER_JOIN, SEMI_JOIN, ANTI_JOIN }; enum JoinType { INNER_JOIN, OUTER_JOIN, SEMI_JOIN, ANTI_JOIN };
@ -71,16 +73,56 @@ namespace Jrd
return m_recSourceId; return m_recSourceId;
} }
virtual void print(thread_db* tdbb, Firebird::string& plan, double getCardinality() const
bool detailed, unsigned level, bool recurse) const = 0; {
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: private:
const ULONG m_cursorId; const ULONG m_cursorId;
const ULONG m_recSourceId; 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. // Abstract base class for record sources.
class RecordSource : public AccessPath class RecordSource : public AccessPath
{ {
@ -106,11 +148,6 @@ namespace Jrd
return true; return true;
} }
double getCardinality() const
{
return m_cardinality;
}
void open(thread_db* tdbb) const; void open(thread_db* tdbb) const;
bool getRecord(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, static Firebird::string printName(thread_db* tdbb, const Firebird::string& name,
const Firebird::string& alias); 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, static void printInversion(thread_db* tdbb, const InversionNode* inversion,
Firebird::string& plan, bool detailed, Firebird::string& plan, bool detailed,
unsigned level, bool navigation = false); 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 saveRecord(thread_db* tdbb, record_param* rpb);
static void restoreRecord(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 void internalOpen(thread_db* tdbb) const = 0;
virtual bool internalGetRecord(thread_db* tdbb) const = 0; virtual bool internalGetRecord(thread_db* tdbb) const = 0;
double m_cardinality = 0.0;
ULONG m_impure = 0; ULONG m_impure = 0;
bool m_recursive = false; bool m_recursive = false;
}; };
@ -191,12 +231,10 @@ namespace Jrd
void close(thread_db* tdbb) const override; 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;
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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 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;
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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 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 setInversion(InversionNode* inversion, BoolExprNode* condition) void setInversion(InversionNode* inversion, BoolExprNode* condition)
{ {
@ -277,6 +310,7 @@ namespace Jrd
} }
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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;
protected: protected:
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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: private:
jrd_rel* const m_relation; jrd_rel* const m_relation;
@ -342,12 +374,10 @@ namespace Jrd
bool refetchRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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;
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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;
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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 markRecursive() override; void markRecursive() override;
void invalidateRecords(Request* request) const override; void invalidateRecords(Request* request) const override;
@ -423,6 +448,7 @@ namespace Jrd
void nullRecords(thread_db* tdbb) const override; void nullRecords(thread_db* tdbb) const override;
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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 markRecursive() override; void markRecursive() override;
void invalidateRecords(Request* request) const override; void invalidateRecords(Request* request) const override;
@ -455,6 +478,7 @@ namespace Jrd
void nullRecords(thread_db* tdbb) const override; void nullRecords(thread_db* tdbb) const override;
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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 markRecursive() override; void markRecursive() override;
void invalidateRecords(Request* request) const override; void invalidateRecords(Request* request) const override;
@ -495,6 +516,7 @@ namespace Jrd
} }
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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 markRecursive() override; void markRecursive() override;
void invalidateRecords(Request* request) const override; void invalidateRecords(Request* request) const override;
@ -535,6 +554,7 @@ namespace Jrd
} }
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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 markRecursive() override; void markRecursive() override;
void invalidateRecords(Request* request) const override; void invalidateRecords(Request* request) const override;
@ -576,6 +593,7 @@ namespace Jrd
} }
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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 markRecursive() override; void markRecursive() override;
void invalidateRecords(Request* request) const override; void invalidateRecords(Request* request) const override;
@ -720,6 +735,7 @@ namespace Jrd
} }
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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); const NestValueArray* group, MapNode* map, RecordSource* next);
public: public:
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;
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
bool internalGetRecord(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override;
}; };
class WindowedStream : public RecordSource class WindowedStream : public RecordSource
@ -971,13 +986,13 @@ namespace Jrd
public: public:
void close(thread_db* tdbb) const override; 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 findUsedStreams(StreamList& streams, bool expandAll = false) const override;
void nullRecords(thread_db* tdbb) const override; void nullRecords(thread_db* tdbb) const override;
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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 markRecursive() override; void markRecursive() override;
void invalidateRecords(Request* request) const override; void invalidateRecords(Request* request) const override;
@ -1025,6 +1037,7 @@ namespace Jrd
void nullRecords(thread_db* tdbb) const override; void nullRecords(thread_db* tdbb) const override;
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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 markRecursive() override; void markRecursive() override;
void invalidateRecords(Request* request) const override; void invalidateRecords(Request* request) const override;
@ -1102,6 +1112,7 @@ namespace Jrd
} }
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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 markRecursive() override; void markRecursive() override;
void invalidateRecords(Request* request) const override; void invalidateRecords(Request* request) const override;
@ -1137,6 +1145,7 @@ namespace Jrd
void nullRecords(thread_db* tdbb) const override; void nullRecords(thread_db* tdbb) const override;
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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 markRecursive() override; void markRecursive() override;
void invalidateRecords(Request* request) const override; void invalidateRecords(Request* request) const override;
@ -1170,6 +1176,7 @@ namespace Jrd
void nullRecords(thread_db* tdbb) const override; void nullRecords(thread_db* tdbb) const override;
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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 markRecursive() override; void markRecursive() override;
void invalidateRecords(Request* request) const override; void invalidateRecords(Request* request) const override;
@ -1226,6 +1230,7 @@ namespace Jrd
static unsigned maxCapacity(); static unsigned maxCapacity();
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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 markRecursive() override; void markRecursive() override;
void invalidateRecords(Request* request) const override; void invalidateRecords(Request* request) const override;
@ -1290,6 +1292,7 @@ namespace Jrd
void nullRecords(thread_db* tdbb) const override; void nullRecords(thread_db* tdbb) const override;
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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 close(thread_db* tdbb) const override;
void getChildren(Firebird::Array<const RecordSource*>& children) const override;
bool refetchRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override;
void print(thread_db* tdbb, Firebird::string& plan, void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override;
bool detailed, unsigned level, bool recurse) const override;
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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 markRecursive() override; void markRecursive() override;
void invalidateRecords(Request* request) const override; void invalidateRecords(Request* request) const override;
void findUsedStreams(StreamList& streams, bool expandAll = false) const override; void findUsedStreams(StreamList& streams, bool expandAll = false) const override;
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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 markRecursive() override; void markRecursive() override;
void invalidateRecords(Request* request) const override; void invalidateRecords(Request* request) const override;
void findUsedStreams(StreamList& streams, bool expandAll = false) const override; void findUsedStreams(StreamList& streams, bool expandAll = false) const override;
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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 markRecursive() override; void markRecursive() override;
void invalidateRecords(Request* request) const override; void invalidateRecords(Request* request) const override;
@ -1443,6 +1437,7 @@ namespace Jrd
void nullRecords(thread_db* tdbb) const override; void nullRecords(thread_db* tdbb) const override;
protected: protected:
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override;
void internalOpen(thread_db* tdbb) const override; void internalOpen(thread_db* tdbb) const override;
bool internalGetRecord(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override;

View File

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

View File

@ -146,21 +146,23 @@ WriteLockResult SingularStream::lockRecord(thread_db* tdbb, bool skipLocked) con
return m_next->lockRecord(tdbb, skipLocked); 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) planEntry.className = "SingularStream";
{
plan += printIndent(++level) + "Singularity Check"; planEntry.description.add() = "Singularity Check";
printOptInfo(plan); printOptInfo(planEntry.description);
}
if (recurse) if (recurse)
m_next->print(tdbb, plan, detailed, level, recurse); {
++level;
m_next->getPlan(tdbb, planEntry.children.add(), level, recurse);
}
} }
void SingularStream::markRecursive() void SingularStream::markRecursive()

View File

@ -116,21 +116,23 @@ WriteLockResult SkipRowsStream::lockRecord(thread_db* tdbb, bool skipLocked) con
return m_next->lockRecord(tdbb, skipLocked); 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) planEntry.className = "SkipRowsStream";
{
plan += printIndent(++level) + "Skip N Records"; planEntry.description.add() = "Skip N Records";
printOptInfo(plan); printOptInfo(planEntry.description);
}
if (recurse) if (recurse)
m_next->print(tdbb, plan, detailed, level, recurse); {
++level;
m_next->getPlan(tdbb, planEntry.children.add(), level, recurse);
}
} }
void SkipRowsStream::markRecursive() void SkipRowsStream::markRecursive()

View File

@ -121,36 +121,41 @@ WriteLockResult SortedStream::lockRecord(thread_db* tdbb, bool skipLocked) const
return m_next->lockRecord(tdbb, skipLocked); 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, void SortedStream::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const
bool detailed, 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; *planDescription = "Refetch";
extras.printf(" (record length: %" ULONGFORMAT", key length: %" ULONGFORMAT")", planDescription = &planEntry.description.add();
m_map->length, m_map->keyLength); ++level;
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);
} }
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++; ++level;
plan += "SORT ("; m_next->getPlan(tdbb, planEntry.children.add(), level, recurse);
m_next->print(tdbb, plan, false, level, recurse);
plan += ")";
} }
} }

View File

@ -162,40 +162,36 @@ WriteLockResult Union::lockRecord(thread_db* tdbb, bool skipLocked) const
return m_args[impure->irsb_count]->lockRecord(tdbb, skipLocked); return m_args[impure->irsb_count]->lockRecord(tdbb, skipLocked);
} }
void Union::getChildren(Array<const RecordSource*>& 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++) 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"); ++level;
printOptInfo(plan);
if (recurse) for (const auto arg : m_args)
{ arg->getPlan(tdbb, planEntry.children.add(), level, 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 += ")";
} }
} }

View File

@ -109,26 +109,27 @@ WriteLockResult VirtualTableScan::lockRecord(thread_db* /*tdbb*/, bool /*skipLoc
status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); status_exception::raise(Arg::Gds(isc_record_lock_not_supp));
} }
void VirtualTableScan::getChildren(Array<const RecordSource*>& 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) planEntry.className = "VirtualTableScan";
{
plan += printIndent(++level) + "Table " +
printName(tdbb, m_relation->rel_name.c_str(), m_alias) + " Full Scan";
printOptInfo(plan);
}
else
{
if (!level)
plan += "(";
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) planEntry.objectType = m_relation->getObjectType();
plan += ")"; 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: public:
BufferedStreamWindow(CompilerScratch* csb, BufferedStream* next); BufferedStreamWindow(CompilerScratch* csb, BufferedStream* next);
void internalOpen(thread_db* tdbb) const override;
void close(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; bool refetchRecord(thread_db* tdbb) const override;
WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) const override; WriteLockResult lockRecord(thread_db* tdbb, bool skipLocked) 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 markRecursive() override; void markRecursive() override;
void invalidateRecords(Request* request) const override; void invalidateRecords(Request* request) const override;
@ -87,6 +83,11 @@ namespace
return impure->irsb_position; 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: public:
NestConst<BufferedStream> m_next; NestConst<BufferedStream> m_next;
}; };
@ -147,21 +148,23 @@ namespace
return m_next->lockRecord(tdbb, skipLocked); 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) planEntry.className = "BufferedStreamWindow";
{
plan += printIndent(++level) + "Window Buffer"; planEntry.description.add() = "Window Buffer";
printOptInfo(plan); printOptInfo(planEntry.description);
}
if (recurse) if (recurse)
m_next->print(tdbb, plan, detailed, level, recurse); {
++level;
m_next->getPlan(tdbb, planEntry.children.add(), level, recurse);
}
} }
void BufferedStreamWindow::markRecursive() 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)); 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) planEntry.className = "WindowedStream";
{
plan += printIndent(++level) + "Window"; planEntry.description.add() = "Window";
printOptInfo(plan); printOptInfo(planEntry.description);
}
if (recurse) if (recurse)
m_joinedStream->print(tdbb, plan, detailed, level, recurse); {
++level;
m_joinedStream->getPlan(tdbb, planEntry.children.add(), level, recurse);
}
} }
void WindowedStream::markRecursive() void WindowedStream::markRecursive()
@ -899,22 +904,24 @@ bool WindowedStream::WindowStream::internalGetRecord(thread_db* tdbb) const
return true; 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) planEntry.className = "WindowStream";
{
plan += printIndent(++level) + "Window Partition"; planEntry.description.add() = "Window Partition";
printOptInfo(plan); printOptInfo(planEntry.description);
}
if (recurse) 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 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