diff --git a/src/dsql/DsqlBatch.cpp b/src/dsql/DsqlBatch.cpp index 7442f15305d..916d0dfe791 100644 --- a/src/dsql/DsqlBatch.cpp +++ b/src/dsql/DsqlBatch.cpp @@ -208,12 +208,6 @@ DsqlBatch* DsqlBatch::open(thread_db* tdbb, DsqlDmlRequest* req, IMessageMetadat const auto statement = req->getDsqlStatement(); - if (statement->getFlags() & DsqlStatement::FLAG_ORPHAN) - { - ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) << - Arg::Gds(isc_bad_req_handle)); - } - switch (statement->getType()) { case DsqlStatement::TYPE_INSERT: @@ -229,7 +223,7 @@ DsqlBatch* DsqlBatch::open(thread_db* tdbb, DsqlDmlRequest* req, IMessageMetadat } const dsql_msg* message = statement->getSendMsg(); - if (! (inMetadata && message && req->parseMetadata(inMetadata, message->msg_parameters))) + if (! (inMetadata && message && message->msg_parameter > 0)) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) << Arg::Gds(isc_batch_param)); @@ -659,18 +653,23 @@ Firebird::IBatchCompletionState* DsqlBatch::execute(thread_db* tdbb) // execute request m_dsqlRequest->req_transaction = transaction; Request* req = m_dsqlRequest->getRequest(); + DsqlStatement* dStmt = m_dsqlRequest->getDsqlStatement(); fb_assert(req); // prepare completion interface AutoPtr completionState (FB_NEW BatchCompletionState(m_flags & (1 << IBatch::TAG_RECORD_COUNTS), m_detailed)); AutoSetRestore batchFlag(&req->req_batch_mode, true); - const dsql_msg* message = m_dsqlRequest->getDsqlStatement()->getSendMsg(); + const dsql_msg* sendMessage = dStmt->getSendMsg(); + // map message to internal engine format + // Do it one time only to avoid parsing its metadata for every message + m_dsqlRequest->metadataToFormat(m_meta, sendMessage); + // Using of positional DML in batch is strange but not forbidden + m_dsqlRequest->mapCursorKey(tdbb); bool startRequest = true; - bool isExecBlock = m_dsqlRequest->getDsqlStatement()->getType() == DsqlStatement::TYPE_EXEC_BLOCK; - const auto receiveMessage = isExecBlock ? m_dsqlRequest->getDsqlStatement()->getReceiveMsg() : nullptr; - auto receiveMsgBuffer = isExecBlock ? m_dsqlRequest->req_msg_buffers[receiveMessage->msg_buffer_number] : nullptr; + bool isExecBlock = dStmt->getType() == DsqlStatement::TYPE_EXEC_BLOCK; + const dsql_msg* receiveMessage = isExecBlock ? dStmt->getReceiveMsg() : nullptr; // process messages ULONG remains; @@ -726,25 +725,18 @@ Firebird::IBatchCompletionState* DsqlBatch::execute(thread_db* tdbb) *id = newId; } - // map message to internal engine format - // pass m_meta one time only to avoid parsing its metadata for every message - m_dsqlRequest->mapInOut(tdbb, false, message, start ? m_meta : nullptr, nullptr, data); - data += m_messageSize; - remains -= m_messageSize; - - UCHAR* msgBuffer = m_dsqlRequest->req_msg_buffers[message->msg_buffer_number]; try { // runsend data to request and collect stats ULONG before = req->req_records_inserted + req->req_records_updated + req->req_records_deleted; - EXE_send(tdbb, req, message->msg_number, message->msg_length, msgBuffer); + EXE_send(tdbb, req, sendMessage->msg_number, m_messageSize, data); ULONG after = req->req_records_inserted + req->req_records_updated + req->req_records_deleted; completionState->regUpdate(after - before); - if (isExecBlock) - EXE_receive(tdbb, req, receiveMessage->msg_number, receiveMessage->msg_length, receiveMsgBuffer); + if (receiveMessage) + EXE_receive(tdbb, req, receiveMessage->msg_number, receiveMessage->msg_length, nullptr); // We don't care about returned record } catch (const Exception& ex) { @@ -764,6 +756,9 @@ Firebird::IBatchCompletionState* DsqlBatch::execute(thread_db* tdbb) startRequest = true; } + + data += m_messageSize; + remains -= m_messageSize; } UCHAR* alignedData = FB_ALIGN(data, m_alignment); diff --git a/src/dsql/DsqlCompilerScratch.cpp b/src/dsql/DsqlCompilerScratch.cpp index 586dfdb9eec..15cc9f9b051 100644 --- a/src/dsql/DsqlCompilerScratch.cpp +++ b/src/dsql/DsqlCompilerScratch.cpp @@ -421,10 +421,7 @@ dsql_var* DsqlCompilerScratch::resolveVariable(const MetaName& varName) // Generate BLR for a return. void DsqlCompilerScratch::genReturn(bool eosFlag) { - const bool hasEos = !(flags & (FLAG_TRIGGER | FLAG_FUNCTION)); - - if (hasEos && !eosFlag) - appendUChar(blr_begin); + const bool hasEos = !(flags & (FLAG_TRIGGER | FLAG_FUNCTION | FLAG_EXEC_BLOCK)); appendUChar(blr_send); appendUChar(1); @@ -455,12 +452,6 @@ void DsqlCompilerScratch::genReturn(bool eosFlag) } appendUChar(blr_end); - - if (hasEos && !eosFlag) - { - appendUChar(blr_stall); - appendUChar(blr_end); - } } void DsqlCompilerScratch::genParameters(Array >& parameters, diff --git a/src/dsql/DsqlCompilerScratch.h b/src/dsql/DsqlCompilerScratch.h index 8755b7cb2d0..a74743c47f1 100644 --- a/src/dsql/DsqlCompilerScratch.h +++ b/src/dsql/DsqlCompilerScratch.h @@ -51,7 +51,10 @@ typedef Firebird::Pair< Firebird::NonPooled, NestConst>> ReturningClause; -// DSQL Compiler scratch block - may be discarded after compilation in the future. +// DSQL Compiler scratch block. +// Contains any kind of objects used during DsqlStatement compilation +// Is deleted with its pool as soon as DsqlStatement is fully formed in prepareStatement() +// or with the statement itself (if the statement reqested it returning true from shouldPreserveScratch()) class DsqlCompilerScratch : public BlrDebugWriter { public: @@ -70,6 +73,7 @@ class DsqlCompilerScratch : public BlrDebugWriter static const unsigned FLAG_DDL = 0x2000; static const unsigned FLAG_FETCH = 0x4000; static const unsigned FLAG_VIEW_WITH_CHECK = 0x8000; + static const unsigned FLAG_EXEC_BLOCK = 0x100000; static const unsigned MAX_NESTING = 512; @@ -105,7 +109,7 @@ class DsqlCompilerScratch : public BlrDebugWriter protected: // DsqlCompilerScratch should never be destroyed using delete. - // It dies together with it's pool in release_request(). + // It dies together with it's pool. ~DsqlCompilerScratch() { } @@ -317,6 +321,7 @@ class DsqlCompilerScratch : public BlrDebugWriter DsqlCompilerScratch* mainScratch = nullptr; Firebird::NonPooledMap outerMessagesMap; // Firebird::NonPooledMap outerVarsMap; // + dsql_msg* recordKeyMessage = nullptr; // Side message for positioned DML private: Firebird::HalfStaticArray ctes; // common table expressions diff --git a/src/dsql/DsqlCursor.cpp b/src/dsql/DsqlCursor.cpp index 676a9d72f72..c4c6d2c7508 100644 --- a/src/dsql/DsqlCursor.cpp +++ b/src/dsql/DsqlCursor.cpp @@ -28,6 +28,7 @@ #include "../dsql/dsql_proto.h" #include "../dsql/DsqlCursor.h" +#include "../dsql/StmtNodes.h" using namespace Firebird; using namespace Jrd; @@ -36,7 +37,11 @@ static const char* const SCRATCH = "fb_cursor_"; static const ULONG PREFETCH_SIZE = 65536; // 64 KB DsqlCursor::DsqlCursor(DsqlDmlRequest* req, ULONG flags) - : m_dsqlRequest(req), m_message(req->getDsqlStatement()->getReceiveMsg()), + : m_dsqlRequest(req), + m_keyBuffer(nullptr), + m_keyBufferLength(0), + m_message(req->getDsqlStatement()->getReceiveMsg()->msg_number), + m_messageLength(0), m_resultSet(NULL), m_flags(flags), m_space(req->getPool(), SCRATCH), m_state(BOS), m_eof(false), m_position(0), m_cachedCount(0) @@ -48,6 +53,11 @@ DsqlCursor::~DsqlCursor() { if (m_resultSet) m_resultSet->resetHandle(); + if (m_keyBuffer) + { + delete[] m_keyBuffer; + m_keyBuffer = nullptr; + } } jrd_tra* DsqlCursor::getTransaction() const @@ -66,6 +76,23 @@ void DsqlCursor::setInterfacePtr(JResultSet* interfacePtr) noexcept m_resultSet = interfacePtr; } +bool DsqlCursor::getCurrentRecordKey(USHORT context, RecordKey& key) const +{ + if (m_keyBuffer == nullptr || context * sizeof(RecordKey) >= m_keyBufferLength) + { + fb_assert(false); + return false; + } + + if (m_state != POSITIONED) + { + return false; + } + + key = m_keyBuffer[context]; + return key.recordNumber.bid_relation_id != 0; +} + void DsqlCursor::close(thread_db* tdbb, DsqlCursor* cursor) { if (!cursor) @@ -88,7 +115,7 @@ void DsqlCursor::close(thread_db* tdbb, DsqlCursor* cursor) if (dsqlRequest->req_traced && TraceManager::need_dsql_free(attachment)) { - TraceSQLStatementImpl stmt(dsqlRequest, NULL); + TraceSQLStatementImpl stmt(dsqlRequest, nullptr, nullptr); TraceManager::event_dsql_free(attachment, &stmt, DSQL_close); } @@ -115,6 +142,15 @@ int DsqlCursor::fetchNext(thread_db* tdbb, UCHAR* buffer) return 1; } + if (m_keyBufferLength == 0) + { + Request* req = m_dsqlRequest->getRequest(); + m_keyBufferLength = req->req_rpb.getCount() * sizeof(RecordKey); + fb_assert(m_keyBufferLength > 0); + m_keyBuffer = new RecordKey[req->req_rpb.getCount()]; + } + + m_dsqlRequest->gatherRecordKey(m_keyBuffer); m_state = POSITIONED; return 0; } @@ -163,7 +199,7 @@ int DsqlCursor::fetchAbsolute(thread_db* tdbb, UCHAR* buffer, SLONG position) { if (!m_eof) { - cacheInput(tdbb); + cacheInput(tdbb, buffer); fb_assert(m_eof); } @@ -248,7 +284,7 @@ void DsqlCursor::getInfo(thread_db* tdbb, case IResultSet::INF_RECORD_COUNT: if (isScrollable && !m_eof) { - cacheInput(tdbb); + cacheInput(tdbb, nullptr); fb_assert(m_eof); } response.insertInt(tag, isScrollable ? m_cachedCount : -1); @@ -291,7 +327,7 @@ int DsqlCursor::fetchFromCache(thread_db* tdbb, UCHAR* buffer, FB_UINT64 positio { if (position >= m_cachedCount) { - if (m_eof || !cacheInput(tdbb, position)) + if (m_eof || !cacheInput(tdbb, buffer, position)) { m_state = EOS; return 1; @@ -299,40 +335,63 @@ int DsqlCursor::fetchFromCache(thread_db* tdbb, UCHAR* buffer, FB_UINT64 positio } fb_assert(position < m_cachedCount); + fb_assert(m_messageLength > 0); // At this point m_messageLength must be set by cacheInput - UCHAR* const msgBuffer = m_dsqlRequest->req_msg_buffers[m_message->msg_buffer_number]; - - const FB_UINT64 offset = position * m_message->msg_length; - const FB_UINT64 readBytes = m_space.read(offset, msgBuffer, m_message->msg_length); - fb_assert(readBytes == m_message->msg_length); - - m_dsqlRequest->mapInOut(tdbb, true, m_message, NULL, buffer); + FB_UINT64 offset = position * (m_messageLength + m_keyBufferLength); + FB_UINT64 readBytes = m_space.read(offset, buffer, m_messageLength); + offset += m_messageLength; + readBytes += m_space.read(offset, m_keyBuffer, m_keyBufferLength); + fb_assert(readBytes == m_messageLength + m_keyBufferLength); m_position = position; m_state = POSITIONED; return 0; } -bool DsqlCursor::cacheInput(thread_db* tdbb, FB_UINT64 position) +bool DsqlCursor::cacheInput(thread_db* tdbb, UCHAR* buffer, FB_UINT64 position) { fb_assert(!m_eof); - const ULONG prefetchCount = MAX(PREFETCH_SIZE / m_message->msg_length, 1); - const UCHAR* const msgBuffer = m_dsqlRequest->req_msg_buffers[m_message->msg_buffer_number]; + // It could not be done before: user buffer length may be unknown until call setDelayedOutputMetadata() + if (m_messageLength == 0) + { + Request* req = m_dsqlRequest->getRequest(); + const MessageNode* msg = req->getStatement()->getMessage(m_message); + m_messageLength = msg->getFormat(req)->fmt_length; + // Save record key unconditionally because setCursorName() can be called after openCursor() + m_keyBufferLength = req->req_rpb.getCount() * sizeof(RecordKey); + m_keyBuffer = new RecordKey[req->req_rpb.getCount()]; + } + + std::unique_ptr ownBuffer; + if (buffer == nullptr) + { + // We are called from getInfo() and there is no user-provided buffer for data. + // Create a temporary one. + // This code cannot be moved into getInfo() itself because it is most likely called before fetch() + // so m_messageLength is still unknown there. + ownBuffer.reset(buffer = new UCHAR[m_messageLength]); + } + + const ULONG prefetchCount = MAX(PREFETCH_SIZE / (m_messageLength + m_keyBufferLength), 1); while (position >= m_cachedCount) { for (ULONG count = 0; count < prefetchCount; count++) { - if (!m_dsqlRequest->fetch(tdbb, NULL)) + if (!m_dsqlRequest->fetch(tdbb, buffer)) { m_eof = true; break; } - const FB_UINT64 offset = m_cachedCount * m_message->msg_length; - const FB_UINT64 writtenBytes = m_space.write(offset, msgBuffer, m_message->msg_length); - fb_assert(writtenBytes == m_message->msg_length); + m_dsqlRequest->gatherRecordKey(m_keyBuffer); + + FB_UINT64 offset = m_cachedCount * (m_messageLength + m_keyBufferLength); + FB_UINT64 writtenBytes = m_space.write(offset, buffer, m_messageLength); + offset += m_messageLength; + writtenBytes += m_space.write(offset, m_keyBuffer, m_keyBufferLength); + fb_assert(writtenBytes == m_messageLength + m_keyBufferLength); m_cachedCount++; } diff --git a/src/dsql/DsqlCursor.h b/src/dsql/DsqlCursor.h index cc1abe36f9b..58c90c132d9 100644 --- a/src/dsql/DsqlCursor.h +++ b/src/dsql/DsqlCursor.h @@ -29,6 +29,7 @@ namespace Jrd { class DsqlDmlRequest; class JResultSet; +struct RecordKey; class DsqlCursor { @@ -41,6 +42,7 @@ class DsqlCursor jrd_tra* getTransaction() const; Attachment* getAttachment() const; void setInterfacePtr(JResultSet* interfacePtr) noexcept; + bool getCurrentRecordKey(USHORT context, RecordKey& key) const; static void close(thread_db* tdbb, DsqlCursor* cursor); @@ -67,10 +69,13 @@ class DsqlCursor private: int fetchFromCache(thread_db* tdbb, UCHAR* buffer, FB_UINT64 position); - bool cacheInput(thread_db* tdbb, FB_UINT64 position = MAX_UINT64); + bool cacheInput(thread_db* tdbb, UCHAR* buffer, FB_UINT64 position = MAX_UINT64); DsqlDmlRequest* const m_dsqlRequest; - const dsql_msg* const m_message; + RecordKey* m_keyBuffer; + ULONG m_keyBufferLength; + const USHORT m_message; + ULONG m_messageLength; JResultSet* m_resultSet; const ULONG m_flags; TempSpace m_space; diff --git a/src/dsql/DsqlRequests.cpp b/src/dsql/DsqlRequests.cpp index cb68720f7e9..f5fde751eb4 100644 --- a/src/dsql/DsqlRequests.cpp +++ b/src/dsql/DsqlRequests.cpp @@ -25,6 +25,7 @@ #include "../dsql/DsqlBatch.h" #include "../dsql/DsqlStatementCache.h" #include "../dsql/Nodes.h" +#include "../dsql/StmtNodes.h" #include "../jrd/Statement.h" #include "../jrd/req.h" #include "../jrd/tra.h" @@ -55,6 +56,59 @@ DsqlRequest::~DsqlRequest() { } +void DsqlRequest::releaseRequest(thread_db* tdbb) +{ + if (req_timer) + { + req_timer->stop(); + req_timer = nullptr; + } + + // Prevent new children from appear + if (req_cursor_name.hasData()) + req_dbb->dbb_cursors.remove(req_cursor_name); + + // If request is parent, orphan the children and release a portion of their requests + + for (auto childStatement : cursors) + { + // Without parent request prepared DsqlRequest will throw error on execute which is exactly what one would expect + childStatement->onReferencedCursorClose(); + + // hvlad: lines below is commented out as + // - child is already unlinked from its parent request + // - we should not free child's sql text until its owner request is alive + // It seems to me we should destroy owner request here, not a child + // statement - as it always was before + + //Jrd::ContextPoolHolder context(tdbb, &childStatement->getPool()); + //releaseStatement(childStatement); + } + + // If the request had an open cursor, close it + + if (req_cursor) + DsqlCursor::close(tdbb, req_cursor); + + if (req_batch) + { + delete req_batch; + req_batch = nullptr; + } + + Jrd::Attachment* att = req_dbb->dbb_attachment; + const bool need_trace_free = req_traced && TraceManager::need_dsql_free(att); + if (need_trace_free) + { + TraceSQLStatementImpl stmt(this, nullptr, nullptr); + TraceManager::event_dsql_free(att, &stmt, DSQL_drop); + } + + // If a request has been compiled, release it now + if (getRequest()) + EXE_release(tdbb, getRequest()); +} + void DsqlRequest::setCursor(thread_db* /*tdbb*/, const TEXT* /*name*/) { status_exception::raise( @@ -165,180 +219,102 @@ void DsqlRequest::destroy(thread_db* tdbb, DsqlRequest* dsqlRequest) { SET_TDBB(tdbb); - if (dsqlRequest->req_timer) - { - dsqlRequest->req_timer->stop(); - dsqlRequest->req_timer = nullptr; - } - - // If request is parent, orphan the children and release a portion of their requests - - for (auto childStatement : dsqlRequest->cursors) - { - childStatement->addFlags(DsqlStatement::FLAG_ORPHAN); - childStatement->setParentRequest(nullptr); - childStatement->setParentDbKey(nullptr); - childStatement->setParentRecVersion(nullptr); - dsqlRequest->req_dbb->dbb_statement_cache->removeStatement(tdbb, childStatement); - - // hvlad: lines below is commented out as - // - child is already unlinked from its parent request - // - we should not free child's sql text until its owner request is alive - // It seems to me we should destroy owner request here, not a child - // statement - as it always was before - - //Jrd::ContextPoolHolder context(tdbb, &childStatement->getPool()); - //releaseStatement(childStatement); - } - - // If the request had an open cursor, close it - - if (dsqlRequest->req_cursor) - DsqlCursor::close(tdbb, dsqlRequest->req_cursor); + // Increase the statement refCount so its pool is not destroyed before the request is gone. + auto dsqlStatement = dsqlRequest->getDsqlStatement(); - if (dsqlRequest->req_batch) + // Let request to clean itself + try { - delete dsqlRequest->req_batch; - dsqlRequest->req_batch = nullptr; + dsqlRequest->releaseRequest(tdbb); } - - Jrd::Attachment* att = dsqlRequest->req_dbb->dbb_attachment; - const bool need_trace_free = dsqlRequest->req_traced && TraceManager::need_dsql_free(att); - if (need_trace_free) + catch(...) { - TraceSQLStatementImpl stmt(dsqlRequest, NULL); - TraceManager::event_dsql_free(att, &stmt, DSQL_drop); + fb_assert(false); } - if (dsqlRequest->req_cursor_name.hasData()) - dsqlRequest->req_dbb->dbb_cursors.remove(dsqlRequest->req_cursor_name); - - // If a request has been compiled, release it now - if (dsqlRequest->getRequest()) - EXE_release(tdbb, dsqlRequest->getRequest()); - - // Increase the statement refCount so its pool is not destroyed before the request is gone. - auto dsqlStatement = dsqlRequest->getDsqlStatement(); - // Release the entire request delete dsqlRequest; - dsqlStatement = nullptr; } -// Parse the message of a request. -USHORT DsqlRequest::parseMetadata(IMessageMetadata* meta, const Array& parameters_list) +// DsqlDmlRequest + +DsqlDmlRequest::DsqlDmlRequest(thread_db* tdbb, MemoryPool& pool, dsql_dbb* dbb, DsqlDmlStatement* aStatement) + : DsqlRequest(pool, dbb, aStatement) { - HalfStaticArray parameters; + request = aStatement->getStatement()->findRequest(tdbb); + tdbb->getAttachment()->att_requests.add(request); + + // If this is positional DML - subscribe to parent cursor as well - for (FB_SIZE_T i = 0; i < parameters_list.getCount(); ++i) + if (aStatement->parentCursorName.hasData()) { - dsql_par* param = parameters_list[i]; + const auto* const symbol = dbb->dbb_cursors.get(aStatement->parentCursorName.c_str()); - if (param->par_index) + if (!symbol) { - if (param->par_index > parameters.getCount()) - parameters.grow(param->par_index); - fb_assert(!parameters[param->par_index - 1]); - parameters[param->par_index - 1] = param; + // cursor is not found + ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-504) << + Arg::Gds(isc_dsql_cursor_err) << + Arg::Gds(isc_dsql_cursor_not_found) << aStatement->parentCursorName); } - } - - // If there's no metadata, then the format of the current message buffer - // is identical to the format of the previous one. - - if (!meta) - return parameters.getCount(); - - FbLocalStatus st; - unsigned count = meta->getCount(&st); - checkD(&st); - - unsigned count2 = parameters.getCount(); - - if (count != count2) - { - ERRD_post(Arg::Gds(isc_dsql_sqlda_err) << - Arg::Gds(isc_dsql_wrong_param_num) <getType(&st, index); - checkD(&st); - unsigned sqlLength = meta->getLength(&st, index); - checkD(&st); - dsc desc; - desc.dsc_flags = 0; + parentRequest = *symbol; + fb_assert(parentRequest != nullptr); - unsigned dataOffset, nullOffset, dtype, dlength; - offset = fb_utils::sqlTypeToDsc(offset, sqlType, sqlLength, - &dtype, &dlength, &dataOffset, &nullOffset); - desc.dsc_dtype = dtype; - desc.dsc_length = dlength; + // Verify that the cursor is appropriate and updatable - desc.dsc_scale = meta->getScale(&st, index); - checkD(&st); - desc.dsc_sub_type = meta->getSubType(&st, index); - checkD(&st); - unsigned textType = meta->getCharSet(&st, index); - checkD(&st); - desc.setTextType(textType); - desc.dsc_address = (UCHAR*)(IPTR) dataOffset; + if (parentRequest->getDsqlStatement()->getType() != DsqlStatement::TYPE_SELECT_UPD) + { + // cursor is not updatable + ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-510) << + Arg::Gds(isc_dsql_cursor_update_err) << aStatement->parentCursorName); + } - const dsql_par* const parameter = parameters[index]; - fb_assert(parameter); + // Check that it contains this relation name + Request* request = parentRequest->getRequest(); + fb_assert(request->req_rpb.getCount() > 0 && request->req_rpb[0].rpb_relation != nullptr); - // ASF: Older than 2.5 engine hasn't validating strings in DSQL. After this has been - // implemented in 2.5, selecting a NONE column with UTF-8 attachment charset started - // failing. The real problem is that the client encodes SQL_TEXT/SQL_VARYING using - // blr_text/blr_varying (i.e. with the connection charset). I'm reseting the charset - // here at the server as a way to make older (and not yet changed) client work - // correctly. - if (desc.isText() && desc.getTextType() == ttype_dynamic) - desc.setTextType(ttype_none); + const MetaName& relName = request->req_rpb[0].rpb_relation->rel_name; + bool found = false; + for (FB_SIZE_T i = 0; i < request->req_rpb.getCount(); ++i) + { + jrd_rel* relation = request->req_rpb[i].rpb_relation; - req_user_descs.put(parameter, desc); + if (relation && relation->rel_name == relName) + { + if (found) + { + // Relation is used twice in cursor + ERRD_post(Arg::Gds(isc_dsql_cursor_err) + << Arg::Gds(isc_dsql_cursor_rel_ambiguous) << relName << aStatement->parentCursorName); + } + parentContext = i; + found = true; + } + } - dsql_par* null = parameter->par_null; - if (null) + if (!found) { - desc.clear(); - desc.dsc_dtype = dtype_short; - desc.dsc_scale = 0; - desc.dsc_length = sizeof(SSHORT); - desc.dsc_address = (UCHAR*)(IPTR) nullOffset; - - req_user_descs.put(null, desc); + // Relation is not in cursor + ERRD_post(Arg::Gds(isc_dsql_cursor_err) + << Arg::Gds(isc_dsql_cursor_rel_not_found) << relName << aStatement->parentCursorName); } + parentRequest->cursors.add(this); } - - return count; } - -// DsqlDmlRequest - -DsqlDmlRequest::DsqlDmlRequest(thread_db* tdbb, MemoryPool& pool, dsql_dbb* dbb, DsqlStatement* aStatement) - : DsqlRequest(pool, dbb, aStatement), - req_msg_buffers(pool) +void DsqlDmlRequest::releaseRequest(thread_db* tdbb) { - // Create the messages buffers - for (auto message : aStatement->getPorts()) + // If request is a child - unsubscribe + if (parentRequest) { - // Allocate buffer for message - const ULONG newLen = message->msg_length + FB_DOUBLE_ALIGN - 1; - UCHAR* msgBuffer = FB_NEW_POOL(getPool()) UCHAR[newLen]; - msgBuffer = FB_ALIGN(msgBuffer, FB_DOUBLE_ALIGN); - fb_assert(message->msg_buffer_number == req_msg_buffers.getCount()); - req_msg_buffers.add(msgBuffer); + parentRequest->cursors.findAndRemove(this); + parentRequest = nullptr; } - request = aStatement->getStatement()->findRequest(tdbb); - tdbb->getAttachment()->att_requests.add(request); + // Let ancestor do cleanup as well + DsqlRequest::releaseRequest(tdbb); } Statement* DsqlDmlRequest::getStatement() const @@ -357,8 +333,8 @@ void DsqlDmlRequest::setDelayedFormat(thread_db* tdbb, IMessageMetadata* metadat Arg::Gds(isc_req_sync)); } + metadataToFormat(metadata, dsqlStatement->getReceiveMsg()); needDelayedFormat = false; - delayedFormat = metadata; } // Fetch next record from a dynamic SQL cursor. @@ -385,12 +361,11 @@ bool DsqlDmlRequest::fetch(thread_db* tdbb, UCHAR* msgBuffer) Arg::Gds(isc_unprepared_stmt)); } - dsql_msg* message = (dsql_msg*) dsqlStatement->getReceiveMsg(); - - if (delayedFormat && message) + // At this point we have to have output metadata from client + if (needDelayedFormat) { - parseMetadata(delayedFormat, message->msg_parameters); - delayedFormat = NULL; + ERRD_post(Arg::Gds(isc_dsql_sqlda_err) << + Arg::Gds(isc_dsql_no_output_sqlda)); } // Set up things for tracing this call @@ -401,26 +376,36 @@ bool DsqlDmlRequest::fetch(thread_db* tdbb, UCHAR* msgBuffer) if (req_timer && req_timer->expired()) tdbb->checkCancelState(); - UCHAR* dsqlMsgBuffer = req_msg_buffers[message->msg_buffer_number]; + // Fetch from already finished request should not produce error + if (!(request->req_flags & req_active)) + { + if (req_timer) + req_timer->stop(); + + trace.fetch(true, ITracePlugin::RESULT_SUCCESS); + return false; + } + if (!firstRowFetched && needRestarts()) { // Note: tra_handle can't be changed by executeReceiveWithRestarts below // and outMetadata and outMsg in not used there, so passing NULL's is safe. jrd_tra* tra = req_transaction; - executeReceiveWithRestarts(tdbb, &tra, NULL, NULL, false, false, true); + executeReceiveWithRestarts(tdbb, &tra, nullptr, nullptr, msgBuffer, false, false, true); fb_assert(tra == req_transaction); } else - JRD_receive(tdbb, request, message->msg_number, message->msg_length, dsqlMsgBuffer); + { + MessageNode* msg = dsqlStatement->getStatement()->getMessage(dsqlStatement->getReceiveMsg()->msg_number); + const Format* fmt = msg->getFormat(request); - firstRowFetched = true; + JRD_receive(tdbb, request, msg->messageNumber, fmt->fmt_length, msgBuffer); + } - const dsql_par* const eof = dsqlStatement->getEof(); - const USHORT* eofPtr = eof ? (USHORT*) (dsqlMsgBuffer + (IPTR) eof->par_desc.dsc_address) : NULL; - const bool eofReached = eof && !(*eofPtr); + firstRowFetched = true; - if (eofReached) + if (!(request->req_flags & req_active)) { if (req_timer) req_timer->stop(); @@ -429,14 +414,6 @@ bool DsqlDmlRequest::fetch(thread_db* tdbb, UCHAR* msgBuffer) return false; } - if (msgBuffer) - { - Request* old = tdbb->getRequest(); - Cleanup restoreRequest([tdbb, old] {tdbb->setRequest(old);}); - tdbb->setRequest(request); - mapInOut(tdbb, true, message, NULL, msgBuffer); - } - trace.fetch(false, ITracePlugin::RESULT_SUCCESS); return true; } @@ -524,12 +501,6 @@ DsqlCursor* DsqlDmlRequest::openCursor(thread_db* tdbb, jrd_tra** traHandle, Jrd::ContextPoolHolder context(tdbb, &getPool()); - if (dsqlStatement->getFlags() & DsqlStatement::FLAG_ORPHAN) - { - ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) << - Arg::Gds(isc_bad_req_handle)); - } - // Validate transaction handle if (!*traHandle) @@ -572,19 +543,23 @@ bool DsqlDmlRequest::needRestarts() // Execute a dynamic SQL statement void DsqlDmlRequest::doExecute(thread_db* tdbb, jrd_tra** traHandle, - IMessageMetadata* outMetadata, UCHAR* outMsg, + const UCHAR* inMsg, IMessageMetadata* outMetadata, UCHAR* outMsg, bool singleton) { firstRowFetched = false; const dsql_msg* message = dsqlStatement->getSendMsg(); if (!message) + { JRD_start(tdbb, request, req_transaction); + } else { - UCHAR* msgBuffer = req_msg_buffers[message->msg_buffer_number]; + fb_assert(inMsg != nullptr); + + ULONG inMsgLength = dsqlStatement->getStatement()->getMessage(message->msg_number)->getFormat(request)->fmt_length; JRD_start_and_send(tdbb, request, req_transaction, message->msg_number, - message->msg_length, msgBuffer); + inMsgLength, inMsg); } // Selectable execute block should get the "proc fetch" flag assigned, @@ -592,11 +567,6 @@ void DsqlDmlRequest::doExecute(thread_db* tdbb, jrd_tra** traHandle, if (dsqlStatement->getType() == DsqlStatement::TYPE_SELECT_BLOCK) request->req_flags |= req_proc_fetch; - // TYPE_EXEC_BLOCK has no outputs so there are no out_msg - // supplied from client side, but TYPE_EXEC_BLOCK requires - // 2-byte message for EOS synchronization - const bool isBlock = (dsqlStatement->getType() == DsqlStatement::TYPE_EXEC_BLOCK); - message = dsqlStatement->getReceiveMsg(); if (outMetadata == DELAYED_OUT_FORMAT) @@ -605,77 +575,38 @@ void DsqlDmlRequest::doExecute(thread_db* tdbb, jrd_tra** traHandle, outMetadata = NULL; } - if (outMetadata && message) - parseMetadata(outMetadata, message->msg_parameters); - - if ((outMsg && message) || isBlock) + if (message && (request->req_flags & req_active)) { - UCHAR temp_buffer[FB_DOUBLE_ALIGN * 2]; - dsql_msg temp_msg(*getDefaultMemoryPool()); - - // Insure that the metadata for the message is parsed, regardless of - // whether anything is found by the call to receive. - - UCHAR* msgBuffer = req_msg_buffers[message->msg_buffer_number]; - - if (!outMetadata && isBlock) + if (outMetadata) { - message = &temp_msg; - temp_msg.msg_number = 1; - temp_msg.msg_length = 2; - msgBuffer = FB_ALIGN(temp_buffer, FB_DOUBLE_ALIGN); + metadataToFormat(outMetadata, message); } - JRD_receive(tdbb, request, message->msg_number, message->msg_length, msgBuffer); - if (outMsg) - mapInOut(tdbb, true, message, NULL, outMsg); - - // if this is a singleton select, make sure there's in fact one record - - if (singleton) { - USHORT counter; - - // Create a temp message buffer and try two more receives. - // If both succeed then the first is the next record and the - // second is either another record or the end of record message. - // In either case, there's more than one record. + MessageNode* msg = dsqlStatement->getStatement()->getMessage(message->msg_number); + // outMetadata can be nullptr. If not - it is already converted to message above + ULONG outMsgLength = msg->getFormat(request)->fmt_length; - UCHAR* message_buffer = (UCHAR*) gds__alloc(message->msg_length); + JRD_receive(tdbb, request, message->msg_number, outMsgLength, outMsg); - ISC_STATUS status = FB_SUCCESS; - FbLocalStatus localStatus; + // if this is a singleton select that return some data, make sure there's in fact one record - for (counter = 0; counter < 2 && !status; counter++) + if (singleton && (request->req_flags & req_active) && outMsgLength > 0) { - localStatus->init(); - AutoSetRestore autoStatus(&tdbb->tdbb_status_vector, &localStatus); + // Create a temp message buffer and try one more receive. + // If it succeed then the next record exists. - try - { - JRD_receive(tdbb, request, message->msg_number, - message->msg_length, message_buffer); - status = FB_SUCCESS; - } - catch (Exception&) + std::unique_ptr message_buffer(new UCHAR[outMsgLength]); + + JRD_receive(tdbb, request, message->msg_number, outMsgLength, message_buffer.get()); + + // Still active request means that second record exists + if ((request->req_flags & req_active)) { - status = tdbb->tdbb_status_vector->getErrors()[1]; + status_exception::raise(Arg::Gds(isc_sing_select_err)); } } - - gds__free(message_buffer); - - // two successful receives means more than one record - // a req_sync error on the first pass above means no records - // a non-req_sync error on any of the passes above is an error - - if (!status) - status_exception::raise(Arg::Gds(isc_sing_select_err)); - else if (status == isc_req_sync && counter == 1) - status_exception::raise(Arg::Gds(isc_stream_eof)); - else if (status != isc_req_sync) - status_exception::raise(&localStatus); } } @@ -723,11 +654,25 @@ void DsqlDmlRequest::execute(thread_db* tdbb, jrd_tra** traHandle, const dsql_msg* message = dsqlStatement->getSendMsg(); if (message) - mapInOut(tdbb, false, message, inMetadata, NULL, inMsg); + { + if (!inMetadata) + { + ERRD_post(Arg::Gds(isc_dsql_sqlda_err) << + Arg::Gds(isc_dsql_no_input_sqlda)); + } + + // If this is not first call of execute(), metadata most likely is already converted to message + // but there is no easy way to check if they match so conversion is unconditional. + // Even if value of inMetadata is the same, other instance could be placed in the same memory. + // Even if the instance is the same, its content may be different from previous call. + metadataToFormat(inMetadata, message); + } + + mapCursorKey(tdbb); - // we need to mapInOut() before tracing of execution start to let trace + // we need to set new format before tracing of execution start to let trace // manager know statement parameters values - TraceDSQLExecute trace(req_dbb->dbb_attachment, this); + TraceDSQLExecute trace(req_dbb->dbb_attachment, this, inMsg); // Setup and start timeout timer const bool have_cursor = dsqlStatement->isCursorBased() && !singleton; @@ -736,15 +681,16 @@ void DsqlDmlRequest::execute(thread_db* tdbb, jrd_tra** traHandle, thread_db::TimerGuard timerGuard(tdbb, req_timer, !have_cursor); if (needRestarts()) - executeReceiveWithRestarts(tdbb, traHandle, outMetadata, outMsg, singleton, true, false); + executeReceiveWithRestarts(tdbb, traHandle, inMsg, outMetadata, outMsg, singleton, true, false); else { - doExecute(tdbb, traHandle, outMetadata, outMsg, singleton); + doExecute(tdbb, traHandle, inMsg, outMetadata, outMsg, singleton); } trace.finish(have_cursor, ITracePlugin::RESULT_SUCCESS); } void DsqlDmlRequest::executeReceiveWithRestarts(thread_db* tdbb, jrd_tra** traHandle, + const UCHAR* inMsg, IMessageMetadata* outMetadata, UCHAR* outMsg, bool singleton, bool exec, bool fetch) { @@ -764,16 +710,16 @@ void DsqlDmlRequest::executeReceiveWithRestarts(thread_db* tdbb, jrd_tra** traHa try { if (exec) - doExecute(tdbb, traHandle, outMetadata, outMsg, singleton); + doExecute(tdbb, traHandle, inMsg, outMetadata, outMsg, singleton); if (fetch) { fb_assert(dsqlStatement->isCursorBased()); const dsql_msg* message = dsqlStatement->getReceiveMsg(); + const Format* fmt = dsqlStatement->getStatement()->getMessage(message->msg_number)->getFormat(request); - UCHAR* dsqlMsgBuffer = req_msg_buffers[message->msg_buffer_number]; - JRD_receive(tdbb, request, message->msg_number, message->msg_length, dsqlMsgBuffer); + JRD_receive(tdbb, request, message->msg_number, fmt->fmt_length, outMsg); } } catch (const status_exception&) @@ -821,204 +767,173 @@ void DsqlDmlRequest::executeReceiveWithRestarts(thread_db* tdbb, jrd_tra** traHa "\tQuery:\n%s\n", numTries, request->getStatement()->sqlText->c_str() ); } - TraceManager::event_dsql_restart(req_dbb->dbb_attachment, req_transaction, this, numTries); + TraceManager::event_dsql_restart(req_dbb->dbb_attachment, req_transaction, this, inMsg, numTries); // When restart we must execute query exec = true; + // Next fetch will be performed by doExecute(), so do not call JRD_receive again + fetch = false; } } -// Map data from external world into message or from message to external world. -void DsqlDmlRequest::mapInOut(thread_db* tdbb, bool toExternal, const dsql_msg* message, - IMessageMetadata* meta, UCHAR* dsql_msg_buf, const UCHAR* in_dsql_msg_buf) +void DsqlDmlRequest::metadataToFormat(Firebird::IMessageMetadata* meta, const dsql_msg* message) { - USHORT count = parseMetadata(meta, message->msg_parameters); - - // Sanity check - - if (count) + if (!message) { - if (toExternal) - { - if (dsql_msg_buf == NULL) - { - ERRD_post(Arg::Gds(isc_dsql_sqlda_err) << - Arg::Gds(isc_dsql_no_output_sqlda)); - } - } - else - { - if (in_dsql_msg_buf == NULL) - { - ERRD_post(Arg::Gds(isc_dsql_sqlda_err) << - Arg::Gds(isc_dsql_no_input_sqlda)); - } - } + fb_assert(false); + return; } - - USHORT count2 = 0; - - for (FB_SIZE_T i = 0; i < message->msg_parameters.getCount(); ++i) + if (!meta) { - dsql_par* parameter = message->msg_parameters[i]; - - if (parameter->par_index) - { - // Make sure the message given to us is long enough - - dsc desc; - if (!req_user_descs.get(parameter, desc)) - desc.clear(); - - /*** - ULONG length = (IPTR) desc.dsc_address + desc.dsc_length; - if (length > msg_length) - { - ERRD_post(Arg::Gds(isc_dsql_sqlda_err) << - Arg::Gds(isc_random) << "Message buffer too short"); - } - ***/ - if (!desc.dsc_dtype) - { - ERRD_post(Arg::Gds(isc_dsql_sqlda_err) << - Arg::Gds(isc_dsql_datatype_err) << - Arg::Gds(isc_dsql_sqlvar_index) << Arg::Num(parameter->par_index-1)); - } + fb_assert(false); + return; + } - UCHAR* msgBuffer = req_msg_buffers[parameter->par_message->msg_buffer_number]; + MessageNode* msg = dsqlStatement->getStatement()->getMessage(message->msg_number); - SSHORT* flag = NULL; - dsql_par* const null_ind = parameter->par_null; - if (null_ind != NULL) - { - dsc userNullDesc; - if (!req_user_descs.get(null_ind, userNullDesc)) - userNullDesc.clear(); + FbLocalStatus st; + unsigned count = meta->getCount(&st); + checkD(&st); - const ULONG null_offset = (IPTR) userNullDesc.dsc_address; + const Format* oldFormat = msg->getFormat(nullptr); + unsigned count2 = oldFormat->fmt_count; - /*** - length = null_offset + sizeof(SSHORT); - if (length > msg_length) - { - ERRD_post(Arg::Gds(isc_dsql_sqlda_err) - << Arg::Gds(isc_random) << "Message buffer too short"); - } - ***/ + if (count * 2 != count2) + { + ERRD_post(Arg::Gds(isc_dsql_sqlda_err) << + Arg::Gds(isc_dsql_wrong_param_num) <par_desc; - nullDesc.dsc_address = msgBuffer + (IPTR) nullDesc.dsc_address; + if (count == 0) + { + // No point to continue, old format is fine + return; + } - if (toExternal) - { - flag = reinterpret_cast(dsql_msg_buf + null_offset); - *flag = *reinterpret_cast(nullDesc.dsc_address); - } - else - { - flag = reinterpret_cast(nullDesc.dsc_address); - *flag = *reinterpret_cast(in_dsql_msg_buf + null_offset); - } - } + // Dumb pointer is fine: on error request pool will be destructed anyway + Format* newFormat = Format::newFormat(*request->req_pool, count2); - const bool notNull = (!flag || *flag >= 0); + newFormat->fmt_length = meta->getMessageLength(&st); + checkD(&st); - dsc parDesc = parameter->par_desc; - parDesc.dsc_address = msgBuffer + (IPTR) parDesc.dsc_address; + unsigned assigned = 0; // Counter of initialized dsc in newFormat + for (unsigned i = 0; i < count2; ++i) + { + const dsql_par* param = message->msg_parameters[i]; + if (param->par_index == 0) + { + // Skip parameters not bound to SQLDA: they must be null indicators handled later. + continue; + } - if (toExternal) - { - desc.dsc_address = dsql_msg_buf + (IPTR) desc.dsc_address; + unsigned index = param->par_index - 1; + unsigned sqlType = meta->getType(&st, index); + checkD(&st); + unsigned sqlLength = meta->getLength(&st, index); + checkD(&st); - if (notNull) - MOVD_move(tdbb, &parDesc, &desc); - else - memset(desc.dsc_address, 0, desc.dsc_length); - } - else if (notNull && !parDesc.isNull()) - { - // Safe cast because desc is used as source only. - desc.dsc_address = const_cast(in_dsql_msg_buf) + (IPTR) desc.dsc_address; - MOVD_move(tdbb, &desc, &parDesc); - } - else - memset(parDesc.dsc_address, 0, parDesc.dsc_length); + // For unknown reason parameters in Format has reversed order + dsc& desc = newFormat->fmt_desc[param->par_parameter]; + desc.dsc_flags = 0; + desc.dsc_dtype = fb_utils::sqlTypeToDscType(sqlType); + desc.dsc_length = sqlLength; + if (sqlType == SQL_VARYING) + desc.dsc_length += sizeof(USHORT); + desc.dsc_scale = meta->getScale(&st, index); + checkD(&st); + desc.dsc_sub_type = meta->getSubType(&st, index); + checkD(&st); + unsigned textType = meta->getCharSet(&st, index); + checkD(&st); + desc.setTextType(textType); + desc.dsc_address = (UCHAR*)(IPTR) meta->getOffset(&st, index); + checkD(&st); + ++assigned; - ++count2; + if (param->par_null != nullptr) + { + dsc& null = newFormat->fmt_desc[param->par_null->par_parameter]; + null.dsc_dtype = dtype_short; + null.dsc_scale = 0; + null.dsc_length = sizeof(SSHORT); + null.dsc_address = (UCHAR*)(IPTR) meta->getNullOffset(&st, index); + checkD(&st); + ++assigned; } } - - if (count != count2) + // Last sanity check + if (assigned != newFormat->fmt_count) { - ERRD_post( - Arg::Gds(isc_dsql_sqlda_err) << - Arg::Gds(isc_dsql_wrong_param_num) << Arg::Num(count) <fmt_count) << Arg::Num(assigned)); } + msg->setFormat(request, newFormat); +} + +void DsqlDmlRequest::mapCursorKey(thread_db* tdbb) +{ const auto dsqlStatement = getDsqlStatement(); - const dsql_par* parameter; + if (!dsqlStatement->parentCursorName.hasData()) + return; + + RecordKey dbKey = {}; - const dsql_par* dbkey; - if (!toExternal && (dbkey = dsqlStatement->getParentDbKey()) && - (parameter = dsqlStatement->getDbKey())) + if (!parentRequest || !parentRequest->req_cursor) // It has been already closed { - UCHAR* parentMsgBuffer = dsqlStatement->getParentRequest() ? - dsqlStatement->getParentRequest()->req_msg_buffers[dbkey->par_message->msg_buffer_number] : NULL; - UCHAR* msgBuffer = req_msg_buffers[parameter->par_message->msg_buffer_number]; + // Here may be code to re-establish link to parent cursor. + ERRD_post(Arg::Gds(isc_dsql_cursor_err) << + Arg::Gds(isc_dsql_cursor_not_found) << dsqlStatement->parentCursorName); + } - fb_assert(parentMsgBuffer); + if (!parentRequest->req_cursor->getCurrentRecordKey(parentContext, dbKey)) + { + ERRD_post(Arg::Gds(isc_cursor_not_positioned) << dsqlStatement->parentCursorName); + } - dsc parentDesc = dbkey->par_desc; - parentDesc.dsc_address = parentMsgBuffer + (IPTR) parentDesc.dsc_address; + fb_assert(request); - dsc desc = parameter->par_desc; - desc.dsc_address = msgBuffer + (IPTR) desc.dsc_address; + // Assign record key + MessageNode* message = request->getStatement()->getMessage(2); + fb_assert(message); - MOVD_move(tdbb, &parentDesc, &desc); + dsc desc = message->getFormat(request)->fmt_desc[0]; + UCHAR* msgBuffer = message->getBuffer(request); + desc.dsc_address = msgBuffer + (IPTR) desc.dsc_address; - dsql_par* null_ind = parameter->par_null; - if (null_ind != NULL) - { - desc = null_ind->par_desc; - desc.dsc_address = msgBuffer + (IPTR) desc.dsc_address; + dsc parentDesc; + parentDesc.makeDbkey(&dbKey.recordNumber); - SSHORT* flag = (SSHORT*) desc.dsc_address; - *flag = 0; - } - } + MOVD_move(tdbb, &parentDesc, &desc); - const dsql_par* rec_version; - if (!toExternal && (rec_version = dsqlStatement->getParentRecVersion()) && - (parameter = dsqlStatement->getRecVersion())) - { - UCHAR* parentMsgBuffer = dsqlStatement->getParentRequest() ? - dsqlStatement->getParentRequest()->req_msg_buffers[rec_version->par_message->msg_buffer_number] : - NULL; - UCHAR* msgBuffer = req_msg_buffers[parameter->par_message->msg_buffer_number]; - - fb_assert(parentMsgBuffer); + // Assign version number - dsc parentDesc = rec_version->par_desc; - parentDesc.dsc_address = parentMsgBuffer + (IPTR) parentDesc.dsc_address; + desc = message->getFormat(request)->fmt_desc[1]; + desc.dsc_address = msgBuffer + (IPTR) desc.dsc_address; - dsc desc = parameter->par_desc; - desc.dsc_address = msgBuffer + (IPTR) desc.dsc_address; + // This is not a mistake, record version is represented as a DbKey in RecordKeyNode::make() to allow (theoretically now) positional DML on views + parentDesc.makeDbkey(&dbKey.recordVersion); - MOVD_move(tdbb, &parentDesc, &desc); + MOVD_move(tdbb, &parentDesc, &desc); +} - dsql_par* null_ind = parameter->par_null; - if (null_ind != NULL) +void DsqlDmlRequest::gatherRecordKey(RecordKey* buffer) const +{ + fb_assert(request->req_rpb.getCount() > 0); + memset(buffer, 0, request->req_rpb.getCount() * sizeof(RecordKey)); + for (unsigned i = 0; i < request->req_rpb.getCount(); ++i) + { + record_param& rpb = request->req_rpb[i]; + if (rpb.rpb_relation && rpb.rpb_number.isValid() && !rpb.rpb_number.isBof()) { - desc = null_ind->par_desc; - desc.dsc_address = msgBuffer + (IPTR) desc.dsc_address; - - SSHORT* flag = (SSHORT*) desc.dsc_address; - *flag = 0; + buffer[i].recordNumber.bid_encode(rpb.rpb_number.getValue() + 1); + buffer[i].recordNumber.bid_relation_id = rpb.rpb_relation->rel_id; + buffer[i].recordVersion = rpb.rpb_transaction_nr; } } } - // DsqlDdlRequest DsqlDdlRequest::DsqlDdlRequest(MemoryPool& pool, dsql_dbb* dbb, DsqlCompilerScratch* aInternalScratch, DdlNode* aNode) @@ -1034,7 +949,7 @@ void DsqlDdlRequest::execute(thread_db* tdbb, jrd_tra** traHandle, IMessageMetadata* outMetadata, UCHAR* outMsg, bool singleton) { - TraceDSQLExecute trace(req_dbb->dbb_attachment, this); + TraceDSQLExecute trace(req_dbb->dbb_attachment, this, inMsg); fb_utils::init_status(tdbb->tdbb_status_vector); @@ -1081,11 +996,11 @@ DsqlTransactionRequest::DsqlTransactionRequest(MemoryPool& pool, dsql_dbb* dbb, // Execute a dynamic SQL statement. void DsqlTransactionRequest::execute(thread_db* tdbb, jrd_tra** traHandle, - IMessageMetadata* /*inMetadata*/, const UCHAR* /*inMsg*/, + IMessageMetadata* /*inMetadata*/, const UCHAR* inMsg, IMessageMetadata* /*outMetadata*/, UCHAR* /*outMsg*/, bool /*singleton*/) { - TraceDSQLExecute trace(req_dbb->dbb_attachment, this); + TraceDSQLExecute trace(req_dbb->dbb_attachment, this, inMsg); node->execute(tdbb, this, traHandle); trace.finish(false, ITracePlugin::RESULT_SUCCESS); } @@ -1117,7 +1032,7 @@ void DsqlSessionManagementRequest::execute(thread_db* tdbb, jrd_tra** traHandle, IMessageMetadata* outMetadata, UCHAR* outMsg, bool singleton) { - TraceDSQLExecute trace(req_dbb->dbb_attachment, this); + TraceDSQLExecute trace(req_dbb->dbb_attachment, this, inMsg); node->execute(tdbb, this, traHandle); trace.finish(false, ITracePlugin::RESULT_SUCCESS); } diff --git a/src/dsql/DsqlRequests.h b/src/dsql/DsqlRequests.h index 860f46433a3..48509a3787f 100644 --- a/src/dsql/DsqlRequests.h +++ b/src/dsql/DsqlRequests.h @@ -40,12 +40,14 @@ class DsqlStatement; class DsqlCompilerScratch; class DsqlCursor; class DsqlDmlStatement; +class DsqlDmlRequest; class dsql_par; class Request; class jrd_tra; class Statement; class SessionManagementNode; class TransactionNode; +struct RecordKey; class DsqlRequest : public Firebird::PermanentStorage @@ -53,6 +55,7 @@ class DsqlRequest : public Firebird::PermanentStorage public: DsqlRequest(MemoryPool& pool, dsql_dbb* dbb, DsqlStatement* aStatement); virtual ~DsqlRequest(); + virtual void releaseRequest(thread_db* tdbb); public: jrd_tra* getTransaction() @@ -115,14 +118,12 @@ class DsqlRequest : public Firebird::PermanentStorage // setup and start timer TimeoutTimer* setupTimer(thread_db* tdbb); - USHORT parseMetadata(Firebird::IMessageMetadata* meta, const Firebird::Array& parameters_list); - static void destroy(thread_db* tdbb, DsqlRequest* request); public: dsql_dbb* req_dbb; // DSQL attachment Firebird::RefPtr dsqlStatement; - Firebird::Array cursors{getPool()}; // Cursor update statements + Firebird::Array cursors{getPool()}; // Cursor update statements jrd_tra* req_transaction = nullptr; // JRD transaction @@ -145,7 +146,8 @@ class DsqlRequest : public Firebird::PermanentStorage class DsqlDmlRequest final : public DsqlRequest { public: - DsqlDmlRequest(thread_db* tdbb, MemoryPool& pool, dsql_dbb* dbb, DsqlStatement* aDsqlStatement); + DsqlDmlRequest(thread_db* tdbb, MemoryPool& pool, dsql_dbb* dbb, DsqlDmlStatement* aDsqlStatement); + void releaseRequest(thread_db* tdbb) override; // Reintroduce method to fake covariant return type with RefPtr. auto getDsqlStatement() @@ -160,6 +162,11 @@ class DsqlDmlRequest final : public DsqlRequest return request; } + void onReferencedCursorClose() + { + parentRequest = nullptr; + } + DsqlCursor* openCursor(thread_db* tdbb, jrd_tra** traHandle, Firebird::IMessageMetadata* inMeta, const UCHAR* inMsg, Firebird::IMessageMetadata* outMeta, ULONG flags) override; @@ -178,30 +185,33 @@ class DsqlDmlRequest final : public DsqlRequest void setDelayedFormat(thread_db* tdbb, Firebird::IMessageMetadata* metadata) override; - void mapInOut(Jrd::thread_db* tdbb, bool toExternal, const dsql_msg* message, Firebird::IMessageMetadata* meta, - UCHAR* dsql_msg_buf, const UCHAR* in_dsql_msg_buf = nullptr); + // Convert IMessageMetadata to Format and force it to corresponding MessageNode for current request. + // After that this MessageNode and their ParameterNodes can work with client message buffer directly + void metadataToFormat(Firebird::IMessageMetadata* metadata, const dsql_msg* message); + void mapCursorKey(thread_db* tdbb); + void gatherRecordKey(RecordKey* buffer) const; private: // True, if request could be restarted bool needRestarts(); void doExecute(thread_db* tdbb, jrd_tra** traHandle, + const UCHAR* inMsg, // Only data buffer, metadata must be synchronized before call Firebird::IMessageMetadata* outMetadata, UCHAR* outMsg, bool singleton); // [Re]start part of "request restarts" algorithm void executeReceiveWithRestarts(thread_db* tdbb, jrd_tra** traHandle, + const UCHAR* inMsg, Firebird::IMessageMetadata* outMetadata, UCHAR* outMsg, bool singleton, bool exec, bool fetch); -public: - Firebird::Array req_msg_buffers; - private: - Firebird::RefPtr delayedFormat; Request* request = nullptr; bool needDelayedFormat = false; bool firstRowFetched = false; + DsqlRequest* parentRequest = nullptr; + USHORT parentContext; }; diff --git a/src/dsql/DsqlStatements.cpp b/src/dsql/DsqlStatements.cpp index 47117b26a68..f279e0e4169 100644 --- a/src/dsql/DsqlStatements.cpp +++ b/src/dsql/DsqlStatements.cpp @@ -108,13 +108,6 @@ void DsqlStatement::setOrgText(const char* ptr, ULONG len) void DsqlDmlStatement::doRelease() { - if (auto parent = getParentRequest()) - { - FB_SIZE_T pos; - if (parent->cursors.find(this, pos)) - parent->cursors.remove(pos); - } - if (statement) { thread_db* tdbb = JRD_get_thread_data(); @@ -152,9 +145,6 @@ void DsqlDmlStatement::dsqlPass(thread_db* tdbb, DsqlCompilerScratch* scratch, n unsigned messageNumber = 0; - for (auto message : ports) - message->msg_buffer_number = messageNumber++; - // have the access method compile the statement #ifdef DSQL_DEBUG @@ -223,7 +213,8 @@ void DsqlDmlStatement::dsqlPass(thread_db* tdbb, DsqlCompilerScratch* scratch, n if (status) status_exception::raise(tdbb->tdbb_status_vector); - node = NULL; + node = nullptr; + scratch = nullptr; } DsqlDmlRequest* DsqlDmlStatement::createRequest(thread_db* tdbb, dsql_dbb* dbb) diff --git a/src/dsql/DsqlStatements.h b/src/dsql/DsqlStatements.h index 1ab48fc9862..f28c06a3768 100644 --- a/src/dsql/DsqlStatements.h +++ b/src/dsql/DsqlStatements.h @@ -59,7 +59,6 @@ class DsqlStatement : public Firebird::PermanentStorage }; // Statement flags. - static const unsigned FLAG_ORPHAN = 0x01; static const unsigned FLAG_NO_BATCH = 0x02; //static const unsigned FLAG_BLR_VERSION4 = 0x04; //static const unsigned FLAG_BLR_VERSION5 = 0x08; @@ -73,8 +72,7 @@ class DsqlStatement : public Firebird::PermanentStorage dsqlAttachment(aDsqlAttachment), type(TYPE_SELECT), flags(0), - blrVersion(5), - ports(pool) + blrVersion(5) { pool.setStatsGroup(memoryStats); } @@ -121,8 +119,6 @@ class DsqlStatement : public Firebird::PermanentStorage void setOrgText(const char* ptr, ULONG len); const Firebird::string& getOrgText() const { return *orgText; } - Firebird::Array& getPorts() { return ports; } - dsql_msg* getSendMsg() { return sendMsg; } const dsql_msg* getSendMsg() const { return sendMsg; } void setSendMsg(dsql_msg* value) { sendMsg = value; } @@ -131,10 +127,6 @@ class DsqlStatement : public Firebird::PermanentStorage const dsql_msg* getReceiveMsg() const { return receiveMsg; } void setReceiveMsg(dsql_msg* value) { receiveMsg = value; } - dsql_par* getEof() { return eof; } - const dsql_par* getEof() const { return eof; } - void setEof(dsql_par* value) { eof = value; } - Firebird::RefStrPtr getCacheKey() { return cacheKey; } void setCacheKey(Firebird::RefStrPtr& value) { cacheKey = value; } void resetCacheKey() { cacheKey = nullptr; } @@ -180,10 +172,8 @@ class DsqlStatement : public Firebird::PermanentStorage Firebird::RefStrPtr sqlText; Firebird::RefStrPtr orgText; Firebird::RefStrPtr cacheKey; - Firebird::Array ports; // Port messages dsql_msg* sendMsg = nullptr; // Message to be sent to start request dsql_msg* receiveMsg = nullptr; // Per record message to be received - dsql_par* eof = nullptr; // End of file parameter DsqlCompilerScratch* scratch = nullptr; private: @@ -194,8 +184,11 @@ class DsqlStatement : public Firebird::PermanentStorage class DsqlDmlStatement final : public DsqlStatement { public: + MetaName parentCursorName; + DsqlDmlStatement(MemoryPool& p, dsql_dbb* aDsqlAttachment, StmtNode* aNode) : DsqlStatement(p, aDsqlAttachment), + parentCursorName(p), node(aNode) { } @@ -221,36 +214,12 @@ class DsqlDmlStatement final : public DsqlStatement void dsqlPass(thread_db* tdbb, DsqlCompilerScratch* scratch, ntrace_result_t* traceResult) override; DsqlDmlRequest* createRequest(thread_db* tdbb, dsql_dbb* dbb) override; - dsql_par* getDbKey() { return dbKey; } - const dsql_par* getDbKey() const { return dbKey; } - void setDbKey(dsql_par* value) { dbKey = value; } - - dsql_par* getRecVersion() { return recVersion; } - const dsql_par* getRecVersion() const { return recVersion; } - void setRecVersion(dsql_par* value) { recVersion = value; } - - dsql_par* getParentRecVersion() { return parentRecVersion; } - const dsql_par* getParentRecVersion() const { return parentRecVersion; } - void setParentRecVersion(dsql_par* value) { parentRecVersion = value; } - - dsql_par* getParentDbKey() { return parentDbKey; } - const dsql_par* getParentDbKey() const { return parentDbKey; } - void setParentDbKey(dsql_par* value) { parentDbKey = value; } - - DsqlDmlRequest* getParentRequest() const { return parentRequest; } - void setParentRequest(DsqlDmlRequest* value) { parentRequest = value; } - protected: void doRelease() override; private: NestConst node; Statement* statement = nullptr; - dsql_par* dbKey = nullptr; // Database key for current of - dsql_par* recVersion = nullptr; // Record Version for current of - dsql_par* parentRecVersion = nullptr; // parent record version - dsql_par* parentDbKey = nullptr; // Parent database key for current of - DsqlDmlRequest* parentRequest = nullptr; // Source request, if cursor update }; diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index 8320ab355be..9be119d3a82 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -9615,8 +9615,7 @@ ValueExprNode* ParameterNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) Arg::Gds(isc_dsql_command_err)); } - auto msg = dsqlMessage ? dsqlMessage : - dsqlParameter ? dsqlParameter->par_message : + auto msg = dsqlParameter ? dsqlParameter->par_message : dsqlScratch->getDsqlStatement()->getSendMsg(); auto node = FB_NEW_POOL(dsqlScratch->getPool()) ParameterNode(dsqlScratch->getPool()); @@ -9793,10 +9792,15 @@ Request* ParameterNode::getParamRequest(Request* request) const void ParameterNode::getDesc(thread_db* /*tdbb*/, CompilerScratch* /*csb*/, dsc* desc) { - *desc = message->format->fmt_desc[argNumber]; + const auto format = message->getFormat(nullptr); + + if (argNumber >= format->fmt_count) + status_exception::raise(Arg::Gds(isc_badparnum)); + + *desc = format->fmt_desc[argNumber]; // Must reset dsc_address because it's used in others places to read literals, but here it was // an offset in the message. - desc->dsc_address = NULL; + desc->dsc_address = nullptr; } ParameterNode* ParameterNode::copy(thread_db* tdbb, NodeCopier& copier) const @@ -9840,7 +9844,7 @@ ParameterNode* ParameterNode::pass1(thread_db* tdbb, CompilerScratch* csb) outerDecl = csb->outerMessagesMap.exist(messageNumber); } - const auto format = message->format; + const auto format = message->getFormat(nullptr); if (argNumber >= format->fmt_count) status_exception::raise(Arg::Gds(isc_badparnum)); @@ -9858,8 +9862,8 @@ ParameterNode* ParameterNode::pass1(thread_db* tdbb, CompilerScratch* csb) { fb_assert(csb->mainCsb); - if (csb->mainCsb) - message->itemsUsedInSubroutines.add(argNumber); + if (!csb->mainCsb) + status_exception::raise(Arg::Gds(isc_ctxnotdef) << Arg::Gds(isc_random) << Arg::Str("Outer parameter has no outer scratch")); } return this; @@ -9874,10 +9878,7 @@ ParameterNode* ParameterNode::pass2(thread_db* tdbb, CompilerScratch* csb) ValueExprNode::pass2(tdbb, csb); - dsc desc; - getDesc(tdbb, csb, &desc); - - if (message->itemsUsedInSubroutines.exist(argNumber)) + if (outerDecl) impureOffset = csb->allocImpure(); else impureOffset = csb->allocImpure(); @@ -9890,7 +9891,7 @@ dsc* ParameterNode::execute(thread_db* tdbb, Request* request) const dsc* retDesc; impure_value* impureForOuter; - if (message->itemsUsedInSubroutines.exist(argNumber)) + if (outerDecl) { impureForOuter = request->getImpure(impureOffset); retDesc = &impureForOuter->vlu_desc; @@ -9916,10 +9917,9 @@ dsc* ParameterNode::execute(thread_db* tdbb, Request* request) const request->req_flags |= req_null; } - desc = &message->format->fmt_desc[argNumber]; + desc = &message->getFormat(paramRequest)->fmt_desc[argNumber]; - retDesc->dsc_address = paramRequest->getImpure( - message->impureOffset + (IPTR) desc->dsc_address); + retDesc->dsc_address = message->getBuffer(paramRequest) + (IPTR) desc->dsc_address; retDesc->dsc_dtype = desc->dsc_dtype; retDesc->dsc_length = desc->dsc_length; retDesc->dsc_scale = desc->dsc_scale; @@ -9951,7 +9951,7 @@ dsc* ParameterNode::execute(thread_db* tdbb, Request* request) const switch (retDesc->dsc_dtype) { case dtype_cstring: - len = strnlen((const char*) p, maxLen); + len = static_cast(strnlen((const char*) p, maxLen)); --maxLen; break; @@ -10178,7 +10178,6 @@ void RecordKeyNode::genBlr(DsqlCompilerScratch* dsqlScratch) void RecordKeyNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc) { - fb_assert(blrOp == blr_dbkey || blrOp == blr_record_version2); fb_assert(dsqlRelation); // Fix for bug 10072 check that the target is a relation @@ -10188,23 +10187,20 @@ void RecordKeyNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc) { USHORT dbKeyLength = (relation->rel_flags & REL_creating ? 8 : relation->rel_dbkey_length); - if (blrOp == blr_dbkey) + if (blrOp == blr_record_version2) { + desc->makeInt64(0); + desc->setNullable(true); + } + else + { + // If views are ever allowed to be used in positional DML, + // we have to concatenate record versions the same way as dbkeys desc->dsc_dtype = dtype_text; desc->dsc_length = dbKeyLength; desc->dsc_flags = DSC_nullable; desc->dsc_ttype() = ttype_binary; } - else // blr_record_version2 - { - if (dbKeyLength == 8) - { - desc->makeInt64(0); - desc->setNullable(true); - } - else - raiseError(dsqlRelation->dsqlContext); - } } else raiseError(dsqlRelation->dsqlContext); @@ -10479,7 +10475,7 @@ dsc* RecordKeyNode::execute(thread_db* /*tdbb*/, Request* request) const else if (blrOp == blr_record_version) { // Make up a record version for a record stream. The tid of the record will be used. - // This will be returned as a 4 byte character string. + // This will be returned as a 8 byte character string. // If the current transaction has updated the record, the record version // coming in from DSQL will have the original transaction # (or current @@ -12186,7 +12182,7 @@ dsc* SubstringSimilarNode::execute(thread_db* tdbb, Request* request) const MoveBuffer escapeBuffer; UCHAR* escapeStr; - int escapeLen = MOV_make_string2(tdbb, escapeDesc, textType, &escapeStr, escapeBuffer); + ULONG escapeLen = MOV_make_string2(tdbb, escapeDesc, textType, &escapeStr, escapeBuffer); // Verify the correctness of the escape character. if (escapeLen == 0 || charSet->length(escapeLen, escapeStr, true) != 1) @@ -14037,11 +14033,7 @@ ValueExprNode* VariableNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) { node->outerDecl = true; - const bool execBlock = (dsqlScratch->mainScratch->flags & DsqlCompilerScratch::FLAG_BLOCK) && - !(dsqlScratch->mainScratch->flags & - (DsqlCompilerScratch::FLAG_PROCEDURE | - DsqlCompilerScratch::FLAG_TRIGGER | - DsqlCompilerScratch::FLAG_FUNCTION)); + const bool execBlock = (dsqlScratch->mainScratch->flags & DsqlCompilerScratch::FLAG_EXEC_BLOCK); if (node->dsqlVar->type == dsql_var::TYPE_INPUT && !execBlock) { @@ -14049,7 +14041,7 @@ ValueExprNode* VariableNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) { // 0 = input, 1 = output. Start outer messages with 2. dsqlScratch->outerMessagesMap.put( - node->dsqlVar->msgNumber, 2 + dsqlScratch->outerMessagesMap.count()); + node->dsqlVar->msgNumber, static_castouterMessagesMap)::ValueType>(2 + dsqlScratch->outerMessagesMap.count())); } } else @@ -14078,11 +14070,7 @@ void VariableNode::genBlr(DsqlCompilerScratch* dsqlScratch) { auto varScratch = outerDecl ? dsqlScratch->mainScratch : dsqlScratch; - const bool execBlock = (varScratch->flags & DsqlCompilerScratch::FLAG_BLOCK) && - !(varScratch->flags & - (DsqlCompilerScratch::FLAG_PROCEDURE | - DsqlCompilerScratch::FLAG_TRIGGER | - DsqlCompilerScratch::FLAG_FUNCTION)); + const bool execBlock = (varScratch->flags & DsqlCompilerScratch::FLAG_EXEC_BLOCK); if (dsqlVar->type == dsql_var::TYPE_INPUT && !execBlock) { diff --git a/src/dsql/ExprNodes.h b/src/dsql/ExprNodes.h index fb79beccf57..c7a199a8430 100644 --- a/src/dsql/ExprNodes.h +++ b/src/dsql/ExprNodes.h @@ -1615,7 +1615,7 @@ class ParameterNode final : public TypedNode makeDesc, bool forceVarChar); - virtual void genBlr(DsqlCompilerScratch* dsqlScratch); - virtual void make(DsqlCompilerScratch* dsqlScratch, dsc* desc); - virtual bool dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, bool ignoreMapCast) const; + bool setParameterType(DsqlCompilerScratch* dsqlScratch, + std::function makeDesc, bool forceVarChar) override; + void genBlr(DsqlCompilerScratch* dsqlScratch) override; + void make(DsqlCompilerScratch* dsqlScratch, dsc* desc) override; + bool dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, bool ignoreMapCast) const override; Request* getParamRequest(Request* request) const; @@ -1649,19 +1649,20 @@ class ParameterNode final : public TypedNode message; NestConst argFlag; NestConst argInfo; USHORT dsqlParameterIndex = 0; + // This is an initial number as got from BLR. + // Message can be modified during merge of SP/view subtrees USHORT messageNumber = MAX_USHORT; USHORT argNumber = 0; bool outerDecl = false; diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index 3252da179b1..7a5e5ccb416 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -73,9 +73,6 @@ namespace Jrd { template static void dsqlExplodeFields(dsql_rel* relation, Array >& fields, bool includeComputed); -static dsql_par* dsqlFindDbKey(const DsqlDmlStatement*, const RelationSourceNode*); -static dsql_par* dsqlFindRecordVersion(const DsqlDmlStatement*, const RelationSourceNode*); -static void dsqlGenEofAssignment(DsqlCompilerScratch* dsqlScratch, SSHORT value); static void dsqlGenReturning(DsqlCompilerScratch* dsqlScratch, ReturningClause* returning, std::optional localTableNumber); static void dsqlGenReturningLocalTableCursor(DsqlCompilerScratch* dsqlScratch, ReturningClause* returning, @@ -3903,8 +3900,8 @@ void ExecProcedureNode::executeProcedure(thread_db* tdbb, Request* request) cons if (inputMessage) { - inMsgLength = inputMessage->format->fmt_length; - inMsg = request->getImpure(inputMessage->impureOffset); + inMsgLength = inputMessage->getFormat(request)->fmt_length; + inMsg = inputMessage->getBuffer(request); } const Format* format = NULL; @@ -3914,9 +3911,9 @@ void ExecProcedureNode::executeProcedure(thread_db* tdbb, Request* request) cons if (outputMessage) { - format = outputMessage->format; + format = outputMessage->getFormat(request); outMsgLength = format->fmt_length; - outMsg = request->getImpure(outputMessage->impureOffset); + outMsg = outputMessage->getBuffer(request); } else { @@ -4950,6 +4947,8 @@ ExecBlockNode* ExecBlockNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) statement->setType(DsqlStatement::TYPE_EXEC_BLOCK); dsqlScratch->flags |= DsqlCompilerScratch::FLAG_BLOCK; + if (!(dsqlScratch->flags & DsqlCompilerScratch::FLAG_SUB_ROUTINE)) + dsqlScratch->flags |= DsqlCompilerScratch::FLAG_EXEC_BLOCK; dsqlScratch->reserveInitialVarNumbers(parameters.getCount() + returns.getCount()); ExecBlockNode* node = FB_NEW_POOL(dsqlScratch->getPool()) ExecBlockNode(dsqlScratch->getPool()); @@ -5079,16 +5078,14 @@ void ExecBlockNode::genBlr(DsqlCompilerScratch* dsqlScratch) DsqlDescMaker::fromNode(dsqlScratch, ¶m->par_desc, varNode, true); } - // Set up parameter to handle EOF - dsql_par* param = MAKE_parameter(statement->getReceiveMsg(), false, false, 0, NULL); - statement->setEof(param); - param->par_desc.dsc_dtype = dtype_short; - param->par_desc.dsc_scale = 0; - param->par_desc.dsc_length = sizeof(SSHORT); - - revertParametersOrder(statement->getReceiveMsg()->msg_parameters); - if (!subRoutine) - GEN_port(dsqlScratch, statement->getReceiveMsg()); + if (returns.hasData()) + { + revertParametersOrder(statement->getReceiveMsg()->msg_parameters); + if (!subRoutine) + GEN_port(dsqlScratch, statement->getReceiveMsg()); + } + else + statement->setReceiveMsg(nullptr); if (subRoutine) { @@ -5143,7 +5140,11 @@ void ExecBlockNode::genBlr(DsqlCompilerScratch* dsqlScratch) dsqlScratch->putOuterMaps(); GEN_hidden_variables(dsqlScratch); - dsqlScratch->appendUChar(blr_stall); + // This verb delays execution until the next call of EXE_receive() + // which should allow it to create its savepoint in the right place on stack. + // But for EB without returning values it is just a waste of time. + if (returns.hasData()) + dsqlScratch->appendUChar(blr_stall); // Put a label before body of procedure, so that // any exit statement can get out dsqlScratch->appendUChar(blr_label); @@ -5157,7 +5158,8 @@ void ExecBlockNode::genBlr(DsqlCompilerScratch* dsqlScratch) statement->setType(DsqlStatement::TYPE_EXEC_BLOCK); dsqlScratch->appendUChar(blr_end); - dsqlScratch->genReturn(true); + if (subRoutine) + dsqlScratch->genReturn(true); dsqlScratch->appendUChar(blr_end); dsqlScratch->endDebug(); @@ -7102,7 +7104,7 @@ void MessageNode::setup(thread_db* tdbb, CompilerScratch* csb, USHORT message, U bool shouldPad = csb->csb_message_pad.get(messageNumber, padField); // Get the number of parameters in the message and prepare to fill out the format block. - + fb_assert(format == nullptr); format = Format::newFormat(*tdbb->getDefaultPool(), count); USHORT maxAlignment = 0; ULONG offset = 0; @@ -7181,7 +7183,7 @@ MessageNode* MessageNode::pass2(thread_db* /*tdbb*/, CompilerScratch* csb) { fb_assert(format); - impureOffset = csb->allocImpure(FB_ALIGNMENT, FB_ALIGN(format->fmt_length, 2)); + impureOffset = csb->allocImpure(); impureFlags = csb->allocImpure(FB_ALIGNMENT, sizeof(USHORT) * format->fmt_count); return this; @@ -7199,6 +7201,50 @@ const StmtNode* MessageNode::execute(thread_db* /*tdbb*/, Request* request, ExeS return parentStmt; } +UCHAR* MessageNode::getBuffer(Request* request) const +{ + MessageBuffer* buffer = request->getImpure(impureOffset); + if (buffer->buffer == nullptr) + { + size_t length = buffer->format == nullptr ? format->fmt_length : buffer->format->fmt_length; + buffer->buffer = reinterpret_cast(request->req_pool->calloc(length ALLOC_ARGS)); + } + return buffer->buffer; +} + +const Format* MessageNode::getFormat(const Request* request) const +{ + if (request != nullptr) + { + const MessageBuffer* buffer = request->getImpure(impureOffset); + if (buffer->format != nullptr) + { + return buffer->format; + } + } + return format.getObject(); +} + +void MessageNode::setFormat(Request* request, Format* newFormat) +{ + fb_assert(request != nullptr); + MessageBuffer* buffer = request->getImpure(impureOffset); + ULONG oldLength = format->fmt_length; + if (buffer->format != nullptr) + { + oldLength = buffer->format->fmt_length; + delete buffer->format; + } + buffer->format = newFormat; + + // Take care about existing buffer because it is the last point where its size is (roughly) known + if (buffer->buffer != nullptr && oldLength < newFormat->fmt_length) + { + request->req_pool->deallocate(buffer->buffer); + buffer->buffer = nullptr; + // but leave allocation of the new buffer to following getBuffer() if any + } +} //-------------------- @@ -7469,6 +7515,11 @@ string ModifyNode::internalPrint(NodePrinter& printer) const void ModifyNode::genBlr(DsqlCompilerScratch* dsqlScratch) { + if (dsqlScratch->recordKeyMessage) + { + GEN_port(dsqlScratch, dsqlScratch->recordKeyMessage); + } + if (dsqlReturning && !dsqlScratch->isPsql()) { if (dsqlCursorName.isEmpty()) @@ -8996,7 +9047,7 @@ RseNode* SelectNode::dsqlProcess(DsqlCompilerScratch* dsqlScratch) const auto processedRse = PASS1_rse(dsqlScratch, selectExpr, this); dsqlScratch->context->clear(base); - if (forUpdate) + if (forUpdate && !processedRse->dsqlDistinct) { statement->setType(DsqlStatement::TYPE_SELECT_UPD); statement->addFlags(DsqlStatement::FLAG_NO_BATCH); @@ -9033,50 +9084,6 @@ SelectNode* SelectNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) DsqlDescMaker::fromNode(dsqlScratch, ¶meter->par_desc, item); } - // Set up parameter to handle EOF. - - const auto parameterEof = MAKE_parameter(statement->getReceiveMsg(), false, false, 0, nullptr); - statement->setEof(parameterEof); - parameterEof->par_desc.dsc_dtype = dtype_short; - parameterEof->par_desc.dsc_scale = 0; - parameterEof->par_desc.dsc_length = sizeof(SSHORT); - - // Save DBKEYs for possible update later. - - if (forUpdate && !node->rse->dsqlDistinct) - { - for (const auto item : node->rse->dsqlStreams->items) - { - //// TODO: LocalTableSourceNode - if (auto relNode = nodeAs(item)) - { - dsql_ctx* context = relNode->dsqlContext; - - if (const auto* const relation = context->ctx_relation) - { - // Set up dbkey. - auto parameter = MAKE_parameter(statement->getReceiveMsg(), false, false, 0, nullptr); - - parameter->par_dbkey_relname = relation->rel_name; - parameter->par_context = context; - - parameter->par_desc.dsc_dtype = dtype_text; - parameter->par_desc.dsc_ttype() = ttype_binary; - parameter->par_desc.dsc_length = relation->rel_dbkey_length; - - // Set up record version. - parameter = MAKE_parameter(statement->getReceiveMsg(), false, false, 0, nullptr); - parameter->par_rec_version_relname = relation->rel_name; - parameter->par_context = context; - - parameter->par_desc.dsc_dtype = dtype_text; - parameter->par_desc.dsc_ttype() = ttype_binary; - parameter->par_desc.dsc_length = sizeof(SINT64); - } - } - } - } - return node; } @@ -9133,17 +9140,6 @@ void SelectNode::genBlr(DsqlCompilerScratch* dsqlScratch) // Build body of FOR loop. - SSHORT constant; - dsc constantDesc; - constantDesc.makeShort(0, &constant); - - // Add invalid usage here. - - dsqlScratch->appendUChar(blr_assignment); - constant = 1; - LiteralNode::genConstant(dsqlScratch, &constantDesc, false); - GEN_parameter(dsqlScratch, statement->getEof()); - for (const auto parameter : message->msg_parameters) { if (parameter->par_node) @@ -9152,33 +9148,9 @@ void SelectNode::genBlr(DsqlCompilerScratch* dsqlScratch) GEN_expr(dsqlScratch, parameter->par_node); GEN_parameter(dsqlScratch, parameter); } - - if (parameter->par_dbkey_relname.hasData() && parameter->par_context) - { - dsqlScratch->appendUChar(blr_assignment); - dsqlScratch->appendUChar(blr_dbkey); - GEN_stuff_context(dsqlScratch, parameter->par_context); - GEN_parameter(dsqlScratch, parameter); - } - - if (parameter->par_rec_version_relname.hasData() && parameter->par_context) - { - dsqlScratch->appendUChar(blr_assignment); - dsqlScratch->appendUChar(blr_record_version); - GEN_stuff_context(dsqlScratch, parameter->par_context); - GEN_parameter(dsqlScratch, parameter); - } } dsqlScratch->appendUChar(blr_end); - - dsqlScratch->appendUChar(blr_send); - dsqlScratch->appendUChar(message->msg_number); - - dsqlScratch->appendUChar(blr_assignment); - constant = 0; - LiteralNode::genConstant(dsqlScratch, &constantDesc, false); - GEN_parameter(dsqlScratch, statement->getEof()); } @@ -9367,7 +9339,7 @@ const StmtNode* StallNode::execute(thread_db* /*tdbb*/, Request* request, ExeSta case Request::req_return: request->req_message = this; request->req_operation = Request::req_return; - request->req_flags |= req_stall; + request->req_flags |= req_stall; // Signal looper to return return this; case Request::req_proceed: @@ -9458,13 +9430,24 @@ const StmtNode* SuspendNode::execute(thread_db* tdbb, Request* request, ExeState switch (request->req_operation) { case Request::req_evaluate: + // Signal EXE_receive that we are ready to assign data + request->req_operation = Request::req_send; + request->req_message = message; + request->req_flags |= req_stall; // Signal looper to return + return this; + + case Request::req_proceed: { + // EXE_receive prepared everything and ask us to proceed assignments + request->req_operation = 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)) + // req_proc_fetch is used by both SP and EB but EB doesn't generate EOS parameter + if (!(request->req_flags & req_proc_select)) return statement; const CompoundStmtNode* list = nodeAs(parentStmt); @@ -9473,30 +9456,27 @@ const StmtNode* SuspendNode::execute(thread_db* tdbb, Request* request, ExeState { list = nodeAs(statement); - if (list && list->onlyAssignments && list->statements.hasData()) + if (list && list->onlyAssignments && list->statements.getCount() > 1) { - // This is the assignment that sets the EOS parameter. + // This should be the assignment that sets the old-fashioned EOS parameter. const AssignmentNode* assign = static_cast( list->statements[list->statements.getCount() - 1].getObject()); EXE_assignment(tdbb, assign); + + // Perform normal return but without stopping of looper because request in this case is expected to finish + // execution till the end. + request->req_operation = Request::req_return; + request->req_message = message; // Just in case if it has been changed in-between + return parentStmt; } - else - return statement; } - else - return statement; - - // fall into + return statement; } 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; + // Inner nodes finished and there is nothing to do anymore so we can return as well + request->req_message = message; // Just in case if it has been changed in-between + request->req_flags |= req_stall; // but still looper must stop processing nodes because who knows what code is after SUSPEND... return parentStmt; default: @@ -10511,66 +10491,6 @@ static void dsqlExplodeFields(dsql_rel* relation, Array >& fields, } } -// Find dbkey for named relation in statement's saved dbkeys. -static dsql_par* dsqlFindDbKey(const DsqlDmlStatement* statement, const RelationSourceNode* relation_name) -{ - DEV_BLKCHK(relation_name, dsql_type_nod); - - const dsql_msg* message = statement->getReceiveMsg(); - dsql_par* candidate = NULL; - const MetaName& relName = relation_name->dsqlName; - - for (FB_SIZE_T i = 0; i < message->msg_parameters.getCount(); ++i) - { - dsql_par* parameter = message->msg_parameters[i]; - - if (parameter->par_dbkey_relname.hasData() && parameter->par_dbkey_relname == relName) - { - if (candidate) - return NULL; - - candidate = parameter; - } - } - - return candidate; -} - -// Find record version for relation in statement's saved record version. -static dsql_par* dsqlFindRecordVersion(const DsqlDmlStatement* statement, const RelationSourceNode* relation_name) -{ - const dsql_msg* message = statement->getReceiveMsg(); - dsql_par* candidate = NULL; - const MetaName& relName = relation_name->dsqlName; - - for (FB_SIZE_T i = 0; i < message->msg_parameters.getCount(); ++i) - { - dsql_par* parameter = message->msg_parameters[i]; - - if (parameter->par_rec_version_relname.hasData() && - parameter->par_rec_version_relname == relName) - { - if (candidate) - return NULL; - - candidate = parameter; - } - } - - return candidate; -} - -// Generate assignment to EOF parameter. -static void dsqlGenEofAssignment(DsqlCompilerScratch* dsqlScratch, SSHORT value) -{ - dsc valueDesc; - valueDesc.makeShort(0, &value); - - dsqlScratch->appendUChar(blr_assignment); - LiteralNode::genConstant(dsqlScratch, &valueDesc, false); - GEN_parameter(dsqlScratch, dsqlScratch->getDsqlStatement()->getEof()); -} - static void dsqlGenReturning(DsqlCompilerScratch* dsqlScratch, ReturningClause* returning, std::optional localTableNumber) { @@ -10621,8 +10541,6 @@ static void dsqlGenReturning(DsqlCompilerScratch* dsqlScratch, ReturningClause* static void dsqlGenReturningLocalTableCursor(DsqlCompilerScratch* dsqlScratch, ReturningClause* returning, USHORT localTableNumber) { - dsqlGenEofAssignment(dsqlScratch, 1); - const USHORT localForContext = dsqlScratch->contextNumber++; dsqlScratch->appendUChar(blr_for); @@ -10652,10 +10570,6 @@ static void dsqlGenReturningLocalTableCursor(DsqlCompilerScratch* dsqlScratch, R } dsqlScratch->appendUChar(blr_end); - - dsqlScratch->appendUChar(blr_send); - dsqlScratch->appendUChar(dsqlScratch->getDsqlStatement()->getReceiveMsg()->msg_number); - dsqlGenEofAssignment(dsqlScratch, 0); } // Generate BLR for returning's local table declaration. @@ -10888,7 +10802,8 @@ static RseNode* dsqlPassCursorReference(DsqlCompilerScratch* dsqlScratch, const DEV_BLKCHK(dsqlScratch, dsql_type_req); thread_db* tdbb = JRD_get_thread_data(); - MemoryPool& pool = *tdbb->getDefaultPool(); + // Use scratch pool because none of created object is stored anywhere + MemoryPool& pool = dsqlScratch->getPool(); // Lookup parent dsqlScratch @@ -10902,26 +10817,54 @@ static RseNode* dsqlPassCursorReference(DsqlCompilerScratch* dsqlScratch, const Arg::Gds(isc_dsql_cursor_not_found) << cursor); } - auto parent = *symbol; + DsqlDmlRequest* parent = *symbol; // Verify that the cursor is appropriate and updatable - dsql_par* source = dsqlFindDbKey(parent->getDsqlStatement(), relation_name); - dsql_par* rv_source = dsqlFindRecordVersion(parent->getDsqlStatement(), relation_name); - - if (!source || !rv_source) + if (parent->getDsqlStatement()->getType() != DsqlStatement::TYPE_SELECT_UPD) { // cursor is not updatable ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-510) << Arg::Gds(isc_dsql_cursor_update_err) << cursor); } - const auto statement = static_cast(dsqlScratch->getDsqlStatement()); + // Check that it contains this relation name + Request* request = parent->getRequest(); + + const MetaName& relName = relation_name->dsqlName; + bool found = false; + for (FB_SIZE_T i = 0; i < request->req_rpb.getCount(); ++i) + { + jrd_rel* relation = request->req_rpb[i].rpb_relation; + + if (relation && relation->rel_name == relName) + { + if (found) + { + // Relation is used twice in cursor + ERRD_post(Arg::Gds(isc_dsql_cursor_err) + << Arg::Gds(isc_dsql_cursor_rel_ambiguous) << relName << cursor); + } + found = true; + } + } + + if (!found) + { + // Relation is not in cursor + ERRD_post(Arg::Gds(isc_dsql_cursor_err) + << Arg::Gds(isc_dsql_cursor_rel_not_found) << relName << cursor); + } - statement->setParentRequest(parent); - statement->setParentDbKey(source); - statement->setParentRecVersion(rv_source); - parent->cursors.add(statement); + DsqlStatement* stmt = dsqlScratch->getDsqlStatement(); + fb_assert(stmt->isDml()); + DsqlDmlStatement* dstmt = static_cast(stmt); + dstmt->parentCursorName = cursor; + + // Create side-channel message to deliver keys from parent cursor + dsql_msg* message = FB_NEW_POOL(pool) dsql_msg(pool); + message->msg_number = 2; // Magic numbers are not good but input and output messages used them so far + dsqlScratch->recordKeyMessage = message; // Build record selection expression @@ -10933,30 +10876,26 @@ static RseNode* dsqlPassCursorReference(DsqlCompilerScratch* dsqlScratch, const RecordKeyNode* dbKeyNode = FB_NEW_POOL(pool) RecordKeyNode(pool, blr_dbkey); dbKeyNode->dsqlRelation = relation_node; - dsql_par* parameter = MAKE_parameter(statement->getSendMsg(), false, false, 0, NULL); - statement->setDbKey(parameter); + RecordKeyNode* recVerNode = FB_NEW_POOL(pool) RecordKeyNode(pool, blr_record_version); + recVerNode->dsqlRelation = relation_node; + + // Order of parameters in resulting message is reversed + dsql_par* verParameter = MAKE_parameter(message, false, false, 0, NULL); + recVerNode->make(dsqlScratch, &verParameter->par_desc); + dsql_par* keyParameter = MAKE_parameter(message, false, false, 1, nullptr); + dbKeyNode->make(dsqlScratch, &keyParameter->par_desc); ParameterNode* paramNode = FB_NEW_POOL(pool) ParameterNode(pool); - paramNode->dsqlParameterIndex = parameter->par_index; - paramNode->dsqlParameter = parameter; - parameter->par_desc = source->par_desc; + paramNode->dsqlParameter = keyParameter; ComparativeBoolNode* eqlNode1 = FB_NEW_POOL(pool) ComparativeBoolNode(pool, blr_eql, dbKeyNode, paramNode); - dbKeyNode = FB_NEW_POOL(pool) RecordKeyNode(pool, blr_record_version); - dbKeyNode->dsqlRelation = relation_node; - - parameter = MAKE_parameter(statement->getSendMsg(), false, false, 0, NULL); - statement->setRecVersion(parameter); - paramNode = FB_NEW_POOL(pool) ParameterNode(pool); - paramNode->dsqlParameterIndex = parameter->par_index; - paramNode->dsqlParameter = parameter; - parameter->par_desc = rv_source->par_desc; + paramNode->dsqlParameter = verParameter; ComparativeBoolNode* eqlNode2 = - FB_NEW_POOL(pool) ComparativeBoolNode(pool, blr_eql, dbKeyNode, paramNode); + FB_NEW_POOL(pool) ComparativeBoolNode(pool, blr_eql, recVerNode, paramNode); rse->dsqlWhere = PASS1_compose(eqlNode1, eqlNode2, blr_and); @@ -11175,16 +11114,6 @@ static ReturningClause* dsqlProcessReturning(DsqlCompilerScratch* dsqlScratch, d } dsqlScratch->returningClause = node; - - if (!singleton) - { - // Set up parameter to handle EOF - auto parameter = MAKE_parameter(dsqlScratch->getDsqlStatement()->getReceiveMsg(), false, false, 0, nullptr); - dsqlScratch->getDsqlStatement()->setEof(parameter); - parameter->par_desc.dsc_dtype = dtype_short; - parameter->par_desc.dsc_scale = 0; - parameter->par_desc.dsc_length = sizeof(SSHORT); - } } } diff --git a/src/dsql/StmtNodes.h b/src/dsql/StmtNodes.h index c6cede470d9..e7bb97c150b 100644 --- a/src/dsql/StmtNodes.h +++ b/src/dsql/StmtNodes.h @@ -1114,11 +1114,24 @@ class MergeNode final : public TypedNode class MessageNode : public TypedNode { + struct MessageBuffer + { + const Format* format; // Message format derived from user MessageMetadata. Overrides default format. + UCHAR* buffer = nullptr; + }; + public: explicit MessageNode(MemoryPool& pool) - : TypedNode(pool), - itemsUsedInSubroutines(pool) + : TypedNode(pool) + { + } + // This constructor is temporary workaround for copying of existing format. + // For details look at comment in CMP_procedure_arguments() + explicit MessageNode(MemoryPool& pool, const Format& oldFormat) + : TypedNode(pool) { + format = Format::newFormat(pool, oldFormat.fmt_count); + *format = oldFormat; } public: @@ -1137,11 +1150,17 @@ class MessageNode : public TypedNode virtual MessageNode* pass2(thread_db* tdbb, CompilerScratch* csb); virtual const StmtNode* execute(thread_db* tdbb, Request* request, ExeState* exeState) const; + UCHAR* getBuffer(Request* request) const; + const Format* getFormat(const Request* request) const; + void setFormat(Request* request, Format* newFormat); + public: - Firebird::SortedArray itemsUsedInSubroutines; - NestConst format; ULONG impureFlags = 0; USHORT messageNumber = 0; + +private: + using StmtNode::impureOffset; // Made private to incapsulate it's interpretation logic + NestConst format; }; diff --git a/src/dsql/dsql.cpp b/src/dsql/dsql.cpp index a6b95506628..e27d52c8ce9 100644 --- a/src/dsql/dsql.cpp +++ b/src/dsql/dsql.cpp @@ -145,12 +145,6 @@ void DSQL_execute(thread_db* tdbb, const auto statement = dsqlRequest->getDsqlStatement(); - if (statement->getFlags() & DsqlStatement::FLAG_ORPHAN) - { - ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) << - Arg::Gds(isc_bad_req_handle)); - } - // Only allow NULL trans_handle if we're starting a transaction or set session properties if (!*tra_handle && diff --git a/src/dsql/dsql.h b/src/dsql/dsql.h index 422e36c3961..577797e184c 100644 --- a/src/dsql/dsql.h +++ b/src/dsql/dsql.h @@ -542,7 +542,6 @@ class dsql_msg : public Firebird::PermanentStorage Firebird::Array msg_parameters; // Parameter list USHORT msg_number = 0; // Message number - USHORT msg_buffer_number = 0; // Message buffer number (used instead of msg_number for blob msgs) ULONG msg_length = 0; // Message length USHORT msg_parameter = 0; // Next parameter number USHORT msg_index = 0; // Next index into SQLDA @@ -554,8 +553,6 @@ class dsql_par : public Firebird::PermanentStorage public: explicit dsql_par(MemoryPool& p) : PermanentStorage(p), - par_dbkey_relname(p), - par_rec_version_relname(p), par_name(p), par_rel_name(p), par_owner_name(p), @@ -567,9 +564,6 @@ class dsql_par : public Firebird::PermanentStorage dsql_msg* par_message = nullptr; // Parent message dsql_par* par_null = nullptr; // Null parameter, if used ValueExprNode* par_node = nullptr; // Associated value node, if any - dsql_ctx* par_context = nullptr; // Context for SELECT FOR UPDATE - MetaName par_dbkey_relname; // Context of internally requested dbkey - MetaName par_rec_version_relname; // Context of internally requested rec. version MetaName par_name; // Parameter name, if any MetaName par_rel_name; // Relation name, if any MetaName par_owner_name; // Owner name, if any diff --git a/src/dsql/gen.cpp b/src/dsql/gen.cpp index ece4e4dc2d5..a3718e44286 100644 --- a/src/dsql/gen.cpp +++ b/src/dsql/gen.cpp @@ -246,8 +246,6 @@ void GEN_port(DsqlCompilerScratch* dsqlScratch, dsql_msg* message) message->msg_length = offset; - dsqlScratch->getDsqlStatement()->getPorts().add(message); - // Remove gaps in par_index due to output parameters using question-marks (CALL syntax). USHORT parIndex = 0; diff --git a/src/jrd/ExtEngineManager.cpp b/src/jrd/ExtEngineManager.cpp index 8f28a59fcc3..0fb0377feea 100644 --- a/src/jrd/ExtEngineManager.cpp +++ b/src/jrd/ExtEngineManager.cpp @@ -195,7 +195,8 @@ namespace if (request->req_operation == Request::req_evaluate) { // Clear the message. This is important for external routines. - UCHAR* msg = request->getImpure(impureOffset); + fb_assert(MessageNode::getFormat(request)->fmt_length == format->fmt_length); + UCHAR* msg = getBuffer(request); memset(msg, 0, format->fmt_length); } @@ -212,7 +213,7 @@ namespace { public: InitParametersNode(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, - Array>& parameters, MessageNode* aMessage) + Array>& parameters, ExtMessageNode* aMessage) : TypedNode(pool), message(aMessage) { @@ -269,7 +270,7 @@ namespace { if (request->req_operation == Request::req_evaluate) { - const auto msg = request->getImpure(message->impureOffset); + const auto msg = message->getBuffer(request); const auto paramCount = defaultValuesNode->items.getCount(); for (unsigned paramIndex = 0; paramIndex < paramCount; ++paramIndex) @@ -304,7 +305,7 @@ namespace } private: - MessageNode* const message; + ExtMessageNode* const message; ValueListNode* defaultValuesNode; }; @@ -317,8 +318,9 @@ namespace : CompoundStmtNode(pool), checkMessageEof(aCheckMessageEof) { + const Format* format = fromMessage->getFormat(nullptr); // Iterate over the format items, except the EOF item. - for (unsigned i = 0; i < (fromMessage->format->fmt_count / 2) * 2; i += 2) + for (unsigned i = 0; i < (format->fmt_count / 2u) * 2u; i += 2) { auto flag = FB_NEW_POOL(pool) ParameterNode(pool); flag->messageNumber = fromMessage->messageNumber; @@ -356,8 +358,8 @@ namespace request->req_operation == Request::req_evaluate && (request->req_flags & req_proc_select)) { - const auto msg = request->getImpure(checkMessageEof->impureOffset); - const auto eof = (SSHORT*) (msg + (IPTR) checkMessageEof->format->fmt_desc.back().dsc_address); + const auto msg = checkMessageEof->getBuffer(request); + const auto eof = (SSHORT*) (msg + (IPTR) checkMessageEof->getFormat(request)->fmt_desc.back().dsc_address); if (!*eof) request->req_operation = Request::req_return; @@ -395,11 +397,11 @@ namespace { impure_state* const impure = request->getImpure(impureOffset); ExtEngineManager::ResultSet*& resultSet = request->req_ext_resultset; - UCHAR* extInMsg = extInMessageNode ? request->getImpure(extInMessageNode->impureOffset) : NULL; - UCHAR* extOutMsg = extOutMessageNode ? request->getImpure(extOutMessageNode->impureOffset) : NULL; - UCHAR* intOutMsg = intOutMessageNode ? request->getImpure(intOutMessageNode->impureOffset) : NULL; + UCHAR* extInMsg = extInMessageNode ? extInMessageNode->getBuffer(request) : NULL; + UCHAR* extOutMsg = extOutMessageNode ? extOutMessageNode->getBuffer(request) : NULL; + UCHAR* intOutMsg = intOutMessageNode ? intOutMessageNode->getBuffer(request) : NULL; SSHORT* eof = intOutMsg ? - (SSHORT*) (intOutMsg + (IPTR) intOutMessageNode->format->fmt_desc.back().dsc_address) : NULL; + (SSHORT*) (intOutMsg + (IPTR) intOutMessageNode->getFormat(request)->fmt_desc.back().dsc_address) : NULL; switch (request->req_operation) { diff --git a/src/jrd/Statement.cpp b/src/jrd/Statement.cpp index 3aac47c4d17..273a90c8dde 100644 --- a/src/jrd/Statement.cpp +++ b/src/jrd/Statement.cpp @@ -75,7 +75,8 @@ Statement::Statement(thread_db* tdbb, MemoryPool* p, CompilerScratch* csb) localTables(*p), invariants(*p), blr(*p), - mapFieldInfo(*p) + mapFieldInfo(*p), + messages(*p, csb->csb_rpt.getCount()) { try { @@ -200,6 +201,18 @@ Statement::Statement(thread_db* tdbb, MemoryPool* p, CompilerScratch* csb) tail->csb_fields = NULL; } + messages.grow(csb->csb_rpt.getCount()); + for (decltype(messages)::size_type i = 0; i < csb->csb_rpt.getCount(); ++i) + { + if (auto message = csb->csb_rpt[i].csb_message) + { + // When outer messages are mapped to inner just pointers are assigned so they keep original numbers inside. + // That's why this assert is commented out. + //fb_assert(i == message->messageNumber); + messages[i] = message; + } + } + if (csb->csb_variables) csb->csb_variables->clear(); @@ -896,6 +909,20 @@ void Statement::buildExternalAccess(thread_db* tdbb, ExternalAccessList& list, c } } +MessageNode* Statement::getMessage(USHORT messageNumber) const +{ + if (messageNumber >= messages.getCount()) + { + status_exception::raise(Arg::Gds(isc_badmsgnum)); + } + MessageNode* result = messages[messageNumber]; + if (result == nullptr) + { + status_exception::raise(Arg::Gds(isc_badmsgnum)); + } + return result; +} + // Make sub routines. template static void makeSubRoutines(thread_db* tdbb, Statement* statement, diff --git a/src/jrd/Statement.h b/src/jrd/Statement.h index da27d6ff88c..7edd82192c3 100644 --- a/src/jrd/Statement.h +++ b/src/jrd/Statement.h @@ -82,6 +82,8 @@ class Statement : public pool_alloc Firebird::string getPlan(thread_db* tdbb, bool detailed) const; void getPlan(thread_db* tdbb, PlanEntry& planEntry) const; + MessageNode* getMessage(USHORT messageNumber) const; + private: static void verifyTriggerAccess(thread_db* tdbb, jrd_rel* ownerRelation, TrigVector* triggers, MetaName userName); @@ -114,6 +116,9 @@ class Statement : public pool_alloc Firebird::RefStrPtr sqlText; // SQL text (encoded in the metadata charset) Firebird::Array blr; // BLR for non-SQL query MapFieldInfo mapFieldInfo; // Map field name to field info + +private: + Firebird::Array messages; // Input/output messages }; diff --git a/src/jrd/blb.cpp b/src/jrd/blb.cpp index 923161dc8b4..9c1089d2c76 100644 --- a/src/jrd/blb.cpp +++ b/src/jrd/blb.cpp @@ -1503,8 +1503,11 @@ blb* blb::open2(thread_db* tdbb, { if (!to_type_specified) to = isc_blob_text; + // Source is either a temporary BLOB already created by data coercion logic + // or a permanent blob which ID was delivered to client "as is" i.e. data coercion is not needed. + // In either case it is already in needed character set and further filtering is not needed if (!to_charset_specified) - to_charset = CS_dynamic; + to_charset = blob->blb_charset; } BLB_gen_bpb(from, to, from_charset, to_charset, new_bpb); diff --git a/src/jrd/cmp.cpp b/src/jrd/cmp.cpp index 87930ad8b64..75bb76257e1 100644 --- a/src/jrd/cmp.cpp +++ b/src/jrd/cmp.cpp @@ -490,9 +490,6 @@ bool CMP_procedure_arguments( csb->csb_msg_number = n = 2; const auto tail = CMP_csb_element(csb, n); - message = tail->csb_message = FB_NEW_POOL(pool) MessageNode(pool); - message->messageNumber = n; - /* dimitr: procedure (with its parameter formats) is allocated out of its own pool (prc_request->req_pool) and can be freed during the cache cleanup (MET_clear_cache). Since the current @@ -510,10 +507,9 @@ bool CMP_procedure_arguments( message->format = format; */ - const auto fmtCopy = Format::newFormat(pool, format->fmt_count); - *fmtCopy = *format; - message->format = fmtCopy; + message = tail->csb_message = FB_NEW_POOL(pool) MessageNode(pool, *format); // --- end of fix --- + message->messageNumber = n; const auto positionalArgCount = argNames ? argCount - argNames->getCount() : argCount; auto sourceArgIt = sources->items.begin(); diff --git a/src/jrd/err_proto.h b/src/jrd/err_proto.h index 742db1fdf3d..fe25cc0b215 100644 --- a/src/jrd/err_proto.h +++ b/src/jrd/err_proto.h @@ -53,7 +53,7 @@ void ERR_bugcheck_msg(const TEXT*); void ERR_soft_bugcheck(int, const TEXT*, int); void ERR_corrupt(int); void ERR_error(int); -void ERR_post(const Firebird::Arg::StatusVector& v); +[[noreturn]] void ERR_post(const Firebird::Arg::StatusVector& v); void ERR_post_nothrow(const Firebird::Arg::StatusVector& v, Jrd::FbStatusVector* statusVector = NULL); void ERR_post_nothrow(const Firebird::IStatus* v, Jrd::FbStatusVector* statusVector = NULL); void ERR_punt(); diff --git a/src/jrd/evl.cpp b/src/jrd/evl.cpp index 9eaa3f643f1..d3fedca597e 100644 --- a/src/jrd/evl.cpp +++ b/src/jrd/evl.cpp @@ -140,14 +140,14 @@ dsc* EVL_assign_to(thread_db* tdbb, const ValueExprNode* node) if (auto paramNode = nodeAs(node)) { + auto paramRequest = paramNode->getParamRequest(request); auto message = paramNode->message; auto arg_number = paramNode->argNumber; - auto desc = &message->format->fmt_desc[arg_number]; + auto desc = &message->getFormat(paramRequest)->fmt_desc[arg_number]; auto impure = request->getImpure(node->impureOffset); - impure->vlu_desc.dsc_address = paramNode->getParamRequest(request)->getImpure( - message->impureOffset + (IPTR) desc->dsc_address); + impure->vlu_desc.dsc_address = message->getBuffer(paramRequest) + (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; diff --git a/src/jrd/exe.cpp b/src/jrd/exe.cpp index 61eccec95dd..b0ec3770832 100644 --- a/src/jrd/exe.cpp +++ b/src/jrd/exe.cpp @@ -679,7 +679,9 @@ void EXE_receive(thread_db* tdbb, jrd_tra* transaction = request->req_transaction; if (!(request->req_flags & req_active)) - ERR_post(Arg::Gds(isc_req_sync)); + { + ERR_post(Arg::Gds(isc_req_sync) << Arg::Gds(isc_random) << Arg::Str("Receive from inactive request")); + } SavNumber savNumber = 0; @@ -710,63 +712,87 @@ void EXE_receive(thread_db* tdbb, try { - if (nodeIs(request->req_message)) + while ((request->req_flags & req_active) && request->req_operation != Request::req_send) + { + // Several reasons to get here: + // 1) Execution flow didn't advance since last req_send + // 2) StallNode has been encountered + // 3) Request needs to receive + // Just run execution skipping all StallNodes until the end, get needed state or encounter req_receive + + // This is obvious problem in blr logic + if (request->req_operation == Request::req_receive) + { + ERR_post(Arg::Gds(isc_req_sync) << Arg::Gds(isc_random) << Arg::Str("Request expected to send but need to receive")); + } + execute_looper(tdbb, request, transaction, request->req_next, Request::req_sync); + } - if (!(request->req_flags & req_active) || request->req_operation != Request::req_send) - ERR_post(Arg::Gds(isc_req_sync)); + if (request->req_flags & req_active) + { + const MessageNode* message = nodeAs(request->req_message); - const MessageNode* message = nodeAs(request->req_message); - const Format* format = message->format; + // Sanity checks first + if (message == nullptr || msg != message->messageNumber) + ERR_post(Arg::Gds(isc_req_sync) << Arg::Gds(isc_random) << Arg::Str("Request got wrong message")); - if (msg != message->messageNumber) - ERR_post(Arg::Gds(isc_req_sync)); + const Format* format = message->getFormat(request); + if (length != format->fmt_length) + ERR_post(Arg::Gds(isc_port_len) << Arg::Num(length) << Arg::Num(format->fmt_length)); - if (length != format->fmt_length) - ERR_post(Arg::Gds(isc_port_len) << Arg::Num(length) << Arg::Num(format->fmt_length)); + // To make sure that the buffer is allocated get it before calling of the looper + UCHAR* msgBuffer = message->getBuffer(request); - memcpy(buffer, request->getImpure(message->impureOffset), length); + // Proceed assignments then + execute_looper(tdbb, request, transaction, request->req_next, Request::req_proceed); - // ASF: temporary blobs returned to the client should not be released - // with the request, but in the transaction end. - if (top_level || transaction->tra_temp_blobs_count) - { - for (int i = 0; i < format->fmt_count; ++i) + // Make "dummy receive" simpler to allow pass nullptr as the output buffer + if (buffer != nullptr) { - const DSC* desc = &format->fmt_desc[i]; + // Copy data + memcpy(buffer, msgBuffer, length); - if (desc->isBlob()) + // ASF: temporary blobs returned to the client should not be released + // with the request, but in the transaction end. + if (top_level || transaction->tra_temp_blobs_count) { - const bid* id = (bid*) (static_cast(buffer) + (ULONG)(IPTR) desc->dsc_address); - - if (transaction->tra_blobs->locate(id->bid_temp_id())) + for (int i = 0; i < format->fmt_count; ++i) { - BlobIndex* current = &transaction->tra_blobs->current(); - - if (top_level && - current->bli_request && - current->bli_request->req_blobs.locate(id->bid_temp_id())) - { - current->bli_request->req_blobs.fastRemove(); - current->bli_request = NULL; - } + const DSC* desc = &format->fmt_desc[i]; - if (!current->bli_materialized && - (current->bli_blob_object->blb_flags & (BLB_close_on_read | BLB_stream)) == - (BLB_close_on_read | BLB_stream)) + if (desc->isBlob()) { - current->bli_blob_object->BLB_close(tdbb); + const bid* id = (bid*) (static_cast(buffer) + (ULONG)(IPTR) desc->dsc_address); + + if (transaction->tra_blobs->locate(id->bid_temp_id())) + { + BlobIndex* current = &transaction->tra_blobs->current(); + + if (top_level && + current->bli_request && + current->bli_request->req_blobs.locate(id->bid_temp_id())) + { + current->bli_request->req_blobs.fastRemove(); + current->bli_request = NULL; + } + + if (!current->bli_materialized && + (current->bli_blob_object->blb_flags & (BLB_close_on_read | BLB_stream)) == + (BLB_close_on_read | BLB_stream)) + { + current->bli_blob_object->BLB_close(tdbb); + } + } + else if (top_level) + { + transaction->checkBlob(tdbb, id, NULL, false); + } } } - else if (top_level) - { - transaction->checkBlob(tdbb, id, NULL, false); - } } } } - - execute_looper(tdbb, request, transaction, request->req_next, Request::req_proceed); } catch (const Exception&) { @@ -865,11 +891,22 @@ void EXE_send(thread_db* tdbb, Request* request, USHORT msg, ULONG length, const JRD_reschedule(tdbb); - if (!(request->req_flags & req_active)) - ERR_post(Arg::Gds(isc_req_sync)); + while ((request->req_flags & req_active) && request->req_operation != Request::req_receive) + { + // Several reasons to get here: + // 1) Execution flow didn't advance since last req_send + // 2) StallNode has been encountered + // Just run execution skipping all StallNodes until the end, get needed state or encounter req_send - if (request->req_operation != Request::req_receive) - ERR_post(Arg::Gds(isc_req_sync)); + // This is obvious problem in blr logic + if (request->req_operation == Request::req_send) + ERR_post(Arg::Gds(isc_req_sync) << Arg::Gds(isc_random) << Arg::Str("Request expected to receive but need to send")); + + execute_looper(tdbb, request, request->req_transaction, request->req_next, Request::req_sync); + } + + if (!(request->req_flags & req_active)) + ERR_post(Arg::Gds(isc_req_sync) << Arg::Gds(isc_random) << Arg::Str("Send data to inactive request")); const auto node = request->req_message; const StmtNode* message = nullptr; @@ -893,16 +930,20 @@ void EXE_send(thread_db* tdbb, Request* request, USHORT msg, ULONG length, const else BUGCHECK(167); // msg 167 invalid SEND request - const auto format = nodeAs(message)->format; + const auto messageNode = nodeAs(message); + const auto format = messageNode->getFormat(request); - if (msg != nodeAs(message)->messageNumber) + if (msg != messageNode->messageNumber) ERR_post(Arg::Gds(isc_req_sync)); if (length != format->fmt_length) ERR_post(Arg::Gds(isc_port_len) << Arg::Num(length) << Arg::Num(format->fmt_length)); - memcpy(request->getImpure(message->impureOffset), buffer, length); + // Set data buffer to read parameters from + UCHAR* msgBuffer = messageNode->getBuffer(request); + memcpy(msgBuffer, buffer, length); + // Process received data execute_looper(tdbb, request, request->req_transaction, request->req_next, Request::req_proceed); } @@ -916,7 +957,11 @@ static void activate_request(thread_db* tdbb, Request* request, jrd_tra* transac BLKCHK(transaction, type_tra); if (request->req_flags & req_active) - ERR_post(Arg::Gds(isc_req_sync) << Arg::Gds(isc_reqinuse)); + { + // Nothing special for old-style BLR which does EOS signalling by hand instead of checking request's activity + // Just kill previous incarnation for them. + EXE_unwind(tdbb, request); + } if (transaction->tra_flags & TRA_prepared) ERR_post(Arg::Gds(isc_req_no_trans)); diff --git a/src/jrd/inf.cpp b/src/jrd/inf.cpp index fd7b7eaa342..e0fef8f6836 100644 --- a/src/jrd/inf.cpp +++ b/src/jrd/inf.cpp @@ -1132,7 +1132,7 @@ ULONG INF_request_info(const Request* request, if (item == isc_info_message_number) length = INF_convert(node->messageNumber, buffer_ptr); else - length = INF_convert(node->format->fmt_length, buffer_ptr); + length = INF_convert(node->getFormat(request)->fmt_length, buffer_ptr); } else length = 0; diff --git a/src/jrd/jrd.cpp b/src/jrd/jrd.cpp index 0706bbeeb61..ed20c515633 100644 --- a/src/jrd/jrd.cpp +++ b/src/jrd/jrd.cpp @@ -4827,7 +4827,7 @@ void JAttachment::transactRequest(CheckStatusWrapper* user_status, ITransaction* if (in_msg_length) { - const ULONG len = inMessage ? inMessage->format->fmt_length : 0; + const ULONG len = inMessage ? inMessage->getFormat(request)->fmt_length : 0; if (in_msg_length != len) { @@ -4835,12 +4835,12 @@ void JAttachment::transactRequest(CheckStatusWrapper* user_status, ITransaction* Arg::Num(len)); } - memcpy(request->getImpure(inMessage->impureOffset), in_msg, in_msg_length); + memcpy(inMessage->getBuffer(request), in_msg, in_msg_length); } EXE_start(tdbb, request, transaction); - const ULONG len = outMessage ? outMessage->format->fmt_length : 0; + const ULONG len = outMessage ? outMessage->getFormat(request)->fmt_length : 0; if (out_msg_length != len) { @@ -4850,8 +4850,7 @@ void JAttachment::transactRequest(CheckStatusWrapper* user_status, ITransaction* if (out_msg_length) { - memcpy(out_msg, request->getImpure(outMessage->impureOffset), - out_msg_length); + memcpy(out_msg, outMessage->getBuffer(request), out_msg_length); } check_autocommit(tdbb, request); diff --git a/src/jrd/recsrc/ProcedureScan.cpp b/src/jrd/recsrc/ProcedureScan.cpp index a351d4a99d9..f95db6916b5 100644 --- a/src/jrd/recsrc/ProcedureScan.cpp +++ b/src/jrd/recsrc/ProcedureScan.cpp @@ -90,8 +90,8 @@ void ProcedureScan::internalOpen(thread_db* tdbb) const if (m_sourceList) { - iml = m_message->format->fmt_length; - im = request->getImpure(m_message->impureOffset); + iml = m_message->getFormat(request)->fmt_length; + im = m_message->getBuffer(request); const NestConst* const sourceEnd = m_sourceList->items.end(); const NestConst* sourcePtr = m_sourceList->items.begin(); diff --git a/src/jrd/req.h b/src/jrd/req.h index 673bb04bd44..c918c2b4e75 100644 --- a/src/jrd/req.h +++ b/src/jrd/req.h @@ -163,6 +163,14 @@ class AffectedRows int modifiedRows; }; +// Record key + +struct RecordKey +{ + RecordNumber::Packed recordNumber; + TraNumber recordVersion; +}; + // request block class Request : public pool_alloc @@ -465,6 +473,10 @@ class Request : public pool_alloc { return reinterpret_cast(&impureArea[offset]); } + template const T* getImpure(unsigned offset) const + { + return reinterpret_cast(&impureArea[offset]); + } void adjustCallerStats() { diff --git a/src/jrd/trace/TraceDSQLHelpers.h b/src/jrd/trace/TraceDSQLHelpers.h index 8561b321c37..47049038742 100644 --- a/src/jrd/trace/TraceDSQLHelpers.h +++ b/src/jrd/trace/TraceDSQLHelpers.h @@ -85,7 +85,7 @@ class TraceDSQLPrepare if ((result == ITracePlugin::RESULT_SUCCESS) && m_request) { - TraceSQLStatementImpl stmt(m_request, NULL); + TraceSQLStatementImpl stmt(m_request, nullptr, nullptr); TraceManager::event_dsql_prepare(m_attachment, m_transaction, &stmt, millis, result); } else @@ -111,16 +111,17 @@ class TraceDSQLPrepare class TraceDSQLExecute { public: - TraceDSQLExecute(Attachment* attachment, DsqlRequest* dsqlRequest) : + TraceDSQLExecute(Attachment* attachment, DsqlRequest* dsqlRequest, const UCHAR* data) : m_attachment(attachment), - m_dsqlRequest(dsqlRequest) + m_dsqlRequest(dsqlRequest), + m_data(data) { m_need_trace = m_dsqlRequest->req_traced && TraceManager::need_dsql_execute(m_attachment); if (!m_need_trace) return; { // scope - TraceSQLStatementImpl stmt(dsqlRequest, NULL); + TraceSQLStatementImpl stmt(dsqlRequest, nullptr, m_data); TraceManager::event_dsql_execute(m_attachment, dsqlRequest->req_transaction, &stmt, true, ITracePlugin::RESULT_SUCCESS); } @@ -156,7 +157,7 @@ class TraceDSQLExecute fb_utils::query_performance_counter() - m_start_clock, m_dsqlRequest->req_fetch_rowcount); - TraceSQLStatementImpl stmt(m_dsqlRequest, stats.getPerf()); + TraceSQLStatementImpl stmt(m_dsqlRequest, stats.getPerf(), m_data); TraceManager::event_dsql_execute(m_attachment, m_dsqlRequest->req_transaction, &stmt, false, result); m_dsqlRequest->req_fetch_baseline = NULL; @@ -172,6 +173,7 @@ class TraceDSQLExecute Attachment* const m_attachment; DsqlRequest* const m_dsqlRequest; SINT64 m_start_clock; + const UCHAR* m_data; }; class TraceDSQLFetch @@ -215,7 +217,7 @@ class TraceDSQLFetch &m_dsqlRequest->getRequest()->req_stats, m_dsqlRequest->req_fetch_elapsed, m_dsqlRequest->req_fetch_rowcount); - TraceSQLStatementImpl stmt(m_dsqlRequest, stats.getPerf()); + TraceSQLStatementImpl stmt(m_dsqlRequest, stats.getPerf(), nullptr); TraceManager::event_dsql_execute(m_attachment, m_dsqlRequest->req_transaction, &stmt, false, result); diff --git a/src/jrd/trace/TraceManager.cpp b/src/jrd/trace/TraceManager.cpp index dfbcadfe0dd..84edc6c4047 100644 --- a/src/jrd/trace/TraceManager.cpp +++ b/src/jrd/trace/TraceManager.cpp @@ -440,11 +440,11 @@ void TraceManager::event_dsql_execute(Attachment* att, jrd_tra* transaction, } void TraceManager::event_dsql_restart(Attachment* att, jrd_tra* transaction, - DsqlRequest* statement, int number) + DsqlRequest* statement, const UCHAR* data, int number) { TraceConnectionImpl conn(att); TraceTransactionImpl tran(transaction); - TraceSQLStatementImpl stmt(statement, NULL); + TraceSQLStatementImpl stmt(statement, nullptr, data); att->att_trace_manager->event_dsql_restart(&conn, transaction ? &tran : NULL, &stmt, (unsigned) number); diff --git a/src/jrd/trace/TraceManager.h b/src/jrd/trace/TraceManager.h index 047663d50e5..b0d389719e8 100644 --- a/src/jrd/trace/TraceManager.h +++ b/src/jrd/trace/TraceManager.h @@ -179,7 +179,7 @@ class TraceManager static void event_dsql_execute(Attachment* att, jrd_tra* transaction, Firebird::ITraceSQLStatement* statement, bool started, ntrace_result_t req_result); - static void event_dsql_restart(Attachment* att, jrd_tra* transaction, DsqlRequest* statement, + static void event_dsql_restart(Attachment* att, jrd_tra* transaction, DsqlRequest* statement, const UCHAR* data, int number); static void shutdown(); diff --git a/src/jrd/trace/TraceObjects.cpp b/src/jrd/trace/TraceObjects.cpp index dc6bab88895..2ed1ba0709f 100644 --- a/src/jrd/trace/TraceObjects.cpp +++ b/src/jrd/trace/TraceObjects.cpp @@ -221,48 +221,49 @@ ITraceParams* TraceSQLStatementImpl::getInputs() void TraceSQLStatementImpl::DSQLParamsImpl::fillParams() { - if (m_descs.getCount() || !m_params || m_params->getCount() == 0) + if (m_descs.getCount() || !m_buffer) return; - if (!m_stmt->getDsqlStatement()->isDml()) + auto stmt = m_stmt->getDsqlStatement(); + if (!stmt->isDml()) { - fb_assert(false); return; } - const auto dmlRequest = (DsqlDmlRequest*) m_stmt; + dsql_msg* msg = stmt->getSendMsg(); + if (!msg) + return; + + const auto params = msg->msg_parameters; + if (params.getCount() == 0) + return; + + const Request* req = m_stmt->getRequest(); + const Format* fmt = stmt->getStatement()->getMessage(msg->msg_number)->getFormat(req); - USHORT first_index = 0; - for (FB_SIZE_T i = 0 ; i < m_params->getCount(); ++i) + for (FB_SIZE_T i = 0 ; i < params.getCount(); ++i) { - const dsql_par* parameter = (*m_params)[i]; + const dsql_par* parameter = params[i]; if (parameter->par_index) { - // Use descriptor for nulls signaling - USHORT null_flag = 0; - if (parameter->par_null) - { - const UCHAR* msgBuffer = - dmlRequest->req_msg_buffers[parameter->par_null->par_message->msg_buffer_number]; - - if (*(SSHORT*) (msgBuffer + (IPTR) parameter->par_null->par_desc.dsc_address)) - null_flag = DSC_null; - } - - dsc* desc = NULL; - const FB_SIZE_T idx = parameter->par_index - 1; if (idx >= m_descs.getCount()) m_descs.getBuffer(idx + 1); - desc = &m_descs[idx]; + dsc& desc = m_descs[idx]; - *desc = parameter->par_desc; - desc->dsc_flags |= null_flag; - - UCHAR* msgBuffer = dmlRequest->req_msg_buffers[parameter->par_message->msg_buffer_number]; - desc->dsc_address = msgBuffer + (IPTR) desc->dsc_address; + desc = fmt->fmt_desc[parameter->par_parameter]; + // Use descriptor for nulls signaling + if (parameter->par_null) + { + if (*(SSHORT*) (m_buffer + (IPTR) fmt->fmt_desc[parameter->par_null->par_parameter].dsc_address)) + desc.dsc_flags |= DSC_null; + } + // Even if plugin try to change data in buffer (which is pointless) + // most likely it is safe because client buffer is writeble though + // in EXE_send() it is declared as const. + desc.dsc_address = const_cast(m_buffer) + (IPTR) desc.dsc_address; } } } @@ -412,13 +413,12 @@ void TraceDscFromValues::fillParams() { //const impure_value* impure = m_request->getImpure(param->impureOffset) const MessageNode* message = param->message; - const Format* format = message->format; + const Format* format = message->getFormat(m_request); const int arg_number = param->argNumber; desc = format->fmt_desc[arg_number]; from_desc = &desc; - desc.dsc_address = m_request->getImpure( - message->impureOffset + (IPTR) desc.dsc_address); + desc.dsc_address = message->getBuffer(m_request) + (IPTR) desc.dsc_address; // handle null flag if present if (param->argFlag) diff --git a/src/jrd/trace/TraceObjects.h b/src/jrd/trace/TraceObjects.h index 31c70778482..f6ead0925e7 100644 --- a/src/jrd/trace/TraceObjects.h +++ b/src/jrd/trace/TraceObjects.h @@ -221,11 +221,11 @@ class TraceSQLStatementImpl : public StatementHolder { public: - TraceSQLStatementImpl(DsqlRequest* stmt, Firebird::PerformanceInfo* perf) : + TraceSQLStatementImpl(DsqlRequest* stmt, Firebird::PerformanceInfo* perf, const UCHAR* inputBuffer) : StatementHolder(stmt ? stmt->getStatement() : nullptr), m_stmt(stmt), m_perf(perf), - m_inputs(stmt) + m_inputs(stmt, inputBuffer) {} // TraceSQLStatement implementation @@ -250,11 +250,9 @@ class TraceSQLStatementImpl : public Firebird::AutoIface > { public: - explicit DSQLParamsImpl(DsqlRequest* const stmt) : - m_stmt(stmt) + explicit DSQLParamsImpl(DsqlRequest* const stmt, const UCHAR* const inputBuffer) : + m_stmt(stmt), m_buffer(inputBuffer) { - if (const auto msg = m_stmt->getDsqlStatement()->getSendMsg()) - m_params = &msg->msg_parameters; } FB_SIZE_T getCount(); @@ -265,7 +263,7 @@ class TraceSQLStatementImpl : void fillParams(); DsqlRequest* const m_stmt; - const Firebird::Array* m_params = nullptr; + const UCHAR* m_buffer; Firebird::HalfStaticArray m_descs; Firebird::string temp_utf8_text; };