В предыдущей части мы изучали, как выполняется выборка строки из таблицы базы данных при выполнении запроса. Мы доизучались до того, что подсистема EXE в функции EXE_looper() в цикле выполняет метод StmtNode::execute(), специфические имплементации которого есть в каждом классе, унаследованном от StmtNode. В этой части мы пройдём по цепочке выполняющихся узлов.
Dsql@2
Мы снова оказываемся в подсистеме Dsql, именно тут реализованы классы, являющиеся потомками абстрактного класса StmtNode. Первым экземпляром, с которым мы столкнулись, будет StallNode.
const StmtNode* StallNode::execute(thread_db* /*tdbb*/, Request* request, ExeState* /*exeState*/) const {
switch (request->req_operation) {
case Request::req_evaluate:
case Request::req_return:
request->req_message = this;
request->req_operation = Request::req_return;
request->req_flags |= req_stall;
return this;
case Request::req_proceed:
request->req_operation = Request::req_return;
return parentStmt;
default:
return parentStmt;
}
}
Ага, тут что-то типа конечного автомата, event-ом выступает request->req_operation, в результате выполнения req_operation может поменяться. Возвращаемое значение - это следующее состояние. Самым первым значением req_operation будет req_sync, и в этом случае StallNode::execute() возвращает parentStmt, в качестве которого выступает ForNode.
const StmtNode* ForNode::execute(thread_db* tdbb, Request* request, ExeState* /*exeState*/) const {
jrd_tra* transaction = request->req_transaction;
ImpureMerge* merge = request->getImpure<ImpureMerge>(impureOffset);
Impure* impure = merge;
switch (request->req_operation) {
case Request::req_evaluate:
// initialize impure values
impure->savepoint = 0;
impure->writeLockMode = false;
if (marks & MARK_MERGE)
merge->recUpdated = nullptr;
if (!(transaction->tra_flags & TRA_system) &&
transaction->tra_save_point &&
transaction->tra_save_point->hasChanges()) {
const Savepoint* const savepoint = transaction->startSavepoint();
impure->savepoint = savepoint->getNumber();
}
cursor->open(tdbb);
if (cursor->isUpdateCounters())
request->req_records_affected.clear();
// fall into
case Request::req_return:
if (stall)
return stall;
// fall into
case Request::req_sync:
{
const bool fetched = cursor->fetchNext(tdbb);
if (withLock) {
const Request* top_request = request->req_snapshot.m_owner;
if ((top_request) && (top_request->req_flags & req_update_conflict))
impure->writeLockMode = true;
}
if (fetched) {
if (impure->writeLockMode && withLock) {
// Skip statement execution and fetch (and try to lock) next record.
request->req_operation = Request::req_sync;
return this;
}
request->req_operation = Request::req_evaluate;
return statement;
}
}
if (impure->writeLockMode)
restartRequest(request, transaction);
request->req_operation = Request::req_return;
if (impure->savepoint) {
while (transaction->tra_save_point &&
transaction->tra_save_point->getNumber() >= impure->savepoint)
{
fb_assert(!transaction->tra_save_point->isChanging());
transaction->releaseSavepoint(tdbb);
}
}
// fall into
default:
{
if (request->req_operation == Request::req_unwind) {
if (request->req_flags & (req_leave | req_continue_loop)) {
const auto label = nodeAs<LabelNode>(parentStmt.getObject());
// If CONTINUE matches our label, restart fetching records
if (label && request->req_label == label->labelNumber &&
(request->req_flags & req_continue_loop))
{
request->req_flags &= ~req_continue_loop;
request->req_operation = Request::req_sync;
return this;
}
// Otherwise (BREAK/LEAVE/EXIT or mismatched CONTINUE), we should unwind further.
// Thus cleanup our savepoint.
if (impure->savepoint) {
while (transaction->tra_save_point &&
transaction->tra_save_point->getNumber() >= impure->savepoint)
{
transaction->releaseSavepoint(tdbb);
}
}
}
}
cursor->close(tdbb);
if (marks & MARK_MERGE) {
delete merge->recUpdated;
merge->recUpdated = nullptr;
}
return parentStmt;
}
}
return NULL;
}
Как мы видим, ForNode - это обёртка над cursor, и вызов ForNode::execute() в том случае, когда req_operation равен Request::req_sync , приведёт к вызову cursor->fetchNext(), после чего req_operation переведётся в Request::req_evaluate, и будет возвращено значение поля statement, которое в нашем случае указывает на экземпляр класса SuspendNode.
RecordSource@2
Метод Cursor::fetchNext() выглядит так:
bool Cursor::fetchNext(thread_db* tdbb) const {
if (m_rse->isScrollable())
return fetchRelative(tdbb, 1);
if (!validate(tdbb))
return false;
const auto request = tdbb->getRequest();
Impure* const impure = request->getImpure<Impure>(m_impure);
if (!impure->irsb_active) {
// error: invalid cursor state
status_exception::raise(Arg::Gds(isc_cursor_not_open));
}
if (impure->irsb_state == EOS)
return false;
if (!m_root->getRecord(tdbb)) {
impure->irsb_state = EOS;
return false;
}
if (m_updateCounters) {
request->req_records_selected++;
request->req_records_affected.bumpFetched();
}
impure->irsb_state = POSITIONED;
return true;
}
Здесь очень мешается некий impure, который выглядит как ещё один контекст. Его поле irsb_state помечается как EOS (end of stream), как только не получилось извлечь следующую строку, и это выглядит как оптимизация. Собственно извлечение строки происходит в m_root->getRecord(), а m_root - это экземпляр класса RecordSource. В нашем случае, это будет FullTableScan. Вот код метода:
bool FullTableScan::internalGetRecord(thread_db* tdbb) const
{
JRD_reschedule(tdbb);
Request* const request = tdbb->getRequest();
record_param* const rpb = &request->req_rpb[m_stream];
Impure* const impure = request->getImpure<Impure>(m_impure);
if (!(impure->irsb_flags & irsb_open)) {
rpb->rpb_number.setValid(false);
return false;
}
const RecordNumber* upper = impure->irsb_upper.isValid() ? &impure->irsb_upper : nullptr;
if (VIO_next_record(tdbb, rpb, request->req_transaction, request->req_pool, DPM_next_all, upper))
{
rpb->rpb_number.setValid(true);
return true;
}
rpb->rpb_number.setValid(false);
return false;
}
Здесь мы видим, что всю работу выполняет VIO_next_record(). Углубляться в него мы сейчас не будем, но запишем на будущее вопросы, на которые мы хотим получить ответы:
Кто создал Cursor и присвоил m_root значение FullTableScan ?
Если вспомнить, что мы видели в первой части в функциях подсистемы VIO, то там было критически важно, что в rpb хранится Relation, в котом закеширован Format, который прицепляется к строке при её чтении. Значит, для быстрой работы rpb должен быть одним и тем же для всех читаемых строк. Здесь мы видим, что действительно, один и тот же rpb берётся из некоего массива request->req_rpb по индексу m_stream , который присваивается в конструкторе. Я предположу, что если request - это один SQL-запрос с группировкой (select securityId, avg(price) from orders group by securityId), то при выполнении такого запроса будут использованы несколько RecordSource: один читает исходные данные, второй группирует. И тогда m_stream используется, чтобы выбирать RecordSource. Это нужно проверить.
В продолжение предыдущего пункта: по исходникам можно убедиться, что m_stream используется только для обращения к req_rpb. Я, конечно, понимаю, что это константный доступ и всё такое, но почему бы не отказаться от m_stream и не хранить сразу rpb, на пару операций меньше для каждой строки ? Это нужно проверить. Первая идея: request недоступен на том шаге, когда вызывается конструктор. Но я могу ответить: в FullTableScan::internalOpen() , который точно вызывается раньше, чем internalGetRecord(), уже есть обращение к rpb, я бы его сохранил в this.
Номер строки, который нужно прочитать, задаётся через impure->irsb_upper . Что это и откуда берётся ?
Dsql@3
Следующим узлом в нашей цепочке будет SuspendNode, вызванный в режиме req_evaluate
const StmtNode* SuspendNode::execute(thread_db* tdbb, Request* request, ExeState* /*exeState*/) const
{
switch (request->req_operation) {
case Request::req_evaluate:
{
// ASF: If this is the send in the tail of a procedure and the procedure was called
// with a SELECT, don't run all the send statements. It may make validations fail when
// the procedure didn't return any rows. See CORE-2204.
// But we should run the last assignment, as it's the one who make the procedure stop.
if (!(request->req_flags & req_proc_fetch))
return statement;
const CompoundStmtNode* list = nodeAs<CompoundStmtNode>(parentStmt);
if (list && !list->parentStmt && list->statements[list->statements.getCount() - 1] == this)
{
list = nodeAs<CompoundStmtNode>(statement);
if (list && list->onlyAssignments && list->statements.hasData())
{
// This is the assignment that sets the EOS parameter.
const AssignmentNode* assign = static_cast<const AssignmentNode*>(
list->statements[list->statements.getCount() - 1].getObject());
EXE_assignment(tdbb, assign);
}
else
return statement;
}
else
return statement;
// fall into
}
case Request::req_return:
request->req_operation = Request::req_send;
request->req_message = message;
request->req_flags |= req_stall;
return this;
case Request::req_proceed:
request->req_operation = Request::req_return;
return parentStmt;
default:
return parentStmt;
}
}
Тут много кода, но в нашем случае всё просто: поскольку у request->req_flags не выставлен флаг req_proc_fetch (потому что выполняется запрос, а не хранимая процедура), то сразу выполнится первый return statement. И управление передаётся в CompondStmtNode.
const StmtNode* CompoundStmtNode::execute(thread_db* tdbb, Request* request, ExeState* /*exeState*/) const {
const NestConst<StmtNode>* end = statements.end();
if (onlyAssignments && !request->req_attachment->isProfilerActive()) {
if (request->req_operation == Request::req_evaluate) {
for (const NestConst<StmtNode>* i = statements.begin(); i != end; ++i) {
const StmtNode* stmt = i->getObject();
EXE_assignment(tdbb, static_cast<const AssignmentNode*>(stmt));
}
request->req_operation = Request::req_return;
}
return parentStmt;
}
impure_state* impure = request->getImpure<impure_state>(impureOffset);
switch (request->req_operation)
{
case Request::req_evaluate:
impure->sta_state = 0;
// fall into
case Request::req_return:
case Request::req_sync:
if (impure->sta_state < statements.getCount())
{
request->req_operation = Request::req_evaluate;
return statements[impure->sta_state++];
}
request->req_operation = Request::req_return;
// fall into
default:
return parentStmt;
}
}
Для нас интересным тут является первая часть функции, которая проверяет атрибут onlyAssignments, потому что в нашем случае он выставлен в true. Раз так, в цикле вызывается EXE_assignment() для всех вложенных AssignmentNode.
void EXE_assignment(thread_db* tdbb, const AssignmentNode* node)
{
DEV_BLKCHK(node, type_nod);
SET_TDBB(tdbb);
Request* request = tdbb->getRequest();
// Get descriptors of src field/parameter/variable, etc.
request->req_flags &= ~req_null;
dsc* from_desc = EVL_expr(tdbb, request, node->asgnFrom);
EXE_assignment(tdbb, node->asgnTo, from_desc, (request->req_flags & req_null),
node->missing, node->missing2);
}
// Evaluate a value expression.
inline dsc* EVL_expr(thread_db* tdbb, Request* request, const ValueExprNode* node)
{
if (!node)
BUGCHECK(303); // msg 303 Invalid expression for evaluation
SET_TDBB(tdbb);
JRD_reschedule(tdbb);
request->req_flags &= ~req_null;
dsc* desc = node->execute(tdbb, request);
if (desc)
request->req_flags &= ~req_null;
else
request->req_flags |= req_null;
return desc;
}
dsc* LiteralNode::execute(thread_db* /*tdbb*/, Request* /*request*/) const
{
return const_cast<dsc*>(&litDesc);
}
Каждый AssignmentNode содержит два вложенных узла: источник asgnFrom и получатель asgnTo.
Присваивание выполняется ожидаемо: сначала вычисляем выражение asgnFrom, результат которого будем присваивать. В нашем случае выражением будет LiteralNode, который возвращает dsc. Мы уже встречались с этой структурой во второй части статьи, она описывает поле в строке, включая тип, смещение в строке и длину. Для нас пока останется загадкой, как именно эта структура была создана, видимо, это происходит на этапе подготовки запроса, а не чтения строк, и для заполнения этой структуры очевидно анализируются метаданные таблицы, например Format. Именно поэтому очень удобно, что структура dsc совпадает по полям со структурой Ods::Descriptor, которая нам встречалась в первой части, то есть одну в другую можно превратить очень быстро и без копирований.
Это описание передаётся в другой вариант EXE_assignment(). Первым существенным действием в нём является вызов EVL_assign_to(), чтобы получить dsc для узла-получателя.
// Perform an assignment.
void EXE_assignment(thread_db* tdbb, const ValueExprNode* to, dsc* from_desc, bool from_null,
const ValueExprNode* missing_node, const ValueExprNode* missing2_node)
{
SET_TDBB(tdbb);
Request* request = tdbb->getRequest();
const auto toVar = nodeAs<VariableNode>(to);
if (toVar && toVar->outerDecl)
request = toVar->getVarRequest(request);
AutoSetRestore2<Request*, thread_db> autoSetRequest(
tdbb, &thread_db::getRequest, &thread_db::setRequest, request);
// Get descriptors of receiving and sending fields/parameters, variables, etc.
dsc* missing = NULL;
if (missing_node)
missing = EVL_expr(tdbb, request, missing_node);
// Get descriptor of target field/parameter/variable, etc.
DSC* to_desc = EVL_assign_to(tdbb, to);
request->req_flags &= ~req_null;
// NS: If we are assigning to NULL, we finished.
// This functionality is currently used to allow calling UDF routines
// without assigning resulting value anywhere.
if (!to_desc)
return;
SSHORT null = from_null ? -1 : 0;
if (!null && missing && MOV_compare(tdbb, missing, from_desc) == 0)
null = -1;
USHORT* impure_flags = NULL;
const auto toParam = nodeAs<ParameterNode>(to);
if (toParam) {
const MessageNode* message = toParam->message;
const auto paramRequest = toParam->getParamRequest(request);
if (toParam->argInfo) {
AutoSetRestore2<Request*, thread_db> autoSetRequest(
tdbb, &thread_db::getRequest, &thread_db::setRequest, paramRequest);
EVL_validate(tdbb, Item(Item::TYPE_PARAMETER, message->messageNumber, toParam->argNumber),
toParam->argInfo, from_desc, null == -1);
}
impure_flags = paramRequest->getImpure<USHORT>(
message->impureFlags + (sizeof(USHORT) * toParam->argNumber));
} else if (toVar) {
const auto varRequest = toVar->getVarRequest(request);
if (toVar->varInfo) {
AutoSetRestore2<Request*, thread_db> autoSetRequest(
tdbb, &thread_db::getRequest, &thread_db::setRequest, varRequest);
EVL_validate(tdbb, Item(Item::TYPE_VARIABLE, toVar->varId),
toVar->varInfo, from_desc, null == -1);
}
impure_flags = &varRequest->getImpure<impure_value>(
toVar->varDecl->impureOffset)->vlu_flags;
}
if (impure_flags != NULL)
*impure_flags |= VLU_checked;
// If the value is non-missing, move/convert it. Otherwise fill the
// field with appropriate nulls.
dsc temp;
if (!null) {
if (!DSC_EQUIV(from_desc, to_desc, false)) {
MOV_move(tdbb, from_desc, to_desc);
} else if (from_desc->dsc_dtype == dtype_short) {
*((SSHORT*) to_desc->dsc_address) = *((SSHORT*) from_desc->dsc_address);
} else if (from_desc->dsc_dtype == dtype_long) {
*((SLONG*) to_desc->dsc_address) = *((SLONG*) from_desc->dsc_address);
} else if (from_desc->dsc_dtype == dtype_int64) {
*((SINT64*) to_desc->dsc_address) = *((SINT64*) from_desc->dsc_address);
} else {
memcpy(to_desc->dsc_address, from_desc->dsc_address, from_desc->dsc_length);
}
to_desc->dsc_flags &= ~DSC_null;
} else {
if (missing2_node && (missing = EVL_expr(tdbb, request, missing2_node)))
MOV_move(tdbb, missing, to_desc);
else
memset(to_desc->dsc_address, 0, to_desc->dsc_length);
to_desc->dsc_flags |= DSC_null;
}
// Handle the null flag as appropriate for fields and message arguments.
const FieldNode* toField = nodeAs<FieldNode>(to);
if (toField) {
Record* record = request->req_rpb[toField->fieldStream].rpb_record;
if (null)
record->setNull(toField->fieldId);
else
record->clearNull(toField->fieldId);
} else if (toParam && toParam->argFlag) {
to_desc = EVL_assign_to(tdbb, toParam->argFlag);
// If the null flag is a string with an effective length of one,
// then -1 will not fit. Therefore, store 1 instead.
if (null && to_desc->dsc_dtype <= dtype_varying) {
USHORT minlen;
switch (to_desc->dsc_dtype)
{
case dtype_text:
minlen = 1;
break;
case dtype_cstring:
minlen = 2;
break;
case dtype_varying:
minlen = 3;
break;
}
if (to_desc->dsc_length <= minlen)
null = 1;
}
temp.dsc_dtype = dtype_short;
temp.dsc_length = sizeof(SSHORT);
temp.dsc_scale = 0;
temp.dsc_sub_type = 0;
temp.dsc_address = (UCHAR*) &null;
MOV_move(tdbb, &temp, to_desc);
}
}
dsc* EVL_assign_to(thread_db* tdbb, const ValueExprNode* node) {
SET_TDBB(tdbb);
DEV_BLKCHK(node, type_nod);
Request* request = tdbb->getRequest();
// The only nodes that can be assigned to are: argument, field and variable.
if (auto paramNode = nodeAs<ParameterNode>(node)) {
auto message = paramNode->message;
auto arg_number = paramNode->argNumber;
auto desc = &message->format->fmt_desc[arg_number];
auto impure = request->getImpure<impure_value>(node->impureOffset);
impure->vlu_desc.dsc_address = paramNode->getParamRequest(request)->getImpure<UCHAR>(message->impureOffset + (IPTR) desc->dsc_address);
impure->vlu_desc.dsc_dtype = desc->dsc_dtype;
impure->vlu_desc.dsc_length = desc->dsc_length;
impure->vlu_desc.dsc_scale = desc->dsc_scale;
impure->vlu_desc.dsc_sub_type = desc->dsc_sub_type;
if (DTYPE_IS_TEXT(desc->dsc_dtype) &&
((INTL_TTYPE(desc) == ttype_dynamic) || (INTL_GET_CHARSET(desc) == CS_dynamic)))
{
// Value is a text value, we're assigning it back to the user
// process, user process has not specified a subtype, user
// process specified dynamic translation and the dsc isn't from
// a 3.3 type request (blr_cstring2 instead of blr_cstring) so
// convert the charset to the declared charset of the process.
impure->vlu_desc.setTextType(tdbb->getCharSet());
}
return &impure->vlu_desc;
} else if (nodeIs<NullNode>(node))
return NULL;
else if (auto varNode = nodeAs<VariableNode>(node)) {
auto impure = varNode->getVarRequest(request)->getImpure<impure_value>(varNode->varDecl->impureOffset);
return &impure->vlu_desc;
} else if (auto fieldNode = nodeAs<FieldNode>(node)) {
auto record = request->req_rpb[fieldNode->fieldStream].rpb_record;
auto impure = request->getImpure<impure_value>(node->impureOffset);
if (!EVL_field(0, record, fieldNode->fieldId, &impure->vlu_desc))
{
// The below condition means that EVL_field() returned
// a read-only dummy value which cannot be assigned to.
// The usual reason is a field being unexpectedly dropped.
if (impure->vlu_desc.dsc_address && !(impure->vlu_desc.dsc_flags & DSC_null))
ERR_post(Arg::Gds(isc_field_disappeared));
}
if (!impure->vlu_desc.dsc_address)
ERR_post(Arg::Gds(isc_read_only_field) << "<unknown>");
return &impure->vlu_desc;
}
SOFT_BUGCHECK(229); // msg 229 EVL_assign_to: invalid operation
return NULL;
}
В EVL_assign_to() выполнение зайдёт в if-ветку, которая обрабатывает paramNode. И что же мы видим внутри: получаем paramNode->message, у которого берём format, из которого, зная порядковый номер параметра, получаем dsc, описывающий то, куда значение параметра нужно записать . Но возвращаем из функции мы не его, а его копию, которая формируется в impure->vlu_desc. Зачем нужно копирование, непонятно, но самое интересное для нас тут то, что dsc_address копируется как смещение от message->impureOffset, то есть копия impure->vlu_desc указывает на то же самое расположение содержимого.
В середине EXE_assignment() отлично видно if по типам данных, который выполняет собственно присваивание через копирование.
После завершения всех присваиваний CompondStmtNode::execute() переведёт режим req_operation в значение req_return, и вернёт parentStmt, которым является тот же самый SuspendNode, который перевёл выполнение на CompoundStmtNode. Но, поскольку req_operation уже другой, то и действия в SuspendNode::execute() будут другими: режим req_operation будет установлен в req_send, выставится флаг stall, и сохраняет в request->req_message значение своего поля message, которое является экземпляром класса MessageNode. Некоторая загадка состоит в том, что этот message нигде не фигурировал. Но я смог в отладчике проверить, что в функции EVL_assign_to() значения paramNode->message будут указывать на тот же самый экземпляр MessageNode, ох уж эти сто указателей на одно и то же, потом проверю, как это так вышло. В итоге SuspendNode::execute() в режиме req_return возвращает себя. Флаг stall заставляет цикл обработки узлов прерваться.
Все эти перескоки из одного StmtNode к другому выглядят очень запутанно, поэтому я нарисовал схему. Те шаги, которые мы уже рассмотрели, отмечены синими стрелками с номерами от 1 до 6. На стрелках указаны значения req_operation.

Как можно подсмотреть в части 2, цепочка вызовов раскрутится до EXE_receive(), который обработает request->req_message , значит я ответил на вопрос, который я же задал в заключении второй части о том, откуда берётся request->req_message. Кроме этого мы увидим, что EXE_receive() вызовет execute_looper() второй раз, уже с параметром Request::req_proceed. Пришло время понять, зачем это нужно. Шаги, которые мы рассмотрим, показаны на картинке зелёными стрелками с номерами от 7 до 10
Выполнение продолжится с SuspendNode, который выставит req_operation в req_return, и в качестве следующего выполняющегося узла вернёт свой parentStmt, которым является тот самый ForNode, который ещё на первом проходе передавал управление на SuspendNode. А ForNode тупо передаёт управление на своё поле stall, которым выступает тот самый StallNode, с которого всё начиналось.
Пока я разбирался с этими узлами выполнения, я наткнулся на вот какую штуку. Оказывается, инструмент командной строки isql имеет настройку "SET EXEC_PATH_DISPLAY BLR;" , при включении которой он начинает показывать BLR, некий внутренний формат Firebird. Если включить его и выполнить наш запрос, то выводится очень интересное содержимое:
SQL> select orderid, securityid,qty,price from orders;
Execution path (BLR):
0 blr_version5,
1 blr_begin,
2 blr_message, 1, 9,0,
6 blr_short, 0,
8 blr_short, 0,
10 blr_double,
11 blr_short, 0,
13 blr_long, 0,
15 blr_short, 0,
17 blr_long, 0,
19 blr_short, 0,
21 blr_varying2, 0,0, 0,2,
26 blr_for,
27 blr_stall,
28 blr_rse, 1,
30 blr_rid, 128,0, 0,
34 blr_end,
35 blr_send, 1,
37 blr_begin,
38 blr_assignment,
39 blr_literal, blr_short, 0, 1,0,
44 blr_parameter, 1, 0,0,
48 blr_assignment,
49 blr_fid, 0, 3,0,
53 blr_parameter2, 1, 2,0, 1,0,
59 blr_assignment,
60 blr_fid, 0, 2,0,
64 blr_parameter2, 1, 4,0, 3,0,
70 blr_assignment,
71 blr_fid, 0, 4,0,
75 blr_parameter2, 1, 6,0, 5,0,
81 blr_assignment,
82 blr_fid, 0, 1,0,
86 blr_parameter2, 1, 8,0, 7,0,
92 blr_end,
93 blr_send, 1,
95 blr_assignment,
96 blr_literal, blr_short, 0, 0,0,
101 blr_parameter, 1, 0,0,
105 blr_end,
106 blr_eoc
И тут мы отлично видим, что в начале идёт MessageNode, соответствующий запрошенным столбцам. Потом идёт ForNode, внутри которого есть StallNode и ещё какой-то RSE (наверное это SuspendNode). Дальше идёт Send, который, похоже, соответствует нашему CompoundStmtNode, потому что внутри идут AssignmentNode. Вот такие пироги. Если почитать файл StmtNodes.cpp , то можно увидеть, что многие потомки StmtNode реализуют метод genBlr(), вот например вариант ForNode::genBlr() .
Заключение
Мы прошлись по интерпретатору операций, которые выполняются при чтении данных из строки базы данных при выполнении пользовательского запроса. Интерпретатор реализован так, что каждая инструкция реализована как C++ класс, наследующийся от StmtNode, с логикой в методе execute().
Чтение полей из строки выполняется в цикле по количеству читаемых полей, и чтение представляет собой копирование из буфера строки таблицы в буфер результата, который в процессе анализа запроса выделен в узле MessageNode. В следующей части мы попробуем понять, как формируются все эти StmtNode, и самый загадочный вопрос: как получается, что ParameterNode, который описывает, куда сохранять значение из поля таблицы, ссылается на тот же самый message, что и SuspendNode.
Итак, сколько раз перекладываются данные при чтении строки? Давайте посчитаем:
В VIO cтрока распаковывается из страницы данных из rle-закодированного вида, в буфер, содержимое которого описано Format-ом.
При выполнении EXE_assignment() для каждого запрошенного столбца значение будет скопировано в буфер, выделенный для строки результата где то в request->impure.
EXE_receive() скопирует буфер строки-результата в свой буфер.
Метод DsqlDmlRequest::mapInOut() скопирует параметры в цикле в выходной буфер.
Самостоятельная работа
Мы рассмотрели вариант, когда пользовательский запрос хотел получить строку из таблицы, и строка нашлась. А что произойдёт, если больше строк в таблице нет? Подсказка: нужно искать в ForNode::execute().