Как работает база данных Firebird, часть 3

от автора

В предыдущей части мы изучали, как выполняется выборка строки из таблицы базы данных при выполнении запроса. Мы доизучались до того, что подсистема 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(). Углубляться в него мы сейчас не будем, но запишем на будущее вопросы, на которые мы хотим получить ответы:

  1. Кто создал Cursor и присвоил m_root значение FullTableScan ?

  2. Если вспомнить, что мы видели в первой части в функциях подсистемы 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. Это нужно проверить.

  3. В продолжение предыдущего пункта: по исходникам можно убедиться, что m_stream используется только для обращения к req_rpb. Я, конечно, понимаю, что это константный доступ и всё такое, но почему бы не отказаться от m_stream и не хранить сразу rpb, на пару операций меньше для каждой строки ? Это нужно проверить. Первая идея: request недоступен на том шаге, когда вызывается конструктор. Но я могу ответить: в FullTableScan::internalOpen() , который точно вызывается раньше, чем internalGetRecord(), уже есть обращение к rpb, я бы его сохранил в this.

  4. Номер строки, который нужно прочитать, задаётся через 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.

Цепочка StmtNode

Цепочка StmtNode

Как можно подсмотреть в части 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.

Итак, сколько раз перекладываются данные при чтении строки? Давайте посчитаем:

  1. В VIO cтрока распаковывается из страницы данных из rle-закодированного вида, в буфер, содержимое которого описано Format-ом.

  2. При выполнении EXE_assignment() для каждого запрошенного столбца значение будет скопировано в буфер, выделенный для строки результата где то в request->impure.

  3. EXE_receive() скопирует буфер строки-результата в свой буфер.

  4. Метод DsqlDmlRequest::mapInOut() скопирует параметры в цикле в выходной буфер.

Самостоятельная работа

  1. Мы рассмотрели вариант, когда пользовательский запрос хотел получить строку из таблицы, и строка нашлась. А что произойдёт, если больше строк в таблице нет? Подсказка: нужно искать в ForNode::execute().


ссылка на оригинал статьи https://habr.com/ru/articles/931218/