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

Комментарии (0)