From c9c8add1ce230706801c231a8b9ed6d51f0289bf Mon Sep 17 00:00:00 2001 From: AlexPeshkoff Date: Fri, 14 Aug 2026 21:18:15 +0300 Subject: [PATCH 01/30] First variant of depends tracking - works only for procedures --- src/common/classes/array.h | 2 +- src/dsql/DsqlRequests.cpp | 2 + src/jrd/CacheVector.h | 134 +++++++++++++++++-------------------- src/jrd/CharSetContainer.h | 2 + src/jrd/Function.h | 2 +- src/jrd/Package.h | 4 +- src/jrd/Relation.h | 6 ++ src/jrd/Routine.cpp | 7 ++ src/jrd/Routine.h | 4 +- src/jrd/dfw.epp | 47 +++++++++++-- src/jrd/met.epp | 49 ++++++++++++++ src/jrd/met.h | 2 +- src/jrd/tra.cpp | 100 +++++++++++++++++++++++++++ src/jrd/tra.h | 27 +++++++- 14 files changed, 304 insertions(+), 84 deletions(-) diff --git a/src/common/classes/array.h b/src/common/classes/array.h index 897474fbb27..c6ac9a268fe 100644 --- a/src/common/classes/array.h +++ b/src/common/classes/array.h @@ -169,7 +169,6 @@ class Array : public Storage count = 0; } -protected: const T& getElement(size_type index) const noexcept { fb_assert(index < count); @@ -182,6 +181,7 @@ class Array : public Storage return data[index]; } +protected: void freeData() noexcept { // CVC: Warning, after this call, "data" is an invalid pointer, be sure to reassign it diff --git a/src/dsql/DsqlRequests.cpp b/src/dsql/DsqlRequests.cpp index 54e13e8064a..26a7f059efe 100644 --- a/src/dsql/DsqlRequests.cpp +++ b/src/dsql/DsqlRequests.cpp @@ -553,6 +553,8 @@ void DsqlDmlRequest::doExecute(thread_db* tdbb, jrd_tra** traHandle, firstRowFetched = false; const dsql_msg* message = dsqlStatement->getSendMsg(); + req_transaction->processUpdates(tdbb); + if (!message) { JRD_start(tdbb, request, req_transaction); diff --git a/src/jrd/CacheVector.h b/src/jrd/CacheVector.h index 87b0ca72434..5cc90157a04 100644 --- a/src/jrd/CacheVector.h +++ b/src/jrd/CacheVector.h @@ -40,6 +40,7 @@ #include "../jrd/constants.h" #include "../jrd/tra_proto.h" #include "../jrd/QualifiedName.h" +#include "../jrd/obj.h" namespace Jrd { @@ -87,6 +88,13 @@ class ElementBase virtual ~ElementBase(); virtual void cleanup(thread_db* tdbb) = 0; + virtual ObjectType getObjectType() = 0; + virtual void getObjectName(QualifiedName& name) = 0; + virtual void newVersion(thread_db* tdbb) = 0; + virtual bool ensureVersioned(thread_db* tdbb, ObjectBase::Flag fl) = 0; + virtual void makeRequests(thread_db* tdbb) = 0; + virtual void commit(thread_db* tdbb, TraNumber curNumber = 0) = 0; + virtual MdcVersion getVersion(thread_db* tdbb) = 0; public: [[noreturn]] void busyError(thread_db* tdbb, MetaId id, const char* family); @@ -99,6 +107,9 @@ class ElementBase return locked; } + // fill dependencies info + void fillDeps(thread_db* tdbb); + private: Lock* lock = nullptr; std::atomic locked = false; @@ -163,11 +174,17 @@ class ListEntry : public HazardObject public: enum State { INITIAL, RELOAD, MISSING, SCANNING, READY }; - ListEntry(Versioned* object, TraNumber traNumber, ObjectBase::Flag fl, ListEntry* link = nullptr) + ListEntry(thread_db* tdbb, Versioned* object, TraNumber traNumber, ObjectBase::Flag fl, ListEntry* link = nullptr) : object(object), traNumber(traNumber), cacheFlags(fl), state(INITIAL) { if (fl & CacheFlag::ERASED) fb_assert(!object); + + // Handle front & back versions of MDC + VersionIncr incr(tdbb); + version = incr.getVersion(); + + // Add to linked list if (link) next.store(link); } @@ -550,19 +567,9 @@ class ListEntry : public HazardObject return state == READY ? false : (thd == Thread::getCurrentThreadId()) && (state == SCANNING); } - static bool upgradable(HazardPtr& listEntry, const Versioned* from) + MdcVersion getVersion() { - for (; listEntry; listEntry.set(listEntry->next)) - { - if (listEntry->object == from) - return false; // not found upgrade version - - if (listEntry->getFlags() & CacheFlag::COMMITTED) - return true; // already upgraded by someone else - } - - fb_assert(false); - return false; // miss from what to upgrade + return version; } private: @@ -582,7 +589,6 @@ class ListEntry : public HazardObject TraNumber traNumber; // when COMMITTED not set - stores transaction that created this list element // when COMMITTED is set - stores transaction after which older elements are not needed // traNumber to be changed BEFORE setting COMMITTED - MdcVersion version = 0; // version of metadata cache when object was added ThreadId thd = 0; // thread that performs object scan() std::atomic cacheFlags; @@ -677,6 +683,11 @@ class CacheElement : public ElementBase, public P return getVersioned(tdbb, TransactionNumber::current(tdbb), fl); } + bool ensureVersioned(thread_db* tdbb, ObjectBase::Flag fl) override + { + return getVersioned(tdbb, fl); + } + bool isReady(thread_db* tdbb) { auto entry = getEntry(tdbb, TransactionNumber::current(tdbb), CacheFlag::NOSCAN | CacheFlag::NOCOMMIT); @@ -748,7 +759,7 @@ class CacheElement : public ElementBase, public P ListEntry* newEntry = nullptr; try { - newEntry = FB_NEW ListEntry(obj, traNum, fl & ~CacheFlag::ERASED); + newEntry = FB_NEW ListEntry(tdbb, obj, traNum, fl & ~CacheFlag::ERASED); } catch (const Firebird::Exception&) { @@ -776,6 +787,7 @@ class CacheElement : public ElementBase, public P return listEntry; // nullptr } + toUpdatedList(tdbb, fl); return HazardPtr>(newEntry); } @@ -784,7 +796,9 @@ class CacheElement : public ElementBase, public P fb_assert(list.load()); listEntry = list; } + fl &= ~CacheFlag::AUTOCREATE; + toUpdatedList(tdbb, fl); return ListEntry::getEntry(tdbb, listEntry, traNum, fl, this); } @@ -812,7 +826,7 @@ class CacheElement : public ElementBase, public P if (!cur) cur = TransactionNumber::current(tdbb); - ListEntry* newEntry = FB_NEW ListEntry(obj, cur, fl); + ListEntry* newEntry = FB_NEW ListEntry(tdbb, obj, cur, fl); if (!ListEntry::add(tdbb, list, newEntry)) { newEntry->cleanup(tdbb, false); @@ -867,15 +881,15 @@ class CacheElement : public ElementBase, public P return nullptr; } - void commit(thread_db* tdbb, TraNumber cur = 0) + void commit(thread_db* tdbb, TraNumber curNumber = 0) override { HazardPtr> current(list); if (current) { - if (!cur) - cur = TransactionNumber::current(tdbb); + if (!curNumber) + curNumber = TransactionNumber::current(tdbb); - auto flags = current->commit(tdbb, cur, TransactionNumber::next(tdbb)); + auto flags = current->commit(tdbb, curNumber, TransactionNumber::next(tdbb)); if (flags & CacheFlag::NOCOMMIT) // Committed newly created version in cache pingLock(tdbb, flags, this->getId(), Versioned::objectFamily(this)); @@ -938,12 +952,17 @@ class CacheElement : public ElementBase, public P return listEntry->scanInProgress(); } - static int getObjectType() + ObjectType getObjectType() override { return Versioned::objectType(); } - void newVersion(thread_db* tdbb) + void getObjectName(QualifiedName& name) override + { + name = this->getName(); + } + + void newVersion(thread_db* tdbb) override { TraNumber traNum; @@ -967,41 +986,25 @@ class CacheElement : public ElementBase, public P } } - bool upgrade(thread_db* tdbb, const Versioned* from) + bool nameIs(const QualifiedName& name) { - HazardPtr> l(list); - - // list of versions should be present - fb_assert(l); - if (!l) - return false; - - // if there is another version at the top nothing to be added - if (l->getVersioned() != from) - return ListEntry::upgradable(l, from); - - // we have candidate for upgrade - make sure it's not half-done - fb_assert(l->getFlags() & CacheFlag::COMMITTED); - if (!(l->getFlags() & CacheFlag::COMMITTED)) - return false; - - // Try to upgrade - ListEntry* newEntry = FB_NEW ListEntry(nullptr, TransactionNumber::current(tdbb), - CacheFlag::COMMITTED | CacheFlag::MINISCAN | CacheFlag::DB_VERSION, l.getPointer()); - if (l.replace(list, newEntry)) - return true; + return this->getName() == name; + } - // undo changes - delete newEntry; + // This is needed to check correctness of statements present in current object's version + void makeRequests(thread_db* tdbb) override + { + Versioned* v = getVersioned(tdbb, CacheFlag::AUTOCREATE); + if (!v) + return; - // Someone already added entry - see is it OK for us - l.set(list); - return ListEntry::upgradable(l, from); + v->makeRequests(tdbb); } - bool nameIs(const QualifiedName& name) + MdcVersion getVersion(thread_db* tdbb) override { - return this->getName() == name; + auto entry = getEntry(tdbb, TransactionNumber::current(tdbb), CacheFlag::NOSCAN | CacheFlag::NOCOMMIT); + return entry ? entry->getVersion() : 0; } private: @@ -1011,6 +1014,13 @@ class CacheElement : public ElementBase, public P atomics::memory_order_release, atomics::memory_order_relaxed); } + // Check flags and may be fill dependencies info + void toUpdatedList(thread_db* tdbb, ObjectBase::Flag fl) + { + if (fl & CacheFlag::DEPENDS) + ElementBase::fillDeps(tdbb); + } + private: std::atomic*> list = nullptr; std::atomic resetAt = 0; @@ -1211,28 +1221,6 @@ class CacheVector : public Firebird::PermanentStorage return data; } - bool upgrade(thread_db* tdbb, MetaId id, const Versioned* from) - { - fb_assert(id < getCount()); - - if (id < getCount()) - { - auto ptr = getDataPointer(id); - fb_assert(ptr); - - if (ptr) - { - StoredElement* data = ptr->load(atomics::memory_order_acquire); - fb_assert(data); - - if (data) - return data->upgrade(tdbb, from); - } - } - - return false; - } - bool lookup(thread_db* tdbb, const QualifiedName& name, ObjectBase::Flag fl, StoredElement** element, Versioned** versioned) { diff --git a/src/jrd/CharSetContainer.h b/src/jrd/CharSetContainer.h index df397fdb249..d25440cd251 100644 --- a/src/jrd/CharSetContainer.h +++ b/src/jrd/CharSetContainer.h @@ -134,6 +134,8 @@ class CharSetVers final : public ObjectBase static ObjectType objectType() noexcept; + void makeRequests(thread_db* tdbb) { } + bool hash(thread_db*, Firebird::sha512&) { return true; diff --git a/src/jrd/Function.h b/src/jrd/Function.h index 1f14c5c8a8c..d842ba5dc16 100644 --- a/src/jrd/Function.h +++ b/src/jrd/Function.h @@ -71,7 +71,7 @@ namespace Jrd } public: - int getObjectType() const noexcept override + ObjectType getObjectType() const noexcept override { return objectType(); } diff --git a/src/jrd/Package.h b/src/jrd/Package.h index 6d54bda85c4..036f614f83b 100644 --- a/src/jrd/Package.h +++ b/src/jrd/Package.h @@ -214,7 +214,7 @@ class Package final : public Firebird::PermanentStorage, public ObjectBase return getPermanent()->id; } - int getObjectType() const noexcept + ObjectType getObjectType() const noexcept { return objectType(); } @@ -226,6 +226,8 @@ class Package final : public Firebird::PermanentStorage, public ObjectBase static ObjectType objectType() noexcept; + void makeRequests(thread_db* tdbb) {/*!!!!!!!!!!!!!!!*/} + bool hash(thread_db* tdbb, Firebird::sha512& digest); Cached::Package* getPermanent() const noexcept diff --git a/src/jrd/Relation.h b/src/jrd/Relation.h index 28f66c8a73b..aed4f7d8a08 100644 --- a/src/jrd/Relation.h +++ b/src/jrd/Relation.h @@ -254,6 +254,8 @@ class DbTriggers final : public Triggers, public ObjectBase static ObjectType objectType() noexcept; + void makeRequests(thread_db* tdbb) {/*!!!!!!!!!!!!!!!*/} + private: DbTriggersHeader* perm; @@ -565,6 +567,8 @@ class IndexVersion final : public ObjectBase static std::optional getIdByName(thread_db* tdbb, ExName name); static ObjectType objectType() noexcept; + void makeRequests(thread_db* tdbb) {/*!!!!!!!!!!!!!!!*/} + ScanResult reload(thread_db* tdbb, ObjectBase::Flag flags) { return scan(tdbb, flags); @@ -693,6 +697,8 @@ class jrd_rel final : public ObjectBase static const char* objectFamily(RelationPermanent* perm); static ObjectType objectType() noexcept; + void makeRequests(thread_db* tdbb) {/*!!!!!!!!!!!!!!!*/} + void releaseTriggers(thread_db* tdbb, bool destroy); const Trigger* findTrigger(const QualifiedName& trig_name) const; const Format* currentFormat(thread_db* tdbb); diff --git a/src/jrd/Routine.cpp b/src/jrd/Routine.cpp index e32096996e9..2b2e6c12403 100644 --- a/src/jrd/Routine.cpp +++ b/src/jrd/Routine.cpp @@ -233,6 +233,13 @@ void Routine::parseMessages(thread_db* tdbb, CompilerScratch* csb, BlrReader blr } } +void Routine::makeRequests(thread_db* tdbb) +{ + auto *req = statement->findRequest(tdbb); + if (req) + req->setUnused(); +} + bool Routine::hash(thread_db* tdbb, Firebird::sha512& digest) { if (inputFields.hasData()) diff --git a/src/jrd/Routine.h b/src/jrd/Routine.h index acbdf75466d..2f23608c47a 100644 --- a/src/jrd/Routine.h +++ b/src/jrd/Routine.h @@ -169,9 +169,11 @@ namespace Jrd void sharedCheckUnlock(thread_db* tdbb); + void makeRequests(thread_db* tdbb); + public: virtual RoutinePermanent* getPermanent() const noexcept = 0; // Permanent part of data - virtual int getObjectType() const noexcept = 0; + virtual ObjectType getObjectType() const noexcept = 0; virtual SLONG getSclType() const noexcept = 0; private: diff --git a/src/jrd/dfw.epp b/src/jrd/dfw.epp index 2c289fee6b2..c9aad867061 100644 --- a/src/jrd/dfw.epp +++ b/src/jrd/dfw.epp @@ -421,6 +421,7 @@ static bool clear_cache(thread_db*, SSHORT, DeferredWork*, jrd_tra*); static bool change_repl_state(thread_db*, SSHORT, DeferredWork*, jrd_tra*); static bool set_statistics(thread_db*, SSHORT, DeferredWork*, jrd_tra*); static bool deps_to_disk(thread_db*, SSHORT, DeferredWork*, jrd_tra*); +static bool update_dependencies(thread_db*, SSHORT, DeferredWork*, jrd_tra*); // ---------------------------------------------------------------- @@ -1228,6 +1229,7 @@ static inline constexpr deferred_task task_table[] = { dfw_modify_package_constant, createOrAlterConstant }, { dfw_delete_package_constant, deleteConstant }, { dfw_create_package, createPackage }, + { dfw_update_dependencies, update_dependencies }, // must be last { dfw_null, NULL } }; @@ -3473,6 +3475,46 @@ static bool delete_collation(thread_db* tdbb, SSHORT phase, DeferredWork* work, } +static bool update_dependencies(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction) +{ +/******************************************* + * + * u p d a t e _ d e p e n d e n c i e s + * + ******************************************* + * + * Functional description + * Update dependent from changed in this transaction objects + * and commit them at specific DFW phase. + * + **************************************/ + SET_TDBB(tdbb); + + switch (phase) + { + case 0: + return false; + + case 1: + case 2: + case 3: + case 4: + case 5: + return true; + + case 6: + transaction->processUpdates(tdbb); + return true; + + case 7: + transaction->processCommits(tdbb); + break; + } + + return false; +} + + static bool delete_parameter(thread_db* tdbb, SSHORT phase, DeferredWork*, jrd_tra*) { /************************************** @@ -3530,12 +3572,7 @@ static bool create_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_ * Create a new index or change the state of an index between active/inactive. * **************************************/ - jrd_rel* relation = nullptr; - SET_TDBB(tdbb); - Jrd::Attachment* attachment = tdbb->getAttachment(); - Database* dbb = tdbb->getDatabase(); - AutoRequest request; switch (phase) { diff --git a/src/jrd/met.epp b/src/jrd/met.epp index f0c1f1fcfe5..453b49ca4f4 100644 --- a/src/jrd/met.epp +++ b/src/jrd/met.epp @@ -5656,3 +5656,52 @@ jrd_rel* MetadataCache::getLtt(thread_db* tdbb, const QualifiedName& name) return nullptr; } + + +void ElementBase::fillDeps(thread_db* tdbb) +{ + auto* attachment = tdbb->getAttachment(); + auto* metaTransaction = attachment->getMetaTransaction(tdbb); + auto* transaction = tdbb->getTransaction(); + + ObjectType objType = getObjectType(); + QualifiedName name; + getObjectName(name); + + MetaName object = name.object; + if (name.package.hasData()) + { + objType = obj_package_header; + object = name.package; + } + + AUTO_HANDLE(hndl); + + FOR (REQUEST_HANDLE hndl TRANSACTION_HANDLE metaTransaction) + D IN RDB$DEPENDENCIES + WITH D.RDB$DEPENDED_ON_TYPE EQ objType + AND D.RDB$DEPENDED_ON_SCHEMA_NAME EQ name.schema.c_str() + AND D.RDB$DEPENDED_ON_NAME EQ object.c_str() + REDUCED TO D.RDB$DEPENDENT_TYPE, D.RDB$DEPENDENT_SCHEMA_NAME, D.RDB$PACKAGE_NAME, D.RDB$DEPENDENT_NAME + { + switch (D.RDB$DEPENDENT_TYPE) + { + case obj_procedure: + { + QualifiedName name; + name.schema = D.RDB$DEPENDENT_SCHEMA_NAME; + if (!D.RDB$PACKAGE_NAME.NULL) + name.package = D.RDB$PACKAGE_NAME; + name.object = D.RDB$DEPENDENT_NAME; + + transaction->storeUpdate(MetadataCache::getPerm(tdbb, name, CacheFlag::AUTOCREATE)); + } + break; + + default: + break; + } + } + END_FOR +} + diff --git a/src/jrd/met.h b/src/jrd/met.h index cbbb1b6bb90..2a15d322ade 100644 --- a/src/jrd/met.h +++ b/src/jrd/met.h @@ -141,7 +141,7 @@ class jrd_prc : public Routine } public: - int getObjectType() const noexcept override + ObjectType getObjectType() const noexcept override { return obj_procedure; } diff --git a/src/jrd/tra.cpp b/src/jrd/tra.cpp index a2576184ed2..5a5bfef38c1 100644 --- a/src/jrd/tra.cpp +++ b/src/jrd/tra.cpp @@ -4453,3 +4453,103 @@ void jrd_tra::eraseSecDbContext() noexcept delete tra_sec_db_context; tra_sec_db_context = NULL; } + +void jrd_tra::storeUpdate(ElementBase* obj) +{ + if (!accumulatedDeps) + { + accumulatedDeps = FB_NEW_POOL(getPool()) Deps(getPool()); + DFW_post_work(this, dfw_update_dependencies, nullptr, nullptr, 0u); + } + + if (!accumulatedDeps->locate(obj)) + accumulatedDeps->add(obj); +} + +void jrd_tra::storeCommit(ElementBase* obj) +{ + if (!updateCommits) + updateCommits = FB_NEW_POOL(getPool()) UpdateCommits(getPool()); + + updateCommits->push(obj); +} + +bool jrd_tra::processUpdates(thread_db* tdbb) +{ + if (!(accumulatedDeps || processingDeps)) + return false; + + MdcVersion startingVersion; + { + VersionIncr incr(tdbb); + startingVersion = incr.getVersion(); + } + + while (accumulatedDeps || processingDeps) + { + // switch deps + if (!processingDeps) + processingDeps = accumulatedDeps.release(); + + // process updates + for(auto run = processingDeps->getFirst(); run; run = processingDeps->getNext()) + { + auto* elem = processingDeps->current(); + + // may be this element was already processed in this update? + if (elem->getVersion(tdbb) >= startingVersion) + continue; + + // now ask cache element to create all possible requests + try + { + elem->makeRequests(tdbb); + } + + // handle specific for outdated element error + catch (const status_exception& ex) + { + if (ex.value()[1] != isc_old_format) + throw; + tdbb->tdbb_status_vector->init(); + + // get all existing currently dependencies + elem->fillDeps(tdbb); + + // make new version and compile it + elem->newVersion(tdbb); + auto rc = elem->ensureVersioned(tdbb, 0); + fb_assert(rc); + + storeCommit(elem); + } + } + + // This portion of update is ready + delete processingDeps.release(); + } + + return true; +} + +void jrd_tra::processCommits(thread_db* tdbb) +{ + if (!updateCommits) + return; + + unsigned pos = 0; + try + { + for (pos = 0; pos < updateCommits->getCount(); pos++) + updateCommits->getElement(pos)->commit(tdbb); + } + catch(const Exception&) + { + // We do not expect exceptions in metacache commits - but + // already committed elements should better go away + if (pos > 0) + updateCommits->removeRange(0, pos); + + throw; + } +} diff --git a/src/jrd/tra.h b/src/jrd/tra.h index 0e06f7c3c07..bfe42cef80d 100644 --- a/src/jrd/tra.h +++ b/src/jrd/tra.h @@ -35,6 +35,8 @@ #include "../include/fb_blk.h" #include "../common/classes/tree.h" #include "../common/classes/GenericMap.h" +#include "../common/classes/auto.h" +#include "../common/classes/array.h" #include "../jrd/exe.h" #include "../jrd/rpb_chain.h" #include "../jrd/blb.h" // For bid structure @@ -430,6 +432,26 @@ class jrd_tra final : public pool_alloc // Finish and delete BulkInsert that belongs to the request void finiBulkInsert(thread_db* tdbb, Request* request); + + // Store an object to be updated + void storeUpdate(ElementBase* obj); + + // Store an object to be updated + void storeCommit(ElementBase* obj); + + // Process updates/commits accumulated by transaction + bool processUpdates(thread_db* tdbb); + void processCommits(thread_db* tdbb); + +private: + // Under processing and accumulated sets of dependencies + typedef Firebird::BePlusTree Deps; + Firebird::AutoPtr processingDeps; + Firebird::AutoPtr accumulatedDeps; + + // Set of updated objects to be committed + typedef Firebird::HalfStaticArray UpdateCommits; + Firebird::AutoPtr updateCommits; }; // System transaction is always transaction 0. @@ -574,7 +596,10 @@ enum dfw_t : int { dfw_delete_package_constant, // Package - dfw_create_package + dfw_create_package, + + // Update various objects dependent from modified in this transaction + dfw_update_dependencies }; } //namespace Jrd From 634f597cf790dc3345d9dc81880311d027e27275 Mon Sep 17 00:00:00 2001 From: AlexPeshkoff Date: Sat, 15 Aug 2026 20:46:13 +0300 Subject: [PATCH 02/30] When object did change same message format does not guarantee correct use of it in dependent objects --- src/jrd/CacheVector.h | 4 ++-- src/jrd/met.epp | 4 ++-- src/jrd/tra.cpp | 44 ++++++++++++++++++++++++++++--------------- src/jrd/tra.h | 6 +++--- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/jrd/CacheVector.h b/src/jrd/CacheVector.h index 5cc90157a04..24624329ebd 100644 --- a/src/jrd/CacheVector.h +++ b/src/jrd/CacheVector.h @@ -108,7 +108,7 @@ class ElementBase } // fill dependencies info - void fillDeps(thread_db* tdbb); + void fillDeps(thread_db* tdbb, bool forceRecompile); private: Lock* lock = nullptr; @@ -1018,7 +1018,7 @@ class CacheElement : public ElementBase, public P void toUpdatedList(thread_db* tdbb, ObjectBase::Flag fl) { if (fl & CacheFlag::DEPENDS) - ElementBase::fillDeps(tdbb); + ElementBase::fillDeps(tdbb, true); } private: diff --git a/src/jrd/met.epp b/src/jrd/met.epp index 453b49ca4f4..483b306303b 100644 --- a/src/jrd/met.epp +++ b/src/jrd/met.epp @@ -5658,7 +5658,7 @@ jrd_rel* MetadataCache::getLtt(thread_db* tdbb, const QualifiedName& name) } -void ElementBase::fillDeps(thread_db* tdbb) +void ElementBase::fillDeps(thread_db* tdbb, bool forceRecompile) { auto* attachment = tdbb->getAttachment(); auto* metaTransaction = attachment->getMetaTransaction(tdbb); @@ -5694,7 +5694,7 @@ void ElementBase::fillDeps(thread_db* tdbb) name.package = D.RDB$PACKAGE_NAME; name.object = D.RDB$DEPENDENT_NAME; - transaction->storeUpdate(MetadataCache::getPerm(tdbb, name, CacheFlag::AUTOCREATE)); + transaction->storeUpdate(MetadataCache::getPerm(tdbb, name, CacheFlag::AUTOCREATE), forceRecompile); } break; diff --git a/src/jrd/tra.cpp b/src/jrd/tra.cpp index 5a5bfef38c1..8e7c02628f4 100644 --- a/src/jrd/tra.cpp +++ b/src/jrd/tra.cpp @@ -4454,7 +4454,7 @@ void jrd_tra::eraseSecDbContext() noexcept tra_sec_db_context = NULL; } -void jrd_tra::storeUpdate(ElementBase* obj) +void jrd_tra::storeUpdate(ElementBase* obj, bool forceRecompile) { if (!accumulatedDeps) { @@ -4462,8 +4462,14 @@ void jrd_tra::storeUpdate(ElementBase* obj) DFW_post_work(this, dfw_update_dependencies, nullptr, nullptr, 0u); } - if (!accumulatedDeps->locate(obj)) - accumulatedDeps->add(obj); + bool* recompile = accumulatedDeps->get(obj); + if (recompile) + *recompile = forceRecompile || *recompile; + else + { + recompile = accumulatedDeps->put(obj); + *recompile = forceRecompile; + } } void jrd_tra::storeCommit(ElementBase* obj) @@ -4492,29 +4498,37 @@ bool jrd_tra::processUpdates(thread_db* tdbb) processingDeps = accumulatedDeps.release(); // process updates - for(auto run = processingDeps->getFirst(); run; run = processingDeps->getNext()) + for(auto iter : *processingDeps) { - auto* elem = processingDeps->current(); + auto* elem = iter.first; + bool use = iter.second; // may be this element was already processed in this update? if (elem->getVersion(tdbb) >= startingVersion) continue; - // now ask cache element to create all possible requests - try + if (!use) { - elem->makeRequests(tdbb); + // now ask cache element to create all possible requests + try + { + elem->makeRequests(tdbb); + } + + // handle specific for outdated element error + catch (const status_exception& ex) + { + if (ex.value()[1] != isc_old_format) + throw; + tdbb->tdbb_status_vector->init(); + use = true; + } } - // handle specific for outdated element error - catch (const status_exception& ex) + if (use) { - if (ex.value()[1] != isc_old_format) - throw; - tdbb->tdbb_status_vector->init(); - // get all existing currently dependencies - elem->fillDeps(tdbb); + elem->fillDeps(tdbb, false); // make new version and compile it elem->newVersion(tdbb); diff --git a/src/jrd/tra.h b/src/jrd/tra.h index bfe42cef80d..02c3c981813 100644 --- a/src/jrd/tra.h +++ b/src/jrd/tra.h @@ -434,9 +434,9 @@ class jrd_tra final : public pool_alloc void finiBulkInsert(thread_db* tdbb, Request* request); // Store an object to be updated - void storeUpdate(ElementBase* obj); + void storeUpdate(ElementBase* obj, bool forceRecompile); - // Store an object to be updated + // Store updated object to be committed void storeCommit(ElementBase* obj); // Process updates/commits accumulated by transaction @@ -445,7 +445,7 @@ class jrd_tra final : public pool_alloc private: // Under processing and accumulated sets of dependencies - typedef Firebird::BePlusTree Deps; + typedef Firebird::GenericMap> Deps; Firebird::AutoPtr processingDeps; Firebird::AutoPtr accumulatedDeps; From 9642f180d4bc8c7ba7c3046f185810eda29d6dea Mon Sep 17 00:00:00 2001 From: AlexPeshkoff Date: Mon, 21 Sep 2026 18:37:25 +0300 Subject: [PATCH 03/30] Added check for functions, triggers and computed fields --- src/jrd/CacheVector.h | 8 +++-- src/jrd/met.epp | 68 +++++++++++++++++++++++++++++++++++++++---- src/jrd/tra.cpp | 4 +-- 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/src/jrd/CacheVector.h b/src/jrd/CacheVector.h index d63092d9aa0..c0527e7b61a 100644 --- a/src/jrd/CacheVector.h +++ b/src/jrd/CacheVector.h @@ -91,7 +91,7 @@ class ElementBase virtual ObjectType getObjectType() = 0; virtual void getObjectName(QualifiedName& name) = 0; virtual void newVersion(thread_db* tdbb) = 0; - virtual bool ensureVersioned(thread_db* tdbb, ObjectBase::Flag fl) = 0; + virtual void makeVersion(thread_db* tdbb) = 0; virtual void makeRequests(thread_db* tdbb) = 0; virtual void commit(thread_db* tdbb, TraNumber curNumber = 0) = 0; virtual MdcVersion getVersion(thread_db* tdbb) = 0; @@ -683,9 +683,11 @@ class CacheElement : public ElementBase, public P return getVersioned(tdbb, TransactionNumber::current(tdbb), fl); } - bool ensureVersioned(thread_db* tdbb, ObjectBase::Flag fl) override + void makeVersion(thread_db* tdbb) override { - return getVersioned(tdbb, fl); + newVersion(tdbb); + auto rc = getVersioned(tdbb, 0); + fb_assert(rc); } bool isReady(thread_db* tdbb) diff --git a/src/jrd/met.epp b/src/jrd/met.epp index 248e0099813..5cd51517e42 100644 --- a/src/jrd/met.epp +++ b/src/jrd/met.epp @@ -5685,23 +5685,79 @@ void ElementBase::fillDeps(thread_db* tdbb, bool forceRecompile) AND D.RDB$DEPENDED_ON_NAME EQ object.c_str() REDUCED TO D.RDB$DEPENDENT_TYPE, D.RDB$DEPENDENT_SCHEMA_NAME, D.RDB$PACKAGE_NAME, D.RDB$DEPENDENT_NAME { + ElementBase* element = nullptr; + + QualifiedName name; + name.schema = D.RDB$DEPENDENT_SCHEMA_NAME; + if (!D.RDB$PACKAGE_NAME.NULL) + name.package = D.RDB$PACKAGE_NAME; + name.object = D.RDB$DEPENDENT_NAME; + switch (D.RDB$DEPENDENT_TYPE) { case obj_procedure: + element = MetadataCache::getPerm(tdbb, name, CacheFlag::AUTOCREATE); + break; + + case obj_udf: + element = MetadataCache::getPerm(tdbb, name, CacheFlag::AUTOCREATE); + break; + + case obj_relation: + case obj_view: + element = MetadataCache::getPerm(tdbb, name, CacheFlag::AUTOCREATE); + break; + + case obj_trigger: { - QualifiedName name; - name.schema = D.RDB$DEPENDENT_SCHEMA_NAME; - if (!D.RDB$PACKAGE_NAME.NULL) - name.package = D.RDB$PACKAGE_NAME; - name.object = D.RDB$DEPENDENT_NAME; + // Distinguish between triggers on table and database-wide + bool found = false; + AUTO_HANDLE(trigHndl); + FOR (REQUEST_HANDLE trigHndl TRANSACTION_HANDLE metaTransaction) + TRG IN RDB$TRIGGERS + WITH TRG.RDB$SCHEMA_NAME EQ D.RDB$DEPENDENT_SCHEMA_NAME AND + TRG.RDB$TRIGGER_NAME EQ D.RDB$DEPENDENT_NAME + { + if (TRG.RDB$RELATION_NAME.NULL) + element = MetadataCache::get(tdbb)->getTriggersSet(tdbb, TRG.RDB$TRIGGER_TYPE); + else + { + name.object = TRG.RDB$RELATION_NAME; + name.schema = TRG.RDB$SCHEMA_NAME; + name.package = ""; + element = MetadataCache::getPerm(tdbb, name, CacheFlag::AUTOCREATE); + } + } + END_FOR + } + break; - transaction->storeUpdate(MetadataCache::getPerm(tdbb, name, CacheFlag::AUTOCREATE), forceRecompile); + case obj_computed: + { + // should refresh ralations that have such computed fields + AUTO_HANDLE(compHndl); + FOR (REQUEST_HANDLE compHndl TRANSACTION_HANDLE metaTransaction) + FLD IN RDB$RELATION_FIELDS + WITH FLD.RDB$FIELD_SOURCE EQ D.RDB$DEPENDENT_NAME + AND FLD.RDB$FIELD_SOURCE_SCHEMA_NAME EQ D.RDB$DEPENDENT_SCHEMA_NAME + { + name.object = FLD.RDB$RELATION_NAME; + name.schema = FLD.RDB$SCHEMA_NAME; + name.package = ""; + auto e = MetadataCache::getPerm(tdbb, name, CacheFlag::AUTOCREATE); + if (e) + transaction->storeUpdate(e, forceRecompile); + } + END_FOR } break; default: break; } + + if (element) + transaction->storeUpdate(element, forceRecompile); } END_FOR } diff --git a/src/jrd/tra.cpp b/src/jrd/tra.cpp index 8e7c02628f4..3e9d24e9c5e 100644 --- a/src/jrd/tra.cpp +++ b/src/jrd/tra.cpp @@ -4531,9 +4531,7 @@ bool jrd_tra::processUpdates(thread_db* tdbb) elem->fillDeps(tdbb, false); // make new version and compile it - elem->newVersion(tdbb); - auto rc = elem->ensureVersioned(tdbb, 0); - fb_assert(rc); + elem->makeVersion(tdbb); storeCommit(elem); } From 5d80b88ae7660471699fd94a10ca38b44eef0ceb Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 19 Sep 2026 22:06:38 +0000 Subject: [PATCH 04/30] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 78f3e331686..7181271f3a6 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:2178 + FORMAL BUILD NUMBER:2179 */ -#define PRODUCT_VER_STRING "6.0.0.2178" -#define FILE_VER_STRING "WI-T6.0.0.2178" -#define LICENSE_VER_STRING "WI-T6.0.0.2178" -#define FILE_VER_NUMBER 6, 0, 0, 2178 +#define PRODUCT_VER_STRING "6.0.0.2179" +#define FILE_VER_STRING "WI-T6.0.0.2179" +#define LICENSE_VER_STRING "WI-T6.0.0.2179" +#define FILE_VER_NUMBER 6, 0, 0, 2179 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "2178" +#define FB_BUILD_NO "2179" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 0e0ac604cd1..c2291874fa8 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=2178 +BuildNum=2179 NowAt=`pwd` cd `dirname $0` From 5625c39f7a985090a079a1d3d2a4d7ea781cc12b Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes <529415+asfernandes@users.noreply.github.com> Date: Sat, 19 Sep 2026 21:28:32 -0300 Subject: [PATCH 05/30] Merge commit from fork --- builds/win32/msvc15/engine_test.vcxproj | 3 + .../win32/msvc15/engine_test.vcxproj.filters | 3 + src/jrd/Attachment.h | 2 +- src/jrd/event.cpp | 34 +- src/jrd/event_proto.h | 2 +- src/jrd/tests/EventManagerTest.cpp | 389 ++++++++++++++++++ 6 files changed, 429 insertions(+), 4 deletions(-) create mode 100644 src/jrd/tests/EventManagerTest.cpp diff --git a/builds/win32/msvc15/engine_test.vcxproj b/builds/win32/msvc15/engine_test.vcxproj index 6f49efefbed..6410da0123c 100644 --- a/builds/win32/msvc15/engine_test.vcxproj +++ b/builds/win32/msvc15/engine_test.vcxproj @@ -264,6 +264,9 @@ + + + diff --git a/builds/win32/msvc15/engine_test.vcxproj.filters b/builds/win32/msvc15/engine_test.vcxproj.filters index 6dd45c98d06..54185852059 100644 --- a/builds/win32/msvc15/engine_test.vcxproj.filters +++ b/builds/win32/msvc15/engine_test.vcxproj.filters @@ -18,6 +18,9 @@ source + + source + source diff --git a/src/jrd/Attachment.h b/src/jrd/Attachment.h index 44156d10557..7e9d41c40ed 100644 --- a/src/jrd/Attachment.h +++ b/src/jrd/Attachment.h @@ -537,7 +537,7 @@ class Attachment : public pool_alloc const ULONG att_lock_owner_id; // ID for the lock manager SLONG att_lock_owner_handle; // Handle for the lock manager ULONG att_backup_state_counter; // Counter of backup state locks for attachment - SLONG att_event_session; // Event session id, if any + std::atomic att_event_session; // Event session id, if any SecurityClass* att_security_class; // security class for database SecurityClassList* att_security_classes; // security classes RuntimeStatistics att_stats; diff --git a/src/jrd/event.cpp b/src/jrd/event.cpp index 59b8139f9ad..38a749ac2d6 100644 --- a/src/jrd/event.cpp +++ b/src/jrd/event.cpp @@ -66,12 +66,38 @@ using namespace Firebird; namespace Jrd { +// Check that the event parameter block is well formed before +// changing the shared global region. Each item shall completely +// fit into the buffer: one byte of the name length, the name +// itself and four bytes of the prior count. +static void validateEpb(USHORT eventsLength, const UCHAR* events) +{ + const UCHAR* p = events + 1; + const UCHAR* const end = events + eventsLength; + + while (p < end) + { + if (end - p < 5 || p[0] > end - p - 5) + Arg::Gds(isc_bad_epb_form).raise(); + + p += 1 + p[0] + 4; + } +} + + void EventManager::init(Attachment* attachment) { Database* const dbb = attachment->att_database; + EventManager* const eventMgr = dbb->eventManager(); - if (!attachment->att_event_session) - attachment->att_event_session = dbb->eventManager()->create_session(); + SLONG expected = attachment->att_event_session; + if (expected) + return; + + const SLONG session = eventMgr->create_session(); + + if (!attachment->att_event_session.compare_exchange_strong(expected, session)) + eventMgr->deleteSession(session); // another thread won the race } @@ -212,6 +238,10 @@ SLONG EventManager::queEvents(SLONG session_id, Arg::Gds(isc_bad_epb_form).raise(); } + // Validate the EPB up front to avoid any changes of the shared global + // region in case of a malformed buffer + validateEpb(events_length, events); + acquire_shmem(); // Allocate request block diff --git a/src/jrd/event_proto.h b/src/jrd/event_proto.h index 7ab5ac91e5d..db2a240e369 100644 --- a/src/jrd/event_proto.h +++ b/src/jrd/event_proto.h @@ -49,6 +49,7 @@ class EventManager final : public Firebird::GlobalStorage, public Firebird::IpcO static void init(Attachment*); void deleteSession(SLONG); + SLONG create_session(); SLONG queEvents(SLONG, USHORT, const UCHAR*, Firebird::IEventCallback*); void cancelEvents(SLONG); @@ -68,7 +69,6 @@ class EventManager final : public Firebird::GlobalStorage, public Firebird::IpcO void acquire_shmem(); frb* alloc_global(UCHAR type, ULONG length, bool recurse); void create_process(); - SLONG create_session(); void delete_event(evnt*); void delete_process(SLONG); void delete_request(evt_req*); diff --git a/src/jrd/tests/EventManagerTest.cpp b/src/jrd/tests/EventManagerTest.cpp new file mode 100644 index 00000000000..aacae1d22df --- /dev/null +++ b/src/jrd/tests/EventManagerTest.cpp @@ -0,0 +1,389 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2026 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#include "firebird.h" +#include "boost/test/unit_test.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../common/classes/fb_string.h" +#include "../common/classes/ImplementHelper.h" +#include "../common/config/config.h" +#include "../common/status.h" +#include "../common/ThreadStart.h" +#include "../jrd/event_proto.h" +#include "../yvalve/gds_proto.h" + +using namespace Firebird; +using namespace Jrd; + + +namespace +{ + +// Wait for a condition with a deadline, polling periodically. +bool waitCond(const std::function& cond, unsigned timeoutMs) +{ + const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeoutMs); + + while (!cond()) + { + if (std::chrono::steady_clock::now() >= deadline) + return false; + + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + + return true; +} + +std::string getUniqueId() +{ + static std::atomic counter{0}; + + const auto now = std::chrono::system_clock::now(); + const auto nowNs = std::chrono::duration_cast( + now.time_since_epoch()).count(); + + return "evt_" + std::to_string(nowNs) + "_" + std::to_string(counter.fetch_add(1)); +} + +// Build an EPB (event parameter block) v1 for a single event. +std::vector makeEpb(const std::string& name, SLONG priorCount) +{ + if (name.length() > 255) + BOOST_FAIL("event name is too long"); + + std::vector epb; + epb.push_back(EPB_version1); + epb.push_back(static_cast(name.length())); + epb.insert(epb.end(), name.begin(), name.end()); + + for (int i = 0; i < 4; ++i) + epb.push_back(static_cast((priorCount >> (8 * i)) & 0xFF)); + + return epb; +} + + +class TestEventCallback final : + public RefCntIface > +{ +public: + // Number of times the AST was invoked. + std::atomic calls{0}; + + void eventCallbackFunction(unsigned length, const unsigned char* events) override + { + std::lock_guard lock(mutex); + ++calls; + + if (length >= 1 && events[0] == EPB_version1) + { + const UCHAR* p = events + 1; + const UCHAR* const end = events + length; + + while (p + 1 <= end) + { + const USHORT count = *p++; + if (USHORT(end - p) < count + 4u) + break; + + std::string name(reinterpret_cast(p), count); + p += count; + received.emplace_back(name, gds__vax_integer(p, 4)); + p += 4; + } + } + } + + // Returns the count reported for the given event name, or -1 if not found. + SLONG getCount(const std::string& name) const + { + std::lock_guard lock(mutex); + + for (const auto& item : received) + { + if (item.first == name) + return item.second; + } + + return -1; + } + +private: + mutable std::mutex mutex; + std::vector> received; +}; + + +// Holds the configuration alive for the duration of a test. +struct TestEnv +{ + ConfigFile configFile; + Config config; + + TestEnv() + : configFile(ConfigFile::USE_TEXT, "\n"), + config(configFile) + { + } +}; + + +struct ManagerHolder +{ + TestEnv env; + Firebird::string id; + std::unique_ptr manager; + SLONG session = 0; + + ManagerHolder() + : id(getUniqueId().c_str()) + { + try + { + manager = std::make_unique(id, &env.config); + session = manager->create_session(); + } + catch (const Exception& ex) + { + FbLocalStatus status; + ex.stuffException(&status); + const ISC_STATUS* s = status->getErrors(); + TEXT buffer[1024]; + while (fb_interpret(buffer, sizeof(buffer), &s)) + fprintf(stderr, "EVENTTEST: init failed: %s\n", buffer); + throw; + } + } + + ~ManagerHolder() + { + manager->deleteSession(session); + } +}; + +} // anonymous namespace + + +BOOST_AUTO_TEST_SUITE(EngineSuite) +BOOST_AUTO_TEST_SUITE(EventManagerSuite) + + +BOOST_AUTO_TEST_CASE(BasicRoundTripTest) +{ + ManagerHolder h; + TestEventCallback cb; + + // A request with prior count equal to the current count is satisfied + // immediately and delivered asynchronously without any explicit posting. + const auto epbAuto = makeEpb("EV_AUTO", 0); + h.manager->queEvents(h.session, epbAuto.size(), epbAuto.data(), &cb); + + BOOST_TEST(waitCond([&]() { return cb.calls >= 1; }, 5000)); + BOOST_TEST(cb.getCount("EV_AUTO") == 1); + + // A cancelled request must never be delivered. + const auto epbCancel = makeEpb("EV_CANCEL", 50); + const SLONG cancelId = h.manager->queEvents(h.session, epbCancel.size(), epbCancel.data(), &cb); + BOOST_TEST(cancelId != 0); + h.manager->cancelEvents(cancelId); + + h.manager->postEvent(sizeof("EV_CANCEL") - 1, "EV_CANCEL", 100); + h.manager->deliverEvents(); + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + BOOST_TEST(cb.calls.load() == 1); + BOOST_TEST(cb.getCount("EV_CANCEL") == -1); + + // A request satisfied by postEvent is delivered by deliverEvents(). + const auto epbPost = makeEpb("EV_POST", 10); + h.manager->queEvents(h.session, epbPost.size(), epbPost.data(), &cb); + + h.manager->postEvent(sizeof("EV_POST") - 1, "EV_POST", 20); + h.manager->deliverEvents(); + + BOOST_TEST(waitCond([&]() { return cb.getCount("EV_POST") >= 0; }, 5000)); + BOOST_TEST(cb.getCount("EV_POST") == 21); +} + + +BOOST_AUTO_TEST_CASE(MalformedEpbTruncatedTailTest) +{ + // An EPB whose last item declares a name that does not fit into the + // buffer together with the mandatory 4-byte prior count must be rejected. + ManagerHolder h; + TestEventCallback cb; + + auto epb = makeEpb("AAA", 0); + epb.push_back(10); // name length of the last item + epb.push_back(1); // a couple of trailing bytes, far too few + epb.push_back(2); + + bool raised = false; + + try + { + h.manager->queEvents(h.session, epb.size(), epb.data(), &cb); + } + catch (const Exception&) + { + raised = true; + } + + BOOST_TEST(raised); + + // Nothing must be delivered for the malformed request and no residue + // must be left behind. + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + BOOST_TEST(cb.calls.load() == 0); + + // The manager must stay fully functional. + const auto epbGood = makeEpb("EV_AFTER_TRUNCATED", 0); + h.manager->queEvents(h.session, epbGood.size(), epbGood.data(), &cb); + BOOST_TEST(waitCond([&]() { return cb.getCount("EV_AFTER_TRUNCATED") >= 0; }, 5000)); + BOOST_TEST(cb.getCount("EV_AFTER_TRUNCATED") == 1); + BOOST_TEST(cb.calls.load() == 1); +} + + +BOOST_AUTO_TEST_CASE(MalformedEpbOverlongNameTest) +{ + // An EPB declaring an event name longer than the whole buffer must be + // rejected and must leave no partially built request behind. Such a + // leftover request would be considered completed and asynchronously + // deliver a garbage event block to the callback. + ManagerHolder h; + TestEventCallback cb; + + auto epb = makeEpb("BBB", 0); + epb.push_back(250); // bogus name length, exceeds the buffer + + bool raised = false; + + try + { + h.manager->queEvents(h.session, epb.size(), epb.data(), &cb); + } + catch (const Exception&) + { + raised = true; + } + + BOOST_TEST(raised); + + // No garbage delivery may arrive for the malformed request. + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + BOOST_TEST(cb.calls.load() == 0); + + // The manager must stay fully functional. + const auto epbGood = makeEpb("EV_AFTER_OVERLONG", 0); + h.manager->queEvents(h.session, epbGood.size(), epbGood.data(), &cb); + BOOST_TEST(waitCond([&]() { return cb.getCount("EV_AFTER_OVERLONG") >= 0; }, 5000)); + BOOST_TEST(cb.getCount("EV_AFTER_OVERLONG") == 1); + BOOST_TEST(cb.calls.load() == 1); +} + + +BOOST_AUTO_TEST_CASE(ConcurrentStressTest) +{ + constexpr unsigned THREAD_COUNT = 4u; + constexpr unsigned ITERATION_COUNT = 300u; + + ManagerHolder h; + TestEventCallback cb; + std::atomic expectedCalls{0}; + + std::vector threads; + std::atomic startBarrier{0}; + + for (unsigned threadNum = 0; threadNum < THREAD_COUNT; ++threadNum) + { + threads.emplace_back([&, threadNum]() { + const std::string prefix = "ST_" + std::to_string(threadNum) + "_"; + ULONG localExpected = 0; + + startBarrier.fetch_add(1, std::memory_order_acq_rel); + while (startBarrier.load(std::memory_order_acquire) < THREAD_COUNT) + std::this_thread::yield(); + + for (unsigned i = 0; i < ITERATION_COUNT; ++i) + { + const std::string name = prefix + std::to_string(i); + const auto epb = makeEpb(name, 0); + + switch (i % 3) + { + // Immediately satisfied request, delivered on its own. + case 0: + h.manager->queEvents(h.session, epb.size(), epb.data(), &cb); + ++localExpected; + break; + + // Request satisfied by postEvent and delivered by deliverEvents(). + case 1: + { + const auto epbLate = makeEpb(name, 5); + h.manager->queEvents(h.session, epbLate.size(), epbLate.data(), &cb); + h.manager->postEvent(name.length(), name.c_str(), 10); + h.manager->deliverEvents(); + ++localExpected; + break; + } + + // Cancelled request, never delivered. + case 2: + { + const auto epbCancelled = makeEpb(name, 100); + const SLONG id = h.manager->queEvents(h.session, + epbCancelled.size(), epbCancelled.data(), &cb); + h.manager->cancelEvents(id); + break; + } + } + } + + expectedCalls += localExpected; + }); + } + + for (auto& thread : threads) + thread.join(); + + BOOST_TEST(waitCond([&]() { return cb.calls >= expectedCalls; }, 30000)); + + // Every satisfied request must be delivered exactly once. + h.manager->deliverEvents(); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + BOOST_TEST(cb.calls.load() == expectedCalls.load()); +} + + +BOOST_AUTO_TEST_SUITE_END() // EventManagerSuite +BOOST_AUTO_TEST_SUITE_END() // EngineSuite From f07747e816feabeb828c5d9505d599dd8c623dbe Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 20 Sep 2026 22:11:11 +0000 Subject: [PATCH 06/30] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 7181271f3a6..c9646accd94 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:2179 + FORMAL BUILD NUMBER:2180 */ -#define PRODUCT_VER_STRING "6.0.0.2179" -#define FILE_VER_STRING "WI-T6.0.0.2179" -#define LICENSE_VER_STRING "WI-T6.0.0.2179" -#define FILE_VER_NUMBER 6, 0, 0, 2179 +#define PRODUCT_VER_STRING "6.0.0.2180" +#define FILE_VER_STRING "WI-T6.0.0.2180" +#define LICENSE_VER_STRING "WI-T6.0.0.2180" +#define FILE_VER_NUMBER 6, 0, 0, 2180 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "2179" +#define FB_BUILD_NO "2180" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index c2291874fa8..188a68d7af0 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=2179 +BuildNum=2180 NowAt=`pwd` cd `dirname $0` From 88871ccf47cec476f2a554904f6ddbed9ec32cfd Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Mon, 21 Sep 2026 07:27:59 -0300 Subject: [PATCH 07/30] Add missing filter --- src/jrd/dfw.epp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jrd/dfw.epp b/src/jrd/dfw.epp index b50369cb41a..784577aa18a 100644 --- a/src/jrd/dfw.epp +++ b/src/jrd/dfw.epp @@ -4106,6 +4106,7 @@ static bool delete_rfr(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tr CROSS RFLD IN RDB$RELATION_FIELDS WITH RFLD.RDB$SCHEMA_NAME EQ REL.RDB$SCHEMA_NAME AND RFLD.RDB$PACKAGE_NAME EQUIV REL.RDB$PACKAGE_NAME AND + RFLD.RDB$RELATION_NAME EQ REL.RDB$RELATION_NAME AND REL.RDB$RELATION_ID EQ work->dfw_id { field_count++; From b661d2b15e3331e3fb224535fdb91d7d4edf4d83 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 21 Sep 2026 23:00:11 +0000 Subject: [PATCH 08/30] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index c9646accd94..5f3e1e32ea5 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:2180 + FORMAL BUILD NUMBER:2181 */ -#define PRODUCT_VER_STRING "6.0.0.2180" -#define FILE_VER_STRING "WI-T6.0.0.2180" -#define LICENSE_VER_STRING "WI-T6.0.0.2180" -#define FILE_VER_NUMBER 6, 0, 0, 2180 +#define PRODUCT_VER_STRING "6.0.0.2181" +#define FILE_VER_STRING "WI-T6.0.0.2181" +#define LICENSE_VER_STRING "WI-T6.0.0.2181" +#define FILE_VER_NUMBER 6, 0, 0, 2181 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "2180" +#define FB_BUILD_NO "2181" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 188a68d7af0..27c05cdae36 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=2180 +BuildNum=2181 NowAt=`pwd` cd `dirname $0` From 887639da29483bdeecf45f00bd944fa8c322d9fb Mon Sep 17 00:00:00 2001 From: TreeHunter <60896014+TreeHunter9@users.noreply.github.com> Date: Tue, 22 Sep 2026 20:35:34 +0300 Subject: [PATCH 09/30] Postfix: Decrement blr length after reading value from blr in `parse_format` for `blr_int128` (#9124) Co-authored-by: Artyom Ivanov --- src/remote/parser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/remote/parser.cpp b/src/remote/parser.cpp index ed9a1369730..cea7aaca6ff 100644 --- a/src/remote/parser.cpp +++ b/src/remote/parser.cpp @@ -299,7 +299,7 @@ static rem_fmt* parse_format(const UCHAR*& blr, size_t& blr_length) break; case blr_int128: - if (blr_length < 1) + if (blr_length-- == 0) return nullptr; desc->dsc_dtype = dtype_int128; desc->dsc_length = sizeof(Int128); From 762d0a040a126465021c280a5d25df34b8db4287 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 22 Sep 2026 22:43:46 +0000 Subject: [PATCH 10/30] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 5f3e1e32ea5..2218c656eb1 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:2181 + FORMAL BUILD NUMBER:2182 */ -#define PRODUCT_VER_STRING "6.0.0.2181" -#define FILE_VER_STRING "WI-T6.0.0.2181" -#define LICENSE_VER_STRING "WI-T6.0.0.2181" -#define FILE_VER_NUMBER 6, 0, 0, 2181 +#define PRODUCT_VER_STRING "6.0.0.2182" +#define FILE_VER_STRING "WI-T6.0.0.2182" +#define LICENSE_VER_STRING "WI-T6.0.0.2182" +#define FILE_VER_NUMBER 6, 0, 0, 2182 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "2181" +#define FB_BUILD_NO "2182" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 27c05cdae36..7d9e6b4fa1f 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=2181 +BuildNum=2182 NowAt=`pwd` cd `dirname $0` From 0eb5c832038366f955954a3c1f40740566eb9696 Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Tue, 22 Sep 2026 15:38:35 +0300 Subject: [PATCH 11/30] Fix #9156: explicit PLAN rejected with 'index ... cannot be used in the specified plan' when the query contains a LEFT OUTER JOIN --- src/jrd/optimizer/InnerJoin.cpp | 8 +++++++- src/jrd/optimizer/Retrieval.cpp | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/jrd/optimizer/InnerJoin.cpp b/src/jrd/optimizer/InnerJoin.cpp index e52c02086f4..5bddd679be9 100644 --- a/src/jrd/optimizer/InnerJoin.cpp +++ b/src/jrd/optimizer/InnerJoin.cpp @@ -214,7 +214,13 @@ void InnerJoin::estimateCost(unsigned position, // its real cardinality during execution. So, unless we have some index-based // filtering applied, let's better be pessimistic and avoid hash joining due to // likely cardinality under-estimation. - const bool avoidHashJoin = (streamCardinality <= MINIMUM_CARDINALITY && !stream->baseIndexes); + bool avoidHashJoin = (streamCardinality <= MINIMUM_CARDINALITY && !stream->baseIndexes); + + // If the user-defined plan is provided and we were able to utilize indices for this retrieval, + // then such indices were explicitly specified in the plan. + // It means the user seems to prefers a loop-join over a hash-join. + if (csb->csb_rpt[stream->number].csb_plan && candidate->indexes && candidate->dependencies) + avoidHashJoin = true; // Consider whether the current stream can be hash-joined to the prior ones. // Beware conditional retrievals, this is impossible for them. diff --git a/src/jrd/optimizer/Retrieval.cpp b/src/jrd/optimizer/Retrieval.cpp index 4ab26cdc2da..4b40d50cee2 100644 --- a/src/jrd/optimizer/Retrieval.cpp +++ b/src/jrd/optimizer/Retrieval.cpp @@ -1449,7 +1449,7 @@ InversionCandidate* Retrieval::makeInversion(InversionCandidateList& inversions) // for a retrieval. Internal (system) requests used by the engine itself are // often optimized using zero or non-actual statistics, so they are processed // using somewhat relaxed rules. - const bool customPlan = csb->csb_rpt[stream].csb_plan; + const bool customPlan = (csb->csb_rpt[stream].csb_plan != nullptr); const bool sysRequest = (csb->csb_g_flags & csb_internal); double totalSelectivity = MAXIMUM_SELECTIVITY; // worst selectivity From f2b45c2efe8d2ab1b7aed66388ca43c1ab2395bb Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 23 Sep 2026 22:40:44 +0000 Subject: [PATCH 12/30] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 2218c656eb1..c4c583ecd9f 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:2182 + FORMAL BUILD NUMBER:2183 */ -#define PRODUCT_VER_STRING "6.0.0.2182" -#define FILE_VER_STRING "WI-T6.0.0.2182" -#define LICENSE_VER_STRING "WI-T6.0.0.2182" -#define FILE_VER_NUMBER 6, 0, 0, 2182 +#define PRODUCT_VER_STRING "6.0.0.2183" +#define FILE_VER_STRING "WI-T6.0.0.2183" +#define LICENSE_VER_STRING "WI-T6.0.0.2183" +#define FILE_VER_NUMBER 6, 0, 0, 2183 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "2182" +#define FB_BUILD_NO "2183" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 7d9e6b4fa1f..8f2bf03f27f 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=2182 +BuildNum=2183 NowAt=`pwd` cd `dirname $0` From 94901723f930aba537d30e181ab33371f1dde82c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 21:48:47 -0300 Subject: [PATCH 13/30] Update tzdata to version 2026d. (#9163) Co-authored-by: asfernandes --- extern/icu/tzdata/be.zip | Bin 94752 -> 95910 bytes extern/icu/tzdata/le.zip | Bin 94048 -> 95287 bytes extern/icu/tzdata/version.txt | 2 +- src/common/TimeZones.h | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extern/icu/tzdata/be.zip b/extern/icu/tzdata/be.zip index d43f06a56b26b66b1173d0690a078fc7bf38de14..272bc6857ea297cda8c99cae74a06f6fbbc58ce2 100644 GIT binary patch delta 67421 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%0AlsTOLhIQOFSshBn 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 XkS<=ii~pwim$sG!86y``Af^7_XSbeh delta 66305 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 diff --git a/extern/icu/tzdata/le.zip b/extern/icu/tzdata/le.zip index b7f5130a4e5be76813aa1693ce73ec3aa5eec370..93e20c8f9f7439b1de9983919a14187b6c622f38 100644 GIT binary patch delta 67320 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%0DDaak65kd`ZOg_q- zHycYQ)5MEtu#jHOyWXHs1#%SDiVVxSwkL#pwa~QlAZ&98+)42Eh_bY}!uRNb^Lu2v zz_Iu2FjHHxUj) z>-+ybWfDrjbVA37{oZ4hmDppSZ8N4Yap*<4@`5!3xoP|T1HS?Nu&jc^XQO+*QcI4m zsgk#s{i8rtEv&!X^p=wwsQ?b0Ty>Ing|bmo2HC^UbE$(YDF*wAcsEfu6bWf~_b z;Nyjc+lY~G#}LCv6yX)ci`&brq+@j+PP(*=UsK$KpAmTyiWKTQHSF0G>+!mZ z-t(@n7g45s#*3QrQgzjwx_==mVTg>b1#vzZ30|2j%}P_O!$@n+3?7Q?gVOkGNDlFN z9jy6)Wa!m1&v3$&E%|wtm46PZX4Gec^iyPl?+|ej)aX`8aX-a5l4?l`KIZCKiOVXK zl@>HB7epa;q{j}lL%uq%Z-<_n-b~^uzZfyho46hQB?Si57&-;ZH9aJWs{vX z@2?-2><2a9-*1;wF!*!7r7?a6;`-Xdup~5dBM{eKO~HT{;GG*hEgqhE#?_(M5b7!1 zXFGK&(Z1Fq?+_CFO+hqmC+3+yCkx11v(t@4j9xHdfqj+yVb6LQ@Q=da3#VkE81BWu z9{^j|Fv35W;LnF?t7(tIP{IL4qfu@V`N+eBR2$} zlzus541WE}7h@eEY5it^)JdMAKN|qnMDqM&!JtJiJ*&XI^}G`0)ku{`-+NHuR|6}a zS6%*zfE${qPbea9pL_e?KO_fTIKFT(gL&Y$9H9;P+iUfg-I~uHdUf2Cx3dVGou6JS zu;|M6sjH7`bcX}rJp-QNz7qVtwN6t?EdA;k_5Ihc(O=r5zuZR;&y%LZlBNY|3pT!Xi{GV;$1@=zhQpl*ssaQBI2K3jc%G{%*|6xDo?3M!ApkBYT!i_Ah_< zlR%h5wITK3gXr05jh-$N_g$G*)3>g*J2KcX%8V$mxDEHtOCav#aB^IaT`U2MWVydy z6=^3YLV6i2+fUpIe^;q=R1hqnQ9dMgfD^*K=BpcZWy`Q<`PErlH)-8hw?YVU=e_XT zu^9~8&QJBE&W$q<$#2hN()c9BmjYnsr29^MGMcpJZluK<3Ck1E-_i3M`<3QT*FRx4 zDvNKH16aL;cI#=JrNd{hzc)~`O*>CX!}C4MZpt<2nLxw@T`~O#Ri-3?XG^u-`YlY~ z$juODq$m1$ST$oKm{_mLarx0Dy~rj%3Y_ir&eC=3s~TPXMKu?eFOwCr$BZsaLDtG* z%pN!Ku88OZ0{1zUlRVrgY@lz8ht3KLlB|C)7-`vd1K}3`M5m-&*h8?OM$3YSE5tkZ8Dmwa_cgV|`Zb8uXosxi&}4ez&44q#O;X z=9BwG^{yy5#f+;n?dMbZAE@7>w(Q1uc+>L(rdH-3>LqEX5PNaN@DzQaTs8t>*~hVW z@Q`0hAc{i`cb58sQ19x2bfmEOqz}a_Rn4fC$$m+k%Bke7-TTnM45Fy!90%{GTSEV3 zXGWZDq;g9(pJw>nLJ#lfQ)hSL{zqnmb=6IpfCo?ps=xh1@x4MUV2j() z^L*oCSRu=Tls6pM_xR^{d77s9o0MzmmT^^1cn{Bjvr>MzKm%X(v;Vba^Q>{ zhwZQ!&7hPyx!;?mJhllw*2&l>@Y*X!uvtV%&i&RcH9>La9>)RbpM0)n@*8m>)lhbeqT1=a%L03bZyx?u zDgDOs`*0?ZmWeu@Fk&6KnPT;+MkPP0IVW87&kau$JUzRk;1~C=BGkA6MxJaVMlr1J zoC|8?hVy=JZRiKk6NvQlAGnia>5hZ)VFN6e8%RRg%VO6es4j6m70=_$JC{WL-tJOV z=cQ2HWl+A>oBhX83$!7bTF5d~ojgJIL^od2OVRIu)o*|x*vO*-{KeCW`-@f?LRhqW zt|}(9(M>P@w9sxEOb%M#d>w(mSptQNd7lyf=0ol}{ZKyc?~UsGZ=^x;AnZ-}a%dfn z5l*|8W;vLT(S#+mz8>=Xm>wE!iJMs1IfB{u*XP{7pVIGN_0Ps$bJlsOeT>l73-@YM z0G>f$#`Zo|j~}DdXJsDd;mboMJ+bnM2yI57Z{>b6-|&F#A!{SLVEDp!)Z^Owz4~Xo ziuVJuhG)EJl!z~MMy_{iAtmKX%>`YDFnp1Aq~X8O#ll$ok;tUQ!?;W+!7=XXONyOh zS)?_xhH9zPRaM+INi?(QJG?twJD#+pvB_3IvUi()Mcm5WS-Z<2!8Tj~HC)^mGpr~0 zwwP7fbL!Z*-1rYUI&~(_hz!xU3mvdpA-;vXAgN_k_4bbO-B zC0etPX}v?B6m?4KF!t!?7FpM%YMf|X+JcNd^ea2#ID&=X4YdWgQ_@pF+jw17IC32j zxcspz1(rA^Y>qb=6q@KBKmUmezGygD{@9PbrhZg={qhbw_8S4TbH3?2vIfC&EMB#5 zgKr7a8}sSQ-Gkid>9=V6vpX@J+rK`%D&(Mv8#hhUc<;3!*!5C(4%;5wJG}+-2Y(mM z`5pC=)Y1Ref8>E9EQ?Ex`E@pEBnBPW!dcCJW${H44rku0e1zZVtKD9_$$VOT6ntfR zV6lr){{9+K=q$O0yCZ2{IK$-STzE%0C~Fy=Wj@Xp!-~YwJYsvTuv`t{^scc_T^uHA z=X#RcE9)L*k#^JBpmdQb?PrpSM;ZHDEYq&i&fG87-lf{c*P@V7`tfI~{9+DB9Bnyv zi=463wj{+et6B_=Y6~%WbJ)9R7p$-P(EmvBJXY~(bJXyV%C7@E7VR&Mxj9ZrbkghhUf5UR&;UjB#omNd`3C;K3qSgfO(JjWnNCCJ!C+C_C1A0wSBi&LDLP~-%hgd7eYSsVwn z!>*CHYqYzrS+{q!o3<*t?LbvBHE7GGxkQb5_L z_JjKEYoqRm{WHoN)0?pmSw{EVhXI71%iJ%aR9RCn)-dFD0Fp3fe^o|FXH zEo`lEYyKaWF=gvtoC1K|($B|2b(@sb6Y7Z74}t{KG;*65PRgrr>YL5G5?$C%DuF7JhT9ZB^D^^-JrTL# z87m#zWwT{tUNc^EUQnCGR{hzsn|bUc)8xB?E5*yz=|Mw_1<9~bx{uI{XY(%?olxzI z2F1F`y2*wG-OQ@7DkW`h-H%^uc55IX$g}@l6{oa*^ZPF>8_moO*HlJ`<}doDx54H= z3npH3_eIrf3xKiLnCsNNmR=B#X@{AZYvP(Iq-n#HxeihXX$U=?xCg-Yb@t6}{~ne$ z8UVA8(f6*d=sZIDuLE1It9!#vn0&HCoH3F^szVqt2ZVZrD@0y|QiLRYll(Qj?xh1B zrA|Q&Q@mqOaZW_j$NGET>urCZj3#F%?Xz35uosDH055$E11Ejt8uOY`14m)Go{1Wi zMU=&r8m7gU8i>J0Yq#6m?)Dm0%a~87SD06lSAbW@%i=ly1KESggX4ql1MGu^Td7y6 zS8m7H8~7#QMXDujXSj|1pgU`X*U71mH_}OSukpZn_jJ;O`CunYjr4n*JepH2+T1^V<^f z5(y9XBg5~2R>DWxDG6Um#(ab_d@IBoDvv~b8DQd+Joo#)szXUm!C|C6L9UzuS>PNV z(~lbT6!@0bp{MLss&iyVrjAJb-dfS@UB_OTSPKxl-8+I-A7LKc_3RRp1ufYB=c5EL zZqx0zHf+Cz(PGITK6}X49zwIt&>jwBLhO*3Jgm)BXU!y(|z!?}R>_@gp z`29IM4|gB=9o;F}BVHiNF0OPKZTGtgFCSTRd~Cw(RHip^a-(fvR>na zlX}G{9|j@~c`hLyVyut*PK=#?Z4PQ|XzOzrlr`M8za&8;HV$o-I)&7LYMp|V6+@rs z`$4CvyF0s#1p!$KhSX8Ji@U!fzB8+3{mic%iF6iHqr!wI7$JMc=ai}5nhgS2o*2?Y zpAnVv@5+d<&@$)gJ`g;K&VF=E`8p?9V^)nTdO}gNN|pqe`FLoqZLl0BK zux=YnSWWj##aT(jpE}d|XoM&+XY5)2wtX{@X$j|~>F=u4oN{$wNF72kx1Y9W3v7k& z%Jqq8Qrpv~y#LXts%_3iJq<1*{h2JdYS8pq}Ot&a^w}@SUtraI|N*iuC@sxjLfmxPqA^B6dN5 zPA`!KXbNtmX&hg-q&W^aIP87pI+xqWD3SQqy0?uFHq@RxH_-y;#))i^dy;)%HihMi z+gk9qZaL%e$+W0J*a*_KmOxL~u3z;A6-}_V@x3zEBj4TJB0@P92CBx8E!248LSN_h{g}ZPdK?RJ^|l|DTvt zVjhU{|L3F{#z8$q{|icWDq>VjuR}rok@^gU@*gPmKaiBAorOIY$N!CzO49eSF^N!a`e9z2X~Pws7jU&i3`7g^vlG`=M{Idm2(aIKyMxx3u2RaX zwST;kasZUY@S{?{B4Dh(A*;v*(_j{>(y2E!iil$&i&FrkcadDl&v0MP-?DEk_PgA2 z++?`eFS4AdQ|GvAP-t%;U$G$bAjLx^ROhmN;SFzH1HR(ihUbfk(xZz~ zd7?KrEkN3?*s;kItgE6oFI!V{nxQd$`%Ac`8E8<0EZ_24omHxW>xW*o86PgitV*@P zvR9I{L%CIA&voeyR{FKNEiMBZl~sCNoF{aR6{26UWGj?qgR3Kwo77u>E`O=4%9U8t zYz5nvx4FC-l^M?EUQ4|+>()iMs#G(SC9gJXOa~gQsj|wofNE4f!fAh`II! z?-eCuR&ui%Q+2x2&W4L@F7laZZ_W87-e~m1YBJ`YNLy0JodbsJhZ7V|D~av!kVI?slLQNRG5M}ggvC9k&(c4 zCg%9u4%Lo?|HS}0I)){sH>&HxDr{GH)!8BJt~#%M>OB3k*JNHgq;zv>#L8%t-rJS!%&g?I^Nm zHa`-?@{Ff=r#$|vm+TPX5dRn9Tw_BB@8G3oloD%lRe+rzknyxG&g?K3RhZ2)h#{2? z)A}{)yT719p2cTdDwYtK{p3H==!ca4hUmW-{Z%L*`-}gl!~pzo*@8WalXUpQ&otSi z=KtM|#uVoiW|^fik$rXH1m<8?Uz!@OEy60MrUi7DO@TLtYGd%T9_|yi#T&}o1*gP8 z=B+L|q$~%pSB+n*dyrN#%|FMiKJu7;X!x{TB-JDN9;#=wI9D+-7vBrIL zofg5a5*iXZ+E}*-38Tj29xg@q$<@NbMT>XeUBm!$Ryesu@LeumOR=@G7k#^#UXO7; zk($NPa|+ptr6;6JP-20-=(wFLx-a6kd;PN(66$fsgZEq}F8E<=Rbh^QPwLIxy{i_W&K1oD$|jl1t$&_~sI-xs6^yFV;EH@>ZXYGfSnt)=HMd}b(+<~M z&LDOo@>(_2+)4Ks@>ZbnlBV&}rs-fx0tCsD*1nArV~rAHj1uE4%n9#X8jsKRrIiM= zDj}NYU=Io~MtSJs-f#^5rxF6HGS=!c%=dSRw{Nip>s1y$bcv7P%U9Z6#>h#EzT3od zjBhuZc^7|?PHRbWj`uaeVi&`1$+8FcIeUYiD5vk8Is6A?1_4lkVA=g1rTnnbF>bqn(1)d?YoD8_ecxV)^p z+RWXZKSb4l8r`6=E4o&bH2%0)0}SK@D^vOBj)f!ERYNP3^}VG7_cBIbbZ4%UxNi@T zELIJP7a`>1t()=kv##rl_UL;r-B;8uGWnKWD`OMk3_G`tskNp=qn;XTXEp_2N;mKt z-QjLnt+S224w>A{2K2gnUCY;eHE*9-HLv2%OrWi;_e~cX%W~mFpW^o)fw4vB6Ur=A zHxEL3(AT{fLZ&`N99FG=4WCEXt`#c57c&{W((3Vx>7#{Z2AhT}W0i571YVj~*^%EG zO%!-ojXPCLk$3l};9oDs>$$4G9zgnH7JRZO2DQu-@k+Zm+Zl9|cwE)&J9z2iIKvzM zl|Xm?M`|rb|M!7!f0m591019$yQp14DBV#1t&Ek6)Vg!NA?+JN**W)Mz_wRy@3x-( zA^Tmj49%ROV|DJg_?fA2&V=^LaUA|yXr(a{<}c$Wf$wa^}}(MNmr$U z*;8puDOx$ny0y%&Zo+Ta0AGv-bC){>(59ny}+>630E^=~}8#X@Aihe>?YH{%YT z(*7*N3`G}2JL57q4g=3~w%k!)SkTqAuyajXh-E~v<*H2a52IP412OwfyEP*B+2$7`#ihZ}2c^1`ZDK_9IDvr~Juy5ccrlPue( ztvO{9{PtV8wnq1{GaF~m!3NPOw$FsiZ=S-C=;SDOgU+>l{2l1n*q-}WSDYz@osaux zOoVKdtTy5<#U??d-XgXLOyT#`=_^-OKG2ekv-2O~f>#x>OOOm^!mDas#HsizSMm7! zf^0XF$FS1kbPDZ4OyP+!e&St!p|B&hjXALUW;D`B23uNaE#;VsBX8n)HTnR#5CS7c$yOxd5WLv0FzlbJA-JqowP#VuyTlA0G+DB?1MF3O#u+MC4ha?Bsk~)Dy`v;tl zNH{R`hRTdVr!AM}{aY0Q!N`C2hZo!wf%>0U6xMde0?7V2re}0l|HuKuNZIKf>zrZ3 z0F{DiSdD4XAiwK+&s4(k!~K-o6^Rt}9&S)3TjA4$9e?}M1f_n4KZwF9_TRR#p&j%IlU{|6v~5>-Lzh1J zO_0OuDtuM^0H|+_6MmC4&YGhAlL4)VlN=$cgL3F%VMAjHr=z>nrfSthKUu{m{p)s$ z54O>b`t3BW%w|b^jVQeopNZCY$x_LGK7S{6)B)*>5>gkB+-d|cC*9@wf4!(eI2tQJ z@2QJz27Nd4x$Q48`zG%wDnR z@Aac2tGE>X@F$&am9@EIw_BsH_|zvyE#|EENr}vs4n%7gxAOeIoa_cPW8ln1xM7Un zG=bfbGjmVVY`Yrn{iA(P(_BOEAJ}bFx}q~u{G;KCZEc1dtMqD8Y7oYcW77xFLMv#fxqm6%UGZWs8r=iM_vG$`;O^VpdnB-DW~o-Kkp<3E!;=+Bt@e8X{QOvS@ozR%2zs6d7O+U z<>xsn<(F;@l{Caneuv;+P#fV){b?6>B}CZ{6x%sE{Xd(*mCb&v`%6Q0zlC0he*uOn zoqcwa4QsDqWT@$ma0wtoWlVaCwgiC(gcrbta*KMFNn$YKGmy+`fLA&J;bzmBXP0l5 zuvo(#8zDIT4tKU^|I=GXyE`k4L9_~d6UIKQ9Z^t_67~(=I-Z(T$8nU)0@K(nTt%bZbX?leHJ*Iqv{hF`RpY`upme^PCTeygOnZ(JX*0s@>Y>)cT2 zcVism?BxE=%m`wBr4EieqohrJMCo(rWE8kXR>NiCpllnjRQMLL7K!Klhb z6cX4cs!qLtbjzeM#IDziVw!}+yAa(Kl_^0Bo>gXr9a~&-ZM|hp30v0k$+`a>iX~lF z4MdJ36Gs$ax_Ku)P0hTB1-^~8&WM9hg2Li3^2-}?&-QAqm+3Zb7V{1$UbXukpi_ha z;vkaD zqCAF+T2%o;)V>rAra2Woy*XY%n7a01@%nhvQ#HdLzU(K@;lpTHfTJmJp*YGep1su3 zEYPuR_eoR2S`g7kBZzA-#{W#ceRYkzWsPW0kn>e7lB))@xDx%o04L1uQ zD6Pa~iJ^(Dhizmw&xBViZL(3{8X0v6;dAZoB~B7f{K-ivIjO?K89@iXLy;$O|J@YN zf$-~_udb;FAL7g;pb~AK2mg|WPd|0%SQ0$-Q(}58$$Qfhp1!givWrh8>zbFv|5Y^_ z=5`m{M?tO^okE~ttr0dH}u@S=HX&tvmS#3poq^N_yDSe@V zXK)_AkbDhm-b}6uTO`$!Mnqht1k{@JhJyqxkMC*)iEAy%_1JPe3r#U6r z5{iXpOsg+>R=NF=RPPG}?O%&lZ=V_>US;f&ONRs}31qOj{~9LSB$6*_;Wsrnt*?$U zfoxneJ=b!L^P4Do7KLMQPrvI(O5u3g)=nHWK_PGDR>!kPRO5vw*|8N}S`cFgutX&- z=5?TO9pH8WoGIXSkA>hM)mP0dHfqUj8byjBPr@SV0b$Xl^sZ)t8DEB}KUNnUT@Loh z5kUz;jN!}iDKUr0=Bs+8aT*kr;xD z7nB7^TkOI!a%5tcvoE)bF>S)@kBEZB=@b7v$lurUzI3yZ4R%wX&-G%gL-@)G8`PCh zU2)On@V@CIhbi$Z1#BbS?CF<2DNH-XMB%!C2TD5+oWA{fO2Ims;fc@uXq=qN9e(Z< z-&kzY{3JIeEAz9LgM<#z%sS@HmDFFNoa)1n$eq--F0iGfBxm=hg9$6paw!{{AvNmR z0|fr>s>%YZr#JGov|+~bqCi>IgtTdigP6`jWQ>p;c|Cyn?J24ZUvI|wCRItKon{R1 zRV4pYsQVLBwyXs+k30pBbI(25ZGc#7Dpp79!BfUTiqEY#Loh;3PZfK^Kl#p94laXu zLVr)5W4@xH#(CS}y~?rLvL#~T#dcjcri-(FXEp4>3y_#g+sYm#Ec8&*4v(^0bH0Ao zOd=Wuzqy`Qjr`*d+`v~}wD7TTvtJtQn+;+pD7X0ny}h_J_E z^sPfwoL>yEM#db^kLEKf>DC13>lbX-zF#dCtk|m;;x1_-G&C(k>I4?51tWp>;pR%R zRRr>!UO_69)cq>|5A0^Az*w@3n4zs9$UsSpvPH+yd|J>fql8CxyBuBTG;v~}T;2ur zLfy<+TFQdH1}0YW4j=uLqh(BbyfaKb^Uz;b)-UWNUkwJdkLlBzCP3Tn+3})1!BA_W ztVR`h2WZm&g1)N06WU zFnzK)09&tNnFIA~>!iHig!GzbCx)MpocRUPHX+t_3=Mv`@36O=>>SlG<&LplQ*G5! zIN-|TDAZ=hND8S+P>8ddsWm>#(Zm-_@)L`RTrbHNV?6X7*^t}BT-079a^Mj1s)9@Y z#N9?jz#|s?o8>f-uQ656iu+5i{xOa*dW4xd zEgj{)oS?(W)L{VZ!hHSX_cvP1@N|ef!=_R0HV4}~KuD_s}K zPTK-SJLhg^SuL7NL){Ou{XaY8qj!{>8N?o9wwP}CF;|4!?LRg?a&~fYG)lr*-Y%NX z6N;V|C!Irc*({L{qx(Ki1P_GjqH?+_JwDvUbf;g2jDVRHMFIb92g*%PUPnR!J!+5w zzTzYg+!2P8Z2hTqT(_egQTIXrq7rb?UrD{rcU6ed@;vG4b0*q-VWimO@eEf=a(Hyn zHgz+T&dr=fM11kQzCwJ;clh#DGqzKk<>3V(0Il=QvOc;)W>a$no~(Vu0^@VSdk1PT z3NM7)fOU3`jH9--o1C1DQ;!mRtHSqf$pa*1iD}_T$pnzflXLKm%vK*LRQ#>sd zdV$hyed}M@QsR4b96i#L{kULeb3HQ;+s=FZRhX3;@%wafus@MtHTCC zVD#1cJ8D$yeGlj3`4%ejNAD#y^V`7X0aJ9ekHziYbWFZ|lpz5Deg@IY!r@2MyUj=c z)Xp1$slEK$^kWP4I`3U*BD2xc$;jjg&JfOXEvh-BbG>2OUdhLu&cnjZru1W^hS>=4 zbUbaMkA7dy?|2)@M~y|!`smm?oqBxQIREHhI!W#3vq3cw{zwNehW2pWQ@?GHbo1Z( z*MLHVIPb3KnDyVZ7oEg%&p)<`0U|Gl!xpOb_Ukq9`4|opVEOuQZ*lOW`-A>XnALH6 zTTVZj=#9q&j7-rR{t|kR{4PY?db5fF~G_5=9*s29;u77(d)hG zJ-6E(RJ3FBbPXpZBy`<0f~e)Y_X;uaK7KtONDe=c;tTnBe0^c+soW`~cH_?#Tt4)E z-;MR^Ui)ZEgt+*$TbJ(K7 z>q{uL@X8ZvcF&T*xzFaDQci?xkMsM6RL=3^T1%cdn%Y^K5e*oB3xtW@|~w*&YFrzXayKZ$I#e`wV~(V+|ihH!{vq^}pJ0VZ-` z-H?wiqxE&jGP~EoB;$eCWZS`dcD9Q-(VMNJ3<8loKZ>)@9+&Gl!vLx&D-$QpH=pWht+0&9DhXFI{l-4;h{OCcZA^MYM&Dxc1>qiwKk5$o?#ks`*VQga~3$g!4P0RGp#GJ@0 zmrSlf$$R6$4rKRCa#(|?7yOm9vju*TpL8hR!QtMC0*7mzQsXTP`6}UmZ`c59oGJ}o`L>Y-6(T^@h#DI$z61~=ppJLo{ky=W6%uFeF*f&GmuB9 z0+|k`gEgQnGJhLciZn+95K|-`@JKiCGcq2{KogNLWC$_}bw|#lXVCA^t>^%>7fJya zbS#pFmcyClEUH8@!5w4{IuX5zECzE?dsH9QMa+?V=o;`lNJp(u1h)48+yxkH`%~#9 zh(X1$90n|b2*E&0L=2BRA_zPu*5qcR&A_+FIhbP!lYbcIMJP7#Xp$pf5r02$3h9Ij?XL$(XbQ}|1eU?;Ps5`^ zcsvPN0BaSV)YVgSJ*nx91QZ}b4eKGLfuhrl7Kuy z${?0wP@pMahZrCO(Lu;#SfB8@6xoSh65WC&4uRE32na?lz`9yB8Vv&RMIesjKn=1D z>534@QDi>a4>dzg(35Bvbb;oHz>6Tsp@02GgR2^;S!%2<6*y}W0yP4GsO4zRU4=PW zkUXFi52NkDa&#oJ0oqz$kPCi;R*?+a!{h<$lfyh1r=h+mj!IE)5Q&DP9`H;S2t`o% zxi2iY7Um>_Bk=tYltdxaom{9ppO$ZbZ~^_$nzhMWbDf}rf$%6J!w^P+20;7#=YKl* z!tJ!jqL1WZCII|5mKUCJt>5UJ@RGb&9yBU1l)X`>eQjy+Vf_txjmx9;=g1)Kv8YiV z;iWzPSbso$y^Z_Rkk`0;S$%(mJhWa0@8^APc~Sj%tLobZjmlR;q7#sJS@e*L`mNf4969 zq^c&2*AghfLhu_{3Rb|VXfxOj_JC}_f)gMQoCB9Z3AhO!f~TMoR09;r$e3jbI3=VN zkTycv1&M`}2dN0sO-MYX8c6z9fNKs3hvWk(2vQ^@8Ki!YhC&(-iG~Eb0)KQM=>qK- zj3HS7?X&A`AwiSXJVQ}|%!SIQ*cRw|XYL#Gn%( z;5t75WMpiE?Hz=4*a~1dkUPQi-DHXd{G^!Wl+Wem)Pe&m_*nLLs&>vw-b=NLU#C#< zMszxF-fA@;KV=O+!+$NMn|}qV?gYJ0o~tq_$;JHA&H?bV8nYaGo^tJ4KK9fwbq@frUF~OYfIHVaibwG14pqoX5zj0lDY6Kic zhO?l%2>MPE>%fGFg=66WW@Ka-!E#qo@WD&Kw+;ICxPc2KhSHGL%YP#u%HRp4XHXum zAr~%DuDG3sC8ru>G(2#> zK>>GVEuY_2WEWt zJ-6L+YE{n{w3$N%Ul8yTMAf~dbgf^3(`B#t;8#`j>m$|tp({1?%yqA+rw3}mo*(OY zx0Qgtmx@qH(@|g_C!$J|#bC9!4sRBrOJ6k7qoQ2(fx%;aet(jVgbqC?;a}e|q;s|# zQEg5cgBNKgd`gBX?J*vsmi%G{`t>xY>_;^NWr59kSvL#1xs4^iP-O*TtE~7nlnwpu z6&pVExs(QnY$@uZ9q?Oa&#&3zK&R4<)aAuESf1=e^_$`h@JJWReV{Ad!_AF48rlNP z)^+DA>^Z$oPUevP_tOI+1^<0CLbrup4Ul% zlyp`fOpfQ|bK+%HYvZ|~Z+fWbZ0VsmP|`y-(=}1ScS?~Jh74}d_03%le%GL1Gc@}3 zA5Ixs{qAY6)#nCZDR9AV^ub^lvyIYtlLq^N=cp+npt!3CC^x9@o{%yiWkUTw54rFp z^cAlXet)Bz*PWoY$aCpO#kst1dI4DLe}#8{e1#nAR!u!QQcceqq0fJPLLV62HRNNG zjp(pb#?;eaP3VL|Q)-Pn2G;MD@}sRC=-c73l=J>rAa&_PU7Xj6X0LXpZhaL`XD#l+ z&-Lr7QmyEw+OIFem$i|pHl)f_Gp8n~&J-o6R)6YrSB)FlT{R`JhstSN4?Oy64^_|j zL{&elo;WwPr%JrJrz+5=m#V|dUaHwWdgEJW^u`k{;4}86;#_&E%45MWyvyNX7?C-=0sokf;s3q^Y$A%ne0j6-UEkE$|26hiw;)EX zZ2;EM25{ampZey~O2w%3Rn!xm3^3|W2EXCdR^Yo~8`Vvf37XGlz?3=LX@c7EcW}d$ za~-f5QysCfBXMj{KPN0P-WiJub-@CBTz@flJ2%YQsD&~@$6e|7!d>Zh%R^~-(Nn2+ z+)G)x%UgMGt&g%~QA_2SS*?@@##KVJ1I`slWV6d8oGxc5GdgC( z-92oq4qIoU(Ai`v8}}ooxbu^ldVQ+7;?&S)YVS_X6`Nx%WCOe{74tl-)M-Z6iqZNu zvJ;PNxL?Ypvd(H-?(6e*>S^2U@hCb-Cbd$yG-W;!X??}oPxyOb?bp~>IDf)%M!@lQ zsUI)+ZyE2+%A~(DUfQ8)klY~&mkLim7oI~Z z#Ng+t%|8N#Z0`ApjNc|bNk|v+5P*t=_FIeI)oo$z*r4M8yc@4ChHbBgv;p>G6Xe2L z#uSnnB%%8PbV97`0m&1R;D5uvL$vFH2^TxgYnWdeuMd8HZ~Tqh%Z6t$yTJ))S_~r|(y{5hRcszU&)^i2ccMWnuhQe@#aR@RM)@s&5Um&#tK=YKi z`LMHkumM9z7^=1Dn)7wP$V(n?S97_$mBs+U3tFMkGJuc8*IZ9 zQft^=8(S@2!gFn{QGcyWU2~qMW83#%Q*3o9#R?{saf9!aQDLobae13=@h7@IP;^YQ zq+aS$=0RJ2$}T(3YLz`@xX?lIc%~!OiNd*QFfKpW-I)_b zy6~uM=oz|!Akba7$=y=z$ahJW$l;EjEL7}W^4AzIt$ zXY_H|eoR-x;dmn5_VcE5Szn*JMZJDE4$57Jo7$366nfC~F;ca|ed5N87{Xx0w zxbw#2I|4d1LVtP3EfRYj5v4q(6OCPWiNS6^j=^$7aSa$hZx)@@Fn(vyl=)GsyK@b+ zV{OX%m4@S94QT_E!FI@nJ%YybpCWf|OU!OqD{$|-)>v&ULF=Tp!8i|J`c8CPWsR91 zeX51Ovb5Zv-mDv_WX}iE^KJ(zS8ordN1q5$&RWn8%zw=cRSp@`9*mmb0sFpB7?6!7 zvB4qXz_(jREJhjunzxR`JgXu>g-JAKSQ1T}SH>vakH*r^FUKhj)^wsT?d`05FjEuT zDDww)rI$_Yrp)Xp1A7$-$_38d!BVmZw#_gRe2e$Qln;7>uRw2X?3vymjFV%Do0Akn z9w#-(bAM82?c6QA1V1a%?gIR+B+k(88R%mlm+hzK|Hd-UfpV^ZR1IY=vD4akJHc)t zeT{0lI&#h#QkGmG8c z^YqwC%s_T;s{vcWOVq`jA^Eb{NWGsmCX2I8WPe4QOv(LAG1Vz>)w7~o12b#fygS+pSbdAYL&b?(Fpi6{G@)RUNY-;2#ZHZ2chD@T*e@mb+)Nw<#7hLi~QK5fG31YC7yh7)1i%Nf5r+=Y1U?~0%9a%ZY*-SJ-~p3JpUPn^2%#eW<=QH^1Bht7K)jh<5aDqr2!Bx>Oh|G=@XJ@)F)f%-JbQn8Mqk+h zU%otyxj&jz?VT0Q!#FZobsV#D9+jG!-nxqqDgOns?5 zrq!4C^DdF!9&%y-ml&V%8y9Dqr6ONv5q{;_ z`1sO&#I~RkqUu2jQ)Y7&Ps+YZSX5ubOP7}t%dTC=+a_{M-QwqXf1MXZ3R|IaFnLM% zBvj(Zb1RtwNtLQ{V-=zMuzyj zh70pF&<)SM;6_ZQ2~`*SHVod|7ay+MmPicp!*eSA2uD0XHS}BnbD|;;{~;@g_~Ux8 z%4dEEQ+KkR%4BpXvEW#H72mZ3G5C9#Di}{_3qEVVOA~*o3$*f7Uw?ed8^>RPP+lXT z+$P&=?M#?INB}hd+$Y!@F+XiJ+5Ou+%qi(!a=t?rL0!mVzq8uMtl7Pf++Mk#xcu7z z_Ga-xMl$ITX}$k2Grtc@{{DLoakSkLRz2+~Q(r$SAAypA{W&n%vHcZHGy{{Or6|S za0@_&Ih@kso}6=NIhp?#C$ zk%sh+(u?Xz6A z7Hn@AUtGE2PX7GZi!~jzOvuY{U^@%^U&O z{vUvc#x{26yOS+iczkO9`J_K%-ukl+pXblSjUAfCpMT~2J&YQ!<2)cHLm-WS_)K-w z+K%vVy=(p$v32QARYlY;M(B; zi--K5Fn`}Pb?$G#_-e44oSB+s8AKecDD zw%)&med+qM-p5`20w1>sVp0g{GQ{Vu)Bi{;&^7y`vA|F7`L};pJW%-s{;dXL66_4P zFlT_tAQyhqt9)UVMV5H$Fno<3JN}%6Ilb7BeYM?)sGVd?wp?h!wCQ8YD#l|>M2Z=? zsDGz9b27RaX%p0(sByPo<83UN)&^E2U1dcKx^KV??!NOXs#?9(QX^nvWugp#g2!R)4*HnH{EWSuD_xI9KV%KD7*B9+U;J7a%72 zIYI26OTkQLdI(u~xE--4Gn7@1X-_Pf-+|oPCybafnj{~O31_yn?MSKdkTnV(MC^7wG`F6)Y?#dUjY-(Qe2>hIOK^5=W!l~B%sE&z*l(aM{;yJsEcU7o4v zHDnLfK76ktz$lCV@pYDB%>6UitkgEN+VmN_y%BJdVhi7rEL&q zbSDTDR|hN2Uxa}3d)g@_Oepw$e|zP_`5nOY7o87!Q_C#mbS5XKroKp`7qES({uld#ZJYX0;?s=w@F^Hb(7-Aos1&$#hVpt zbGH;dpRiT&ZpXiW^sUT7FDKGT2X<#lv|m0UH&AT2)PDMN~x_<9Dx7npaUQDWfhR$t;Tv6)qs$buYa-NtXg2Vu@19c0zmC-1baOZrKMv; zN}FUcT_e*`R+GAPDWRuy+@VLam;ttZs{y^5mnhRYLvXm*NclZ$4AQerFx4hg@cmK@ zn>oh}44!O`r3`BZVv?F;(Qy{QGuRUI@U)_%oUJj5fer1BN|i01N@;@|wo3g1JNiM6 zy?^rlb_Y5?-4U~&fYX^XoUpo{&LD5N3s&au3bu7}!;V_C07@@+<@H*3FjnHJ%qjH* ziTAyfYYutSRH2XZhm|eq?mJs4Q>V42{T32Uojb9|CqlsfD@~p|vE$F8z|@v+)(Nas zthvvyPlJAZa>DwOq@C+b7i#gR=fCBG9)G+)_q~5VJBW~f#jcnJE*-K#GkGXmfaJFd@l9^-tk`)FN%acm3Ol0Z_5Nt ziDer6fbJWdwd+Fx=OrR7&fh?6k3x(uL*5sb^_%h^@TCrN7h>8C7ZwD$5f?AGk$?71 z?o9bX&0Yzybde`(d*6#8M|hJ*Ss$WcNK0~^sui&-x-~hQCYZkVnsp*ENw+OIFi5jb zBwOME%!G3R@A&AD`5~yZFgk+PPymljrc=%Hvc2sF!;_T3Vf`0V<()Gk& z%rwHhkeB!8^UPz2ediVc8`MJk+apEEo^{~%&z9=EiPm63Y8y&_oG*~aw11`UclYDH z-Tf8dm_L>08vyL|0{OddZG{%U2;q!b@4!>b!)SIiNzI!TPOt9P zks6&6LC3xs{FhWWnn{r>#fck6n8 z&0YZOxD4wmZlR4Ig#VbNp@Sa{E7>L-`>bC{z1p{mHu+ISU0Ie6#($q#&A+GkFoY)U;-#oyV zW*!75j~(LK`G>)!K7T9~%H)8#vySkyx*Y|hQjYOMS|6u>?s9^UHO-|*wmeA%e(Bo}~Z-xTr>$P3_N&x?FMehKXLzszTXBCyn=m|wsZ)4OX+_;KtN zdU5GhO1bG8J^4^6HFnN*y6;MkN*q>3x1V-{3W)oIcI00;NG zJg~Y4XUhBhjf(r=#_b3Eu}crZu@jG|vcr#QF7pX>bQMn@nO{z=oBotuJNg+ld%$!0 z$8ImEVPO^Y(AF>cA3Q4QPNuK;RQ)QDT3N-%-K_?3MK!$l$=AR;tClz3SO<)k0RG8r z1U#9DQbuD$w0~i;n0hSJp&yaDl$y|^-RJ31J1_&VW~%|Uh?md{IYXW)Hln{_jrp`} z6Ogvalpnto1LNnI@x3OSgI>d$@j*$=K~S6pFAcT?Qco)?(AgT)=-W^>sFb#TDy6E+ zZ0V}=cGR^Td;02j2kLOTBQTwSQ|U9DKzUDR{@`#IP=Dy}%CG9=26kJt;AeQb)8}j5 z`9Ts-dS|I8A9df0{?{RIYG9!cJ#l4ADspElI%!&Ks>MR$eV+kB+&}6s0CysU-*TnN z{sQcM7R7)4Hl|Y5iuz|Z)qnnf*;8wpPZs>bg^#Rt8vCG+-J1%_yot|lL7Tq!|Jb`0 zuo}`nK7W)-Nkh69BXQ2@qDe(pjnsCM^rp+6D27F%<*X&FvpH-epVc7OY>j(at4S`| zlyS+nu2U(cWQep0C#_3$!=ttZLh9d4fPYPv1VMG{0O33>7%GaLNEnR?fewxj zC0dX$Xq|01Q3ZV{%`gxV`K(B~_E`jxPEV4?mQ99FWkyOT9E*a}lBP(}%~N4%3=Rdx zPlKoXPlxQMeB{xYA5vntel0VUc&~Lke_d{iHXyQBX-I)$D3AvM5O4>kOuh8^!lCa zuh0ht4{U!S54!VZ6*{5ldw1vfv5e!(I)A>ZKQGX*eh=4ruYT;c;I{gW> zt8noY3iDi?%|K(0&|*;zaI!p#?R7oIM5`UAZnp7~|wL4WLJ ztk&o{9p+euZHz0!Jc?gYtsC3vE0)%N zZ~tHbwXvT8y=<5vwXn&Mj?yru!heg6X|FmH$|c(ri@at=nQk!0MrT=2a`8Y6Ngjl% zEl04P^9Q4Cu9n!n2}96ZCRR#+{dCJv`sLGM=&nj0ef|ZX%0DZh_usIl(s$d?Yfjiw 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%0DfK>Eo+`&!djNedE2dju!On??f7W(>9Jo4p#M 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

jesdiz;}E^aoUwZjj<+|m~4=(VDpYps*@xb7NR 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 JL<-I6{|%-r#t{Gj delta 66021 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 E0moE$MF0Q* diff --git a/extern/icu/tzdata/version.txt b/extern/icu/tzdata/version.txt index 9217a2da674..b62e2687afb 100644 --- a/extern/icu/tzdata/version.txt +++ b/extern/icu/tzdata/version.txt @@ -1 +1 @@ -2026c +2026d diff --git a/src/common/TimeZones.h b/src/common/TimeZones.h index e0002527c27..cdb60110a99 100644 --- a/src/common/TimeZones.h +++ b/src/common/TimeZones.h @@ -1,6 +1,6 @@ // The content of this file is generated with help of update-ids utility Do not edit. -static const char* BUILTIN_TIME_ZONE_VERSION = "2026c"; +static const char* BUILTIN_TIME_ZONE_VERSION = "2026d"; // Do not change order of items in this array! The index corresponds to a TimeZone ID, which must be fixed! static const char* BUILTIN_TIME_ZONE_LIST[] = { From 4e3513cd29ef1e4a3bd574e2903d947e471bdfdb Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Wed, 23 Sep 2026 17:27:03 +0300 Subject: [PATCH 14/30] Fix #9151: 'no current record for fetch operation' on a LEFT JOIN to an aggregated derived table --- src/jrd/optimizer/Optimizer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/jrd/optimizer/Optimizer.cpp b/src/jrd/optimizer/Optimizer.cpp index 2eccaef6e85..450d03f1864 100644 --- a/src/jrd/optimizer/Optimizer.cpp +++ b/src/jrd/optimizer/Optimizer.cpp @@ -1046,7 +1046,6 @@ RecordSource* Optimizer::compile(BoolExprNodeStack* parentStack) // dependent rivers from hashing or sort/merging that happens below. rivers.add(FB_NEW_POOL(getPool()) CrossJoin(this, rivers, JoinType::INNER)); - rivers.back()->activate(csb); } // Now process streams dependent on rivers that are dependent themselves From 04f96984b0872254066ff2295933a37e839b2359 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 24 Sep 2026 23:00:27 +0000 Subject: [PATCH 15/30] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index c4c583ecd9f..631fc3e3c6e 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:2183 + FORMAL BUILD NUMBER:2185 */ -#define PRODUCT_VER_STRING "6.0.0.2183" -#define FILE_VER_STRING "WI-T6.0.0.2183" -#define LICENSE_VER_STRING "WI-T6.0.0.2183" -#define FILE_VER_NUMBER 6, 0, 0, 2183 +#define PRODUCT_VER_STRING "6.0.0.2185" +#define FILE_VER_STRING "WI-T6.0.0.2185" +#define LICENSE_VER_STRING "WI-T6.0.0.2185" +#define FILE_VER_NUMBER 6, 0, 0, 2185 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "2183" +#define FB_BUILD_NO "2185" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 8f2bf03f27f..0b1b6bbfd47 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=2183 +BuildNum=2185 NowAt=`pwd` cd `dirname $0` From 4a219781da1a00be6d4e207f6a3e491413d7485e Mon Sep 17 00:00:00 2001 From: AlexPeshkoff Date: Sat, 26 Sep 2026 00:07:56 +0300 Subject: [PATCH 16/30] Disable DDL under user savepoint --- src/dsql/DsqlRequests.cpp | 9 +++++++++ src/include/firebird/impl/msg/jrd.h | 1 + src/include/gen/Firebird.pas | 1 + 3 files changed, 11 insertions(+) diff --git a/src/dsql/DsqlRequests.cpp b/src/dsql/DsqlRequests.cpp index 26a7f059efe..231b9b25106 100644 --- a/src/dsql/DsqlRequests.cpp +++ b/src/dsql/DsqlRequests.cpp @@ -979,6 +979,15 @@ void DsqlDdlRequest::execute(thread_db* tdbb, jrd_tra** traHandle, //// Doing it in DFW_perform_work to avoid problems with DDL+DML in the same transaction. /// req_dbb->dbb_attachment->att_dsql_instance->dbb_statement_cache->purgeAllAttachments(tdbb); + // Check for presence of user savepoints + for (Savepoint::Iterator iter(req_transaction->tra_save_point); *iter; ++iter) + { + Savepoint* const current = *iter; + + if (!current->isSystem()) + (Arg::Gds(isc_user_savepoint) << current->getName().c_str()).raise(); + } + node->executeDdl(tdbb, internalScratch, req_transaction); const bool isInternalRequest = diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index 31b3e13807a..a9d76243c7b 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -1022,3 +1022,4 @@ FB_IMPL_MSG(JRD, 1019, dsql_agg_exit_group, -204, "42", "000", "EXIT is not allo FB_IMPL_MSG(JRD, 1020, dsql_agg_return, -204, "42", "000", "RETURN is not allowed in ON START DO, ON ACCUMULATE DO or ON FINISH DO sections of aggregate function; use EXIT instead") FB_IMPL_MSG(JRD, 1021, hypfun_args_non_equal_sort_item, -833, "42", "000", "Number of arguments of hypothetical-set aggregate function @1 must match number of sort items in WITHIN GROUP clause") FB_IMPL_MSG(JRD, 1022, old_format, -804, "07", "000", "Statement format outdated, need to be reprepared") +FB_IMPL_MSG(JRD, 1023, user_savepoint, -901, "0A", "000", "Running under user savepoint @1, DDL prohibited") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 03819b9c107..7510eaa9468 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -6073,6 +6073,7 @@ IPerformanceStatsImpl = class(IPerformanceStats) isc_dsql_agg_return = 335545340; isc_hypfun_args_non_equal_sort_item = 335545341; isc_old_format = 335545342; + isc_user_savepoint = 335545343; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; From 44c9e4ce2320e5fb96c220004efc3c44fc8a1e7d Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 25 Sep 2026 23:04:41 +0000 Subject: [PATCH 17/30] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 631fc3e3c6e..5f3613327c8 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:2185 + FORMAL BUILD NUMBER:2186 */ -#define PRODUCT_VER_STRING "6.0.0.2185" -#define FILE_VER_STRING "WI-T6.0.0.2185" -#define LICENSE_VER_STRING "WI-T6.0.0.2185" -#define FILE_VER_NUMBER 6, 0, 0, 2185 +#define PRODUCT_VER_STRING "6.0.0.2186" +#define FILE_VER_STRING "WI-T6.0.0.2186" +#define LICENSE_VER_STRING "WI-T6.0.0.2186" +#define FILE_VER_NUMBER 6, 0, 0, 2186 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "2185" +#define FB_BUILD_NO "2186" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 0b1b6bbfd47..4cf3c17cc35 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=2185 +BuildNum=2186 NowAt=`pwd` cd `dirname $0` From 3a6212f71fd9b126d888930b17707c2d4a51d2bd Mon Sep 17 00:00:00 2001 From: TreeHunter <60896014+TreeHunter9@users.noreply.github.com> Date: Sat, 26 Sep 2026 08:10:51 +0300 Subject: [PATCH 18/30] Correct the behavior of `PoolAllocator` according to implementation of our internal allocator (#9152) Co-authored-by: Artyom Ivanov --- src/common/classes/alloc.h | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/common/classes/alloc.h b/src/common/classes/alloc.h index ec82044d027..87a16731635 100644 --- a/src/common/classes/alloc.h +++ b/src/common/classes/alloc.h @@ -481,7 +481,19 @@ namespace Firebird using void_pointer = void* ; using const_void_pointer = const void*; using difference_type = std::ptrdiff_t; - using is_always_equal = std::true_type; + + // We are using stateful allocator, so they are not always equal by definition. + using is_always_equal = std::false_type; + + // Do not propagate allocator on any type of assignment or swap (but remember, swap on two objects with + // different allocators is UB if swap propagation is false, so be careful with it), because it can lead + // to accidental misuse or undesired behavior, e.g.: + // `std::string str1(default_pool, std::move(std::string(req_pool, "some string")));` + // Where `str1` will borrow a pointer from `req_pool`, but `str1` can live longer then `req_pool`, so + // when `req_pool` is gone, we will have a dead pointer. + using propagate_on_container_copy_assignment = std::false_type; + using propagate_on_container_move_assignment = std::false_type; + using propagate_on_container_swap = std::false_type; template struct rebind From 08b41ce54d84df59a83fb5e47ca716eb89fc8c9f Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 26 Sep 2026 22:22:03 +0000 Subject: [PATCH 19/30] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 5f3613327c8..30708a878fd 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:2186 + FORMAL BUILD NUMBER:2187 */ -#define PRODUCT_VER_STRING "6.0.0.2186" -#define FILE_VER_STRING "WI-T6.0.0.2186" -#define LICENSE_VER_STRING "WI-T6.0.0.2186" -#define FILE_VER_NUMBER 6, 0, 0, 2186 +#define PRODUCT_VER_STRING "6.0.0.2187" +#define FILE_VER_STRING "WI-T6.0.0.2187" +#define LICENSE_VER_STRING "WI-T6.0.0.2187" +#define FILE_VER_NUMBER 6, 0, 0, 2187 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "2186" +#define FB_BUILD_NO "2187" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 4cf3c17cc35..f77108dcb2c 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=2186 +BuildNum=2187 NowAt=`pwd` cd `dirname $0` From 4b0b4777b8c0c4af89dd5cc3156728014e951e48 Mon Sep 17 00:00:00 2001 From: AlexPeshkoff Date: Sun, 27 Sep 2026 20:13:01 +0300 Subject: [PATCH 20/30] No matter that deadlock does not happen it will be good idea to unlock dbb_sync before tarcing successful attach --- src/jrd/jrd.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/jrd/jrd.cpp b/src/jrd/jrd.cpp index 063fc5ac05a..fdb63403818 100644 --- a/src/jrd/jrd.cpp +++ b/src/jrd/jrd.cpp @@ -3218,9 +3218,10 @@ JAttachment* JProvider::createDatabase(CheckStatusWrapper* user_status, const ch // Initialize TIP cache dbb->dbb_tip_cache = TipCache::create(tdbb); - // Init complete - we can release dbInitMutex + // Init complete - we can release dbInitMutex & dbb_sync dbb->dbb_flags &= ~(DBB_new | DBB_creating); guardDbInit.leave(); + dbbGuard.unlock(); REPL_attach(tdbb, false); From 4f59e76485929c724ecdb0ac12923a1549fe7b82 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 27 Sep 2026 22:47:56 +0000 Subject: [PATCH 21/30] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 30708a878fd..2eb09e3597f 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:2187 + FORMAL BUILD NUMBER:2188 */ -#define PRODUCT_VER_STRING "6.0.0.2187" -#define FILE_VER_STRING "WI-T6.0.0.2187" -#define LICENSE_VER_STRING "WI-T6.0.0.2187" -#define FILE_VER_NUMBER 6, 0, 0, 2187 +#define PRODUCT_VER_STRING "6.0.0.2188" +#define FILE_VER_STRING "WI-T6.0.0.2188" +#define LICENSE_VER_STRING "WI-T6.0.0.2188" +#define FILE_VER_NUMBER 6, 0, 0, 2188 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "2187" +#define FB_BUILD_NO "2188" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index f77108dcb2c..a783efa4432 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=2187 +BuildNum=2188 NowAt=`pwd` cd `dirname $0` From 3ae1cd8753210e534278649933298262b05dfba9 Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes <529415+asfernandes@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:27:20 -0300 Subject: [PATCH 22/30] Feature #9154 - gbak FAST_PATH mode (#9155) --- builds/posix/make.shared.variables | 6 +- builds/win32/msvc15/burp.vcxproj | 5 + builds/win32/msvc15/burp.vcxproj.filters | 17 +- builds/win32/msvc15/engine_static.vcxproj | 3 + .../msvc15/engine_static.vcxproj.filters | 9 + builds/win32/msvc15/gbak.vcxproj | 3 + builds/win32/msvc15/gbak.vcxproj.filters | 11 +- doc/README.gbak_fast_path.md | 156 +++++++ src/CMakeLists.txt | 14 +- src/burp/BurpTasks.cpp | 86 +++- src/burp/BurpTasks.h | 85 +++- src/burp/FastPathBurpProvider.h | 62 +++ src/burp/FastPathRecordReader.h | 68 +++ src/burp/FastPathRecordWriter.h | 81 ++++ src/burp/RestoreMessageLayout.cpp | 172 ++++++++ src/burp/RestoreMessageLayout.h | 96 +++++ src/burp/backup.epp | 181 ++++++-- src/burp/burp.cpp | 15 + src/burp/burp.h | 3 + src/burp/burpswi.h | 4 +- src/burp/engine/FastPathBurpUtil.cpp | 69 +++ src/burp/engine/FastPathRecordReader.cpp | 402 ++++++++++++++++++ src/burp/engine/FastPathRecordWriter.cpp | 390 +++++++++++++++++ src/burp/main/FastPathBurpProviderStub.cpp | 60 +++ src/burp/main/FastPathRecordReaderStub.cpp | 71 ++++ src/burp/main/FastPathRecordWriterStub.cpp | 66 +++ src/burp/restore.epp | 183 +++++++- src/include/firebird/impl/consts_pub.h | 2 + src/include/firebird/impl/msg/gbak.h | 5 + src/include/gen/Firebird.pas | 5 + src/jrd/blb.cpp | 64 ++- src/jrd/blb.h | 6 + src/jrd/svc.cpp | 2 + src/utilities/fbsvcmgr/fbsvcmgr.cpp | 2 + 34 files changed, 2326 insertions(+), 78 deletions(-) create mode 100644 doc/README.gbak_fast_path.md create mode 100644 src/burp/FastPathBurpProvider.h create mode 100644 src/burp/FastPathRecordReader.h create mode 100644 src/burp/FastPathRecordWriter.h create mode 100644 src/burp/RestoreMessageLayout.cpp create mode 100644 src/burp/RestoreMessageLayout.h create mode 100644 src/burp/engine/FastPathBurpUtil.cpp create mode 100644 src/burp/engine/FastPathRecordReader.cpp create mode 100644 src/burp/engine/FastPathRecordWriter.cpp create mode 100644 src/burp/main/FastPathBurpProviderStub.cpp create mode 100644 src/burp/main/FastPathRecordReaderStub.cpp create mode 100644 src/burp/main/FastPathRecordWriterStub.cpp diff --git a/builds/posix/make.shared.variables b/builds/posix/make.shared.variables index c0e1cc0ab8c..d12ec2ac449 100644 --- a/builds/posix/make.shared.variables +++ b/builds/posix/make.shared.variables @@ -114,10 +114,10 @@ GFIX_Objects:= $(Svc_GFIX_Objects) $(call dirObjects,alice/main) AllObjects += $(GFIX_Objects) # gbak -Svc_GBAK_Objects:= $(call dirObjects,burp) -GBAK_Objects:= $(Svc_GBAK_Objects) $(call dirObjects,burp/main) +Svc_GBAK_Objects:= $(call dirObjects,burp) $(call dirObjects,burp/engine) +GBAK_Objects:= $(call dirObjects,burp) $(call dirObjects,burp/main) -AllObjects += $(GBAK_Objects) +AllObjects += $(GBAK_Objects) $(call dirObjects,burp/engine) # gsec Svc_GSEC_Objects:= $(call dirObjects,utilities/gsec) diff --git a/builds/win32/msvc15/burp.vcxproj b/builds/win32/msvc15/burp.vcxproj index ef564dc7f9c..cde4c981601 100644 --- a/builds/win32/msvc15/burp.vcxproj +++ b/builds/win32/msvc15/burp.vcxproj @@ -193,6 +193,7 @@ + @@ -211,10 +212,14 @@ + + + + diff --git a/builds/win32/msvc15/burp.vcxproj.filters b/builds/win32/msvc15/burp.vcxproj.filters index 93cc7a69136..e4a1a947a18 100644 --- a/builds/win32/msvc15/burp.vcxproj.filters +++ b/builds/win32/msvc15/burp.vcxproj.filters @@ -24,6 +24,9 @@ BURP files + + BURP files + BURP files @@ -70,6 +73,15 @@ Header files + + Header files + + + Header files + + + Header files + Header files @@ -82,8 +94,11 @@ Header files + + Header files + Header files - \ No newline at end of file + diff --git a/builds/win32/msvc15/engine_static.vcxproj b/builds/win32/msvc15/engine_static.vcxproj index 8c4feaad72f..cc9f09abc13 100644 --- a/builds/win32/msvc15/engine_static.vcxproj +++ b/builds/win32/msvc15/engine_static.vcxproj @@ -200,6 +200,9 @@ + + + diff --git a/builds/win32/msvc15/engine_static.vcxproj.filters b/builds/win32/msvc15/engine_static.vcxproj.filters index 9b99e9ebb8f..cc260a038df 100644 --- a/builds/win32/msvc15/engine_static.vcxproj.filters +++ b/builds/win32/msvc15/engine_static.vcxproj.filters @@ -432,6 +432,15 @@ Services + + Services + + + Services + + + Services + JRD files\GPRE cpp diff --git a/builds/win32/msvc15/gbak.vcxproj b/builds/win32/msvc15/gbak.vcxproj index 7dd90063bd5..40d3dc5e3ee 100644 --- a/builds/win32/msvc15/gbak.vcxproj +++ b/builds/win32/msvc15/gbak.vcxproj @@ -285,6 +285,9 @@ + + + diff --git a/builds/win32/msvc15/gbak.vcxproj.filters b/builds/win32/msvc15/gbak.vcxproj.filters index 3bbffdaef90..adf432c7e21 100644 --- a/builds/win32/msvc15/gbak.vcxproj.filters +++ b/builds/win32/msvc15/gbak.vcxproj.filters @@ -51,5 +51,14 @@ BURP files + + BURP files + + + BURP files + + + BURP files + - \ No newline at end of file + diff --git a/doc/README.gbak_fast_path.md b/doc/README.gbak_fast_path.md new file mode 100644 index 00000000000..66c95a74100 --- /dev/null +++ b/doc/README.gbak_fast_path.md @@ -0,0 +1,156 @@ +# gbak FAST_PATH mode + +`FAST_PATH` is an optional, non-default backup and restore mode available through the Services Manager. It is intended to make +data-heavy `gbak` operations faster by moving table data directly between the `gbak` service and the database engine internal layers, +with less per-record processing. + +The backup format is unchanged. A backup created with `FAST_PATH` can be restored normally, and an ordinary backup can be +restored with `FAST_PATH`. Database creation and metadata restore continue to use the normal restore process. + +## When to use it + +### Speeding up large operations + +`FAST_PATH` is most useful for large backups or restores where moving and processing many rows is significant. It can +reduce CPU use and elapsed time, but the improvement depends on the database, data types, storage, and number of +parallel workers. Normal restore already has batch and bulk paths, so measure `FAST_PATH` against the current normal +restore with a representative backup or restore before making it the default for all jobs. + +The mode is particularly suitable for data-heavy operations on persistent user tables. For restore, use a trusted backup +and plan appropriate post-restore data-quality checks, because the direct data path bypasses per-row validation performed +by some normal restore paths. + +### Recovering data when normal restore fails + +`FAST_PATH` restore can also be worth trying when a normal restore fails and the goal is to salvage the data. This works +only for a specific class of failures: normal restore rejects rows through its statement machinery — per-row `NOT NULL` +and domain validation expressions, and request/BLR compilation for the statement path. A backup whose rows no longer +satisfy the currently defined rules (for example, taken before a `NOT NULL` or domain constraint was added, or loaded +while enforcement was disabled) can therefore abort a normal restore while the stored values themselves are intact. + +`FAST_PATH` skips that validation pass and the statement machinery: it converts each backed-up message directly into an +engine record with the same conversion routines normal storage uses, then buffers it through bulk insert. Rows rejected +only by validation can load this way. + +Limits to keep in mind: the direct path still checks message lengths and field mappings and still converts data types, +and it is fail-fast — a conversion or engine error aborts the restore rather than skipping the row. It will not rescue +a corrupt or truncated backup stream, missing tables or columns, or unreadable blobs. Treat the recovered database as +containing potentially rule-violating rows: validation metadata itself is restored normally, so later DML is still +checked, but the loaded rows are not retrospectively validated. Inspect the data, fix or remove offending rows, and run +explicit post-restore checks before putting the database into production. + +## Restore semantics + +`FAST_PATH` changes the table-data transfer path, not the backup format or the normal metadata restore process. For eligible +persistent user tables, restore converts backed-up values directly into engine records (using the same conversion routines as +normal record storage) and buffers them through the engine's bulk-insert mechanism, bypassing normal restore statement +execution and its per-row validation processing. + +This must not be confused with replaying application `INSERT` statements. Ordinary `gbak` restore also restores explicit +stored values rather than regenerating column defaults or identity values, and does not replay user DML trigger side effects. +Table `CHECK` constraints implemented through triggers are not a per-row validation guarantee of ordinary restore either. + +The validation difference depends on the normal restore path being compared. Statement-based stores can evaluate field +validation expressions; the normal bulk-insert path has an explicit `NOT NULL` check but not the general store +validation-expression pass. `FAST_PATH` bypasses that `NOT NULL` check as well: the direct writer does not evaluate per-row +`NOT NULL` or domain validation expressions. + +Unless affected by other restore options, validation metadata is restored normally. Retaining that metadata does not +retrospectively validate rows loaded through the direct writer. + +Do not treat either restore mode as a complete audit of application data rules. If additional assurance is required, run +explicit post-restore checks for the relevant domain rules, table constraints, and application invariants. Ordinary restore +is not a way to replay trigger side effects. + +The direct path still checks message lengths and field mappings and performs required data-type conversions; it is not a +complete backup-integrity validator. + +`NO_VALIDITY` (`-no_validity`) is separate from `FAST_PATH`. It omits restored column and domain nullability flags and domain +validation BLR and source; it is not merely a temporary instruction to skip checks during data loading. Those omitted rules are +consequently unavailable for later enforcement unless recreated. `FAST_PATH` alone does not remove those definitions. Conversely, +`NO_VALIDITY` is not a blanket removal of all constraints: table `CHECK` trigger metadata and index-backed primary-key, unique, +and foreign-key constraints follow their normal restore paths. + +## Usage + +`FAST_PATH` requires the `-service` option (Services Manager). It works both via `gbak -service ... -fast` (or `-fast_path`) and via +`fbsvcmgr` / Services API options. + +With `fbsvcmgr`, use `bkp_fast_path` for backup and `res_fast_path` for restore. Paths in these examples are server-side. +For example: + +```text +fbsvcmgr localhost:service_mgr \ + -user SYSDBA -password \ + action_backup dbname /path/source.fdb \ + bkp_file /path/source.fbk bkp_fast_path +``` + +```text +fbsvcmgr localhost:service_mgr \ + -user SYSDBA -password \ + action_restore bkp_file /path/source.fbk \ + dbname /path/restored.fdb res_fast_path +``` + +The same mode is available through `gbak -service`, which forwards its switches via the service command line: + +```text +gbak -service localhost:service_mgr -user SYSDBA -password \ + -backup /path/source.fdb /path/source.fbk -fast_path +``` + +```text +gbak -service localhost:service_mgr -user SYSDBA -password \ + -create_database /path/source.fbk /path/restored.fdb -fast_path +``` + +Services API clients set the corresponding bit in the service-start `isc_spb_options` value: + +| Operation | `fbsvcmgr` option | Services API option bit | +|-----------|-------------------|-------------------------| +| Backup | `bkp_fast_path` | `isc_spb_bkp_fast_path` (`0x040000`) | +| Restore | `res_fast_path` | `isc_spb_res_fast_path` (`0x040000`; backup and restore option bits live in separate per-action namespaces) | + +The option can be combined with the usual backup, restore, and parallel worker options. `FAST_PATH` does not change the normal +table-data selection rules. With `METADATA_ONLY`, backup omits table rows, and restore consumes but does not load any table rows +present in the backup; the direct row-transfer path is therefore not exercised. + +## Scope and eligibility + +The direct data path targets persistent user tables. Normal `gbak` selection happens before `FAST_PATH` eligibility is tested: +system relations with `RDB$SYSTEM_FLAG = 1` are excluded from the backed-up relation list, views do not have their rows backed up, +and external-table rows are omitted unless external-table conversion is requested. Temporary-table contents are not persistent +database data. + +Restore uses the direct data path only for backups in format 8 or newer; tables from older backups are silently loaded through +the normal restore path. + +Privileges are the same as for a normal backup: besides the `USE_GBAK_UTILITY` requirement for the attachment, the backup +checks schema `USAGE`, table `SELECT` and column `SELECT` for every table it reads directly. + +Because `FAST_PATH` uses the engine provider directly instead of the provider list, database names must be local to the server +(a path or an alias). Names with a remote prefix such as `host:path` or `inet://host/path` are not routed and fail. + +## Architecture + +`FAST_PATH` selects the in-process engine provider. Direct VIO access needs an engine `JAttachment`, which the +Services Manager yvalve handle is not, so backup workers always use a private engine attachment - including with a +single worker. Those worker transactions join the main backup snapshot with `isc_tpb_at_snapshot_number` (the same +mechanism as `-parallel` workers). +The reader scans records with `VIO_next_record()` and fills the normal backup message layout; downstream backup serialization +remains unchanged. + +Restore retains normal metadata processing, backup decoding, and blob and array handling. Eligible table-data workers convert each +message into an engine record and buffer it through the transaction's bulk-insert page images, the same mechanism the normal +restore batch path uses to reserve and fill data pages. The direct path is fail-fast: a conversion or engine error aborts the restore +rather than skipping a row. This is also true when `-incremental` is specified; normal-mode relations retain their existing +incremental error handling. Rows buffered in the bulk-insert images are flushed at table end and at commit. In-memory batches use an aligned +stride distinct from the serialized message length so packed records keep field alignment; the backup format itself is unpadded. + +## Verification + +`FAST_PATH` is a trade-off for trusted backups. Restore-test backups, check relevant data rules with application-specific queries, and +benchmark representative scalar-heavy, blob-heavy, and array-heavy workloads with one and multiple workers before enabling the option in +production jobs. CPU, elapsed time, and peak memory should all be compared, since the direct path changes allocation and batching behaviour +as well as per-row CPU. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 13059dc5ebe..899fc183bac 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -323,6 +323,7 @@ add_library (alice ${alice_src} ${alice_generated_src_master}) ######################################## file(GLOB burp_src "burp/*.cpp" "burp/*.h") +file(GLOB burp_engine_src "burp/engine/*.cpp" "burp/engine/*.h") set(burp_generated_src burp/backup.epp @@ -459,6 +460,7 @@ set(engine_src ${engine_src} utilities/gsec/gsec.cpp utilities/gstat/ppg.cpp utilities/nbackup/nbackup.cpp + ${burp_engine_src} # parse ${GENERATED_DIR}/dsql/parse.cpp ) @@ -761,11 +763,17 @@ target_link_libraries (gfix alice common yvalve) # EXECUTABLE boot_gbak ######################################## -file(GLOB gbak_include "burp/*.h") +file(GLOB gbak_include "burp/*.h" "burp/engine/*.h") + +set(gbak_stub_src + burp/main/FastPathBurpProviderStub.cpp + burp/main/FastPathRecordReaderStub.cpp + burp/main/FastPathRecordWriterStub.cpp +) if (NOT CMAKE_CROSSCOMPILING) -add_executable (boot_gbak burp/main/burpMain.cpp ${gbak_include} ${VERSION_RC}) +add_executable (boot_gbak burp/main/burpMain.cpp ${gbak_stub_src} ${gbak_include} ${VERSION_RC}) target_link_libraries (boot_gbak boot_burp common boot_yvalve) set_output_directory (boot_gbak . CURRENT_DIR) project_group (boot_gbak Boot) @@ -777,7 +785,7 @@ endif() # if (NOT CMAKE_CROSSCOMPILING) # EXECUTABLE gbak ######################################## -add_executable (gbak burp/main/burpMain.cpp ${gbak_include} ${VERSION_RC}) +add_executable (gbak burp/main/burpMain.cpp ${gbak_stub_src} ${gbak_include} ${VERSION_RC}) target_link_libraries (gbak burp common yvalve) diff --git a/src/burp/BurpTasks.cpp b/src/burp/BurpTasks.cpp index 2235328f102..c4345d6723b 100644 --- a/src/burp/BurpTasks.cpp +++ b/src/burp/BurpTasks.cpp @@ -31,6 +31,7 @@ #include "../common/classes/SafeArg.h" #include "../burp/burp_proto.h" #include "../burp/mvol_proto.h" +#include "../burp/FastPathBurpProvider.h" using MsgFormat::SafeArg; using namespace Firebird; @@ -141,7 +142,7 @@ BackupRelationTask::BackupRelationTask(BurpGlobals* tdgbl) : BurpTask(tdgbl), m_items.add(item); item = FB_NEW_POOL(*pool) Item(this, false); - item->m_ownAttach = false; // will use attach from main thread + item->m_ownAttach = m_masterGbl->gbl_fast_path; // Direct VIO requires an engine attachment. m_items.add(item); for (int i = 1; i < workers; i++) @@ -182,7 +183,11 @@ void BackupRelationTask::SetRelation(burp_rel* relation) m_readDone = false; m_nextPP = 0; - m_metadata.setRelation(m_relation, getMaxWorkers() > 2); + const bool useFastPath = m_masterGbl->gbl_fast_path && + !relation->rel_system && !(relation->rel_flags & (REL_view | REL_external)) && + relation->rel_type == rel_persistent; + + m_metadata.setRelation(m_relation, getMaxWorkers() > 2, useFastPath); } bool BackupRelationTask::handler(WorkItem& _item) @@ -505,14 +510,33 @@ void BackupRelationTask::initItem(BurpGlobals* tdgbl, Item& item) if (!item.m_att) { FbLocalStatus status; - DispatcherPtr provider; + FastPathBurpProvider provider(m_masterGbl->gbl_fast_path); + + // Propagate the database crypt callback so encrypted databases + // requiring per-attachment keys (useOnlyOwnKeys) work on workers. + if (m_masterGbl->gbl_fast_path) + { + ICryptKeyCallback* cryptCb = nullptr; + + if (m_masterGbl->gbl_sw_keyholder) + cryptCb = MVOL_get_crypt(m_masterGbl); + else + cryptCb = m_masterGbl->uSvc->getCryptCallback(); + + if (cryptCb) + { + provider.get()->setDbCryptCallback(&status, cryptCb); + if (status->getState() & IStatus::STATE_ERRORS) + BURP_abort(&status); + } + } // attach, start tran, etc const unsigned char* dpbBuffer = m_masterGbl->gbl_dpb_data.begin(); const unsigned int dpbLength = m_masterGbl->gbl_dpb_data.getCount(); - item.m_att = provider->attachDatabase(&status, tdgbl->gbl_database_file_name, + item.m_att = provider.get()->attachDatabase(&status, tdgbl->gbl_database_file_name, dpbLength, dpbBuffer); if (status->getState() & IStatus::STATE_ERRORS) BURP_abort(&status); @@ -520,10 +544,14 @@ void BackupRelationTask::initItem(BurpGlobals* tdgbl, Item& item) ClumpletWriter tpb(ClumpletReader::Tpb, 128, isc_tpb_version3); tpb.insertTag(isc_tpb_concurrency); tpb.insertTag(isc_tpb_read); + if (tdgbl->gbl_sw_ignore_limbo) tpb.insertTag(isc_tpb_ignore_limbo); + tpb.insertTag(isc_tpb_no_auto_undo); + // add snapshot id + fb_assert(m_masterGbl->tr_snapshot); tpb.insertBigInt(isc_tpb_at_snapshot_number, m_masterGbl->tr_snapshot); item.m_tra = item.m_att->startTransaction(&status, @@ -570,11 +598,13 @@ void BackupRelationTask::freeItem(Item& item) void BackupRelationTask::stopItems() { - MutexLockGuard guard(m_mutex, FB_FUNCTION); - for (Item** p = m_items.begin(); p < m_items.end(); p++) + { + MutexLockGuard guard((*p)->m_mutex, FB_FUNCTION); (*p)->m_cleanCond.notifyAll(); + } + MutexLockGuard guard(m_mutex, FB_FUNCTION); m_dirtyCond.notifyAll(); } @@ -703,7 +733,11 @@ void RestoreRelationTask::SetRelation(BurpGlobals* tdgbl, burp_rel* relation) m_lastRecord = rec_relation_data; m_records = 0; - m_verbRecs = 0; + + { // scope + MutexLockGuard guard(m_verbMutex, FB_FUNCTION); + m_verbRecs = 0; + } m_metadata.setRelation(tdgbl, m_relation); } @@ -732,6 +766,7 @@ bool RestoreRelationTask::handler(WorkItem& _item) } catch (const LongJump&) { + MutexLockGuard guard(m_mutex, FB_FUNCTION); m_stop = true; m_error = true; m_dirtyCond.notifyAll(); @@ -747,6 +782,7 @@ bool RestoreRelationTask::handler(WorkItem& _item) BURP_print_status(&st, true); } + MutexLockGuard guard(m_mutex, FB_FUNCTION); m_stop = true; m_error = true; m_dirtyCond.notifyAll(); @@ -831,6 +867,9 @@ void RestoreRelationTask::verbRecs(FB_UINT64& records, bool total) records = 0; const FB_UINT64 newVerb = (newRecs / m_masterGbl->verboseInterval) * m_masterGbl->verboseInterval; + + MutexLockGuard guard(m_verbMutex, FB_FUNCTION); + if (newVerb > m_verbRecs) { m_verbRecs = newVerb; @@ -841,6 +880,8 @@ void RestoreRelationTask::verbRecs(FB_UINT64& records, bool total) void RestoreRelationTask::verbRecsFinal() { + MutexLockGuard guard(m_verbMutex, FB_FUNCTION); + if (m_verbRecs < static_cast(m_records)) { m_verbRecs = m_records; @@ -890,6 +931,7 @@ void RestoreRelationTask::initItem(BurpGlobals* tdgbl, Item& item) tdgbl->runtimeODS = m_masterGbl->runtimeODS; tdgbl->gbl_use_no_auto_undo = m_masterGbl->gbl_use_no_auto_undo; tdgbl->gbl_use_auto_release_temp_blobid = m_masterGbl->gbl_use_auto_release_temp_blobid; + tdgbl->gbl_fast_path = m_masterGbl->gbl_fast_path; if (item.m_ownAttach) { @@ -897,7 +939,6 @@ void RestoreRelationTask::initItem(BurpGlobals* tdgbl, Item& item) { // attach, start tran, etc FbLocalStatus status; - DispatcherPtr provider; ClumpletWriter dpb(ClumpletReader::dpbList, 128, m_masterGbl->gbl_dpb_data.begin(), @@ -906,8 +947,24 @@ void RestoreRelationTask::initItem(BurpGlobals* tdgbl, Item& item) const UCHAR* dpbBuffer = dpb.getBuffer(); const USHORT dpbLength = dpb.getBufferLength(); - item.m_att = provider->attachDatabase(&status, tdgbl->gbl_database_file_name, - dpbLength, dpbBuffer); + if (tdgbl->gbl_fast_path) + { + ICryptKeyCallback* cryptCb = nullptr; + + if (m_masterGbl->gbl_sw_keyholder) + cryptCb = MVOL_get_crypt(m_masterGbl); + else + cryptCb = m_masterGbl->uSvc->getCryptCallback(); + + item.m_att = FastPathBurpProvider::attachWorker(&status, + tdgbl->gbl_database_file_name, dpbLength, dpbBuffer, cryptCb); + } + else + { + DispatcherPtr provider; + item.m_att = provider->attachDatabase(&status, tdgbl->gbl_database_file_name, + dpbLength, dpbBuffer); + } if (status->getState() & IStatus::STATE_ERRORS) BURP_abort(&status); @@ -960,18 +1017,21 @@ bool RestoreRelationTask::freeItem(Item& item, bool commit) // more detailed message required ? BURP_print_status(&status); } - item.m_tra = nullptr; + else + item.m_tra = nullptr; } if (item.m_tra) { - item.m_tra->rollback(&status); + FbLocalStatus rollbackStatus; + item.m_tra->rollback(&rollbackStatus); item.m_tra = nullptr; } if (item.m_att) { - item.m_att->detach(&status); + FbLocalStatus detachStatus; + item.m_att->detach(&detachStatus); item.m_att = nullptr; } } diff --git a/src/burp/BurpTasks.h b/src/burp/BurpTasks.h index 545716c08d9..9daff507fc9 100644 --- a/src/burp/BurpTasks.h +++ b/src/burp/BurpTasks.h @@ -37,19 +37,34 @@ #include "../common/classes/auto.h" #include "../common/classes/condition.h" #include "../common/classes/fb_atomic.h" +#include "../burp/RestoreMessageLayout.h" +#include "../burp/FastPathRecordWriter.h" +#include "../burp/FastPathRecordReader.h" +#include "../burp/FastPathBurpProvider.h" namespace Burp { +constexpr unsigned FAST_PATH_BACKUP_BATCH_MAX_RECORDS = 1000; +constexpr FB_SIZE_T FAST_PATH_BACKUP_BATCH_MAX_BYTES = 1024 * 1024; +// Staging buffer for the restore direct writer: messages are accumulated here +// so one engine attachment sync covers many rows. The BulkInsert object itself +// stays alive across staging batches (it auto-flushes full extents) and is +// finished only at table end, so staging size bounds memory/latency +// without stranding a partial-extent tail per batch. +constexpr unsigned FAST_PATH_RESTORE_BATCH_MAX_RECORDS = 8000; +constexpr FB_SIZE_T FAST_PATH_RESTORE_BATCH_MAX_BYTES = 8 * 1024 * 1024; + class ReadRelationMeta { public: ReadRelationMeta() noexcept : - m_blr(*getDefaultMemoryPool()) + m_blr(*getDefaultMemoryPool()), + m_layout(*getDefaultMemoryPool()) { clear(); } - void setRelation(const burp_rel* relation, bool partition); + void setRelation(burp_rel* relation, bool partition, bool fastPath); void clear(); bool haveInputs() const noexcept @@ -57,12 +72,24 @@ class ReadRelationMeta return m_inMgsNum != m_outMgsNum; } + bool isFastPath() const noexcept + { + return m_fastPath; + } + + const RestoreMessageLayout& getLayout() const noexcept + { + return m_layout; + } + //private: const burp_rel* m_relation; SSHORT m_fldCount; SSHORT m_inMgsNum; SSHORT m_outMgsNum; Firebird::HalfStaticArray m_blr; + RestoreMessageLayout m_layout; + bool m_fastPath = false; RCRD_LENGTH m_outMsgLen; RCRD_LENGTH m_outRecLen; RCRD_OFFSET m_outEofOffset; @@ -72,7 +99,8 @@ class ReadRelationReq { public: ReadRelationReq() noexcept : - m_outMsg(*getDefaultMemoryPool()) + m_outMsg(*getDefaultMemoryPool()), + m_fastPathBatch(*getDefaultMemoryPool()) { } @@ -86,7 +114,8 @@ class ReadRelationReq void compile(Firebird::CheckStatusWrapper* status, Firebird::IAttachment* db); void setParams(ULONG loPP, ULONG hiPP); - void start(Firebird::CheckStatusWrapper* status, Firebird::ITransaction* tran); + void start(Firebird::CheckStatusWrapper* status, Firebird::IAttachment* att, + Firebird::ITransaction* tran); void receive(Firebird::CheckStatusWrapper* status); void release(Firebird::CheckStatusWrapper* status); @@ -116,8 +145,13 @@ class ReadRelationReq const ReadRelationMeta* m_meta = nullptr; InMsg m_inMgs{}; Firebird::Array m_outMsg; + Firebird::Array m_fastPathBatch; SSHORT* m_eof = nullptr; Firebird::IRequest* m_request = nullptr; + FastPathRecordReader m_fastPathReader; + unsigned m_fastPathBatchRecords = 0; + unsigned m_fastPathBatchPosition = 0; + bool m_usingFastPath = false; }; @@ -125,12 +159,13 @@ class WriteRelationMeta { public: WriteRelationMeta() noexcept : + m_messageLayout(*getDefaultMemoryPool()), m_blr(*getDefaultMemoryPool()) { clear(); } - void setRelation(BurpGlobals* tdgbl, const burp_rel* relation); + void setRelation(BurpGlobals* tdgbl, burp_rel* relation); void clear(); Firebird::IBatch* createBatch(BurpGlobals* tdgbl, Firebird::IAttachment* att); @@ -138,13 +173,22 @@ class WriteRelationMeta //private: bool prepareBatch(BurpGlobals* tdgbl); void prepareRequest(BurpGlobals* tdgbl); + void prepareMessageLayout(BurpGlobals* tdgbl); + void buildNonFastPathMode(BurpGlobals* tdgbl); - const burp_rel* m_relation; + const RestoreMessageLayout& getMessageLayout() const noexcept + { + return m_messageLayout; + } + + burp_rel* m_relation; Firebird::Mutex m_mutex; bool m_batchMode; + bool m_fastPathMode; bool m_batchOk; - ULONG m_inMsgLen; + RCRD_LENGTH m_inMsgLen; ULONG m_blobCount; + RestoreMessageLayout m_messageLayout; // batch mode Firebird::string m_sqlStatement; @@ -162,7 +206,8 @@ class WriteRelationReq public: WriteRelationReq() noexcept : m_inMsg(*getDefaultMemoryPool()), - m_batchMsg(*getDefaultMemoryPool()) + m_batchMsg(*getDefaultMemoryPool()), + m_fastPathBatch(*getDefaultMemoryPool()) { } @@ -174,12 +219,22 @@ class WriteRelationReq void reset(WriteRelationMeta* meta); void clear(); + void initFastPathWriter(Firebird::IAttachment* att, Firebird::ITransaction* tra, + const burp_rel* relation, const RestoreMessageLayout& layout); + void execFastPathBatch(); + + // Flush the bulk insert used by the fast-path writer and destroy the + // writer (it caches the raw engine transaction, which is replaced by + // commit_relation_data() in incremental restore). Runs in get_data() + // before that commit; safe to call when no writer exists. + void finishFastPathBulk(); + void compile(BurpGlobals* tdgbl, Firebird::IAttachment* att); void send(BurpGlobals* tdgbl, Firebird::ITransaction* tran, bool lastRec); void execBatch(BurpGlobals * tdgbl); void release(); - ULONG getDataLength() const noexcept + RCRD_LENGTH getDataLength() const noexcept { return m_inMsg.getCount(); } @@ -214,8 +269,11 @@ class WriteRelationReq WriteRelationMeta* m_meta = nullptr; Firebird::Array m_inMsg; Firebird::Array m_batchMsg; + Firebird::Array m_fastPathBatch; Firebird::IBatch* m_batch = nullptr; Firebird::IRequest* m_request = nullptr; + Firebird::AutoPtr m_fastPathWriter; + unsigned m_fastPathBatchRecs = 0; int m_recs = 0; // total records sent int m_batchRecs = 0; // records in current batch bool m_resync = true; @@ -395,8 +453,8 @@ class BackupRelationTask : public BurpTask Firebird::Mutex m_mutex; Firebird::HalfStaticArray m_items; - volatile bool m_stop; - bool m_error; + std::atomic_bool m_stop; + std::atomic_bool m_error; Firebird::HalfStaticArray m_buffers; Firebird::HalfStaticArray m_dirtyBuffers; @@ -516,9 +574,10 @@ class RestoreRelationTask : public BurpTask Firebird::Mutex m_mutex; Firebird::HalfStaticArray m_items; - volatile bool m_stop; - bool m_error; + std::atomic_bool m_stop; + std::atomic_bool m_error; Firebird::AtomicCounter m_records; // records restored for the current relation + Firebird::Mutex m_verbMutex; FB_UINT64 m_verbRecs; // last records count reported Firebird::HalfStaticArray m_buffers; diff --git a/src/burp/FastPathBurpProvider.h b/src/burp/FastPathBurpProvider.h new file mode 100644 index 00000000000..264f566df0a --- /dev/null +++ b/src/burp/FastPathBurpProvider.h @@ -0,0 +1,62 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2026 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#ifndef BURP_FAST_PATH_BURP_PROVIDER_H +#define BURP_FAST_PATH_BURP_PROVIDER_H + +#include "firebird/Interface.h" +#include "../common/classes/ImplementHelper.h" +#include "../common/classes/auto.h" + +namespace Burp { + +class FastPathBurpProvider +{ +private: + struct EngineHolder; + +public: + explicit FastPathBurpProvider(bool useFastPath); + ~FastPathBurpProvider(); + + FastPathBurpProvider(const FastPathBurpProvider&) = delete; + FastPathBurpProvider& operator=(const FastPathBurpProvider&) = delete; + +public: + Firebird::IProvider* get() noexcept + { + return provider; + } + + static Firebird::IAttachment* attachWorker(Firebird::CheckStatusWrapper* status, + const char* dbName, unsigned dpbLen, const unsigned char* dpb, + Firebird::ICryptKeyCallback* cryptCallback); + +private: + Firebird::DispatcherPtr dispatcher; + Firebird::AutoPtr engineHolder; + Firebird::IProvider* provider; +}; + +} // namespace Burp + +#endif // BURP_FAST_PATH_BURP_PROVIDER_H diff --git a/src/burp/FastPathRecordReader.h b/src/burp/FastPathRecordReader.h new file mode 100644 index 00000000000..e1d867e5c07 --- /dev/null +++ b/src/burp/FastPathRecordReader.h @@ -0,0 +1,68 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2026 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#ifndef BURP_FAST_PATH_RECORD_READER_H +#define BURP_FAST_PATH_RECORD_READER_H + +#include "../common/common.h" +#include "../common/classes/auto.h" + +struct burp_rel; + +namespace Firebird { + class IAttachment; + class ITransaction; +} + +namespace Burp { + +class RestoreMessageLayout; + +class FastPathRecordReader +{ +private: + struct Imp; + +public: + FastPathRecordReader(); + ~FastPathRecordReader(); + + FastPathRecordReader(const FastPathRecordReader&) = delete; + FastPathRecordReader& operator=(const FastPathRecordReader&) = delete; + +public: + void init(Firebird::IAttachment* att, Firebird::ITransaction* tra, + const burp_rel* relation, const RestoreMessageLayout& layout, bool partition); + + void start(ULONG loPP, ULONG hiPP); + bool readRecord(UCHAR* messageBuffer, RCRD_LENGTH msgLength); + unsigned readRecords(UCHAR* messageBuffer, RCRD_LENGTH msgLength, unsigned maxRecords); + + void clear(); + +private: + Firebird::AutoPtr imp; +}; + +} // namespace Burp + +#endif // BURP_FAST_PATH_RECORD_READER_H diff --git a/src/burp/FastPathRecordWriter.h b/src/burp/FastPathRecordWriter.h new file mode 100644 index 00000000000..4fdd72bdce8 --- /dev/null +++ b/src/burp/FastPathRecordWriter.h @@ -0,0 +1,81 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2026 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#ifndef BURP_FAST_PATH_RECORD_WRITER_H +#define BURP_FAST_PATH_RECORD_WRITER_H + +#include "../common/common.h" +#include "../common/classes/auto.h" + +struct burp_rel; + +namespace Firebird { + class IAttachment; + class ITransaction; +} + +namespace Burp { + +class RestoreMessageLayout; + +class FastPathRecordWriter +{ +public: + FastPathRecordWriter(); + ~FastPathRecordWriter(); + + FastPathRecordWriter(const FastPathRecordWriter&) = delete; + FastPathRecordWriter& operator=(const FastPathRecordWriter&) = delete; + +public: + // The caller must route only persistent user tables to direct VIO. Other + // problems raise an error. + void init(Firebird::IAttachment* att, Firebird::ITransaction* tra, + const burp_rel* relation, const RestoreMessageLayout& layout); + + // Convert and buffer a batch of messages using a single engine attachment + // sync. Any failure aborts the direct restore; there is no per-record + // recovery because BulkInsert has no rollback for blob/array side effects. + void writeRecords(const UCHAR* messageBuffer, RCRD_LENGTH msgLength, unsigned recordCount); + + // Flush buffered pages and unregister the bulk insert from the + // transaction. The bulk object auto-flushes full extents internally and + // stays alive across message batches, so this runs at table end and at + // commit; the next row transparently registers a fresh one when needed. + // Safe to call when no bulk insert is registered. + void finishBulk(); + + // Release ArrayFields left behind by records in the last batch. Must only be + // called after a whole VIO batch has been processed: future batches have not + // created their arrays yet. + void releaseRequestlessArrays(); + + void clear(); + +private: + struct Imp; + Firebird::AutoPtr imp; +}; + +} // namespace Burp + +#endif // BURP_FAST_PATH_RECORD_WRITER_H diff --git a/src/burp/RestoreMessageLayout.cpp b/src/burp/RestoreMessageLayout.cpp new file mode 100644 index 00000000000..ee920e34a1f --- /dev/null +++ b/src/burp/RestoreMessageLayout.cpp @@ -0,0 +1,172 @@ +/* + * The contents of this file are subject to the Interbase Public + * License Version 1.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy + * of the License at http://www.Inprise.com/IPL.html + * + * Software distributed under the License is distributed on an + * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express + * or implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code was created by Inprise Corporation + * and its predecessors. Portions created by Inprise Corporation are + * Copyright (C) Inprise Corporation. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + * + * Adriano dos Santos Fernandes + */ + +#include "firebird.h" + +// burp.h and ini.h both define struct gfld with different layouts +#define gfld burp_gfld +#include "../burp/RestoreMessageLayout.h" +#include "../burp/burp.h" +#undef gfld + +#include "../common/classes/SafeArg.h" +#include "../common/dsc_proto.h" +#include "../jrd/align.h" +#include "../jrd/val.h" + +using namespace Firebird; + +namespace Burp { + + +static bool makeMessageDescriptor(dsc& desc, const burp_fld* field, bool fixFssData, CSetId fixFssDataId) +{ + USHORT blrType = field->fld_type; + FLD_LENGTH length = field->fld_length; + + if (field->fld_flags & FLD_array) + blrType = blr_quad; + else + { + // Normalize extended text opcodes: charset/collation are passed + // separately to DSC_make_descriptor(). + switch (blrType) + { + case blr_text2: + blrType = blr_text; + break; + case blr_varying2: + blrType = blr_varying; + break; + case blr_cstring2: + blrType = blr_cstring; + break; + default: + break; + } + } + + if (blrType != blr_blob && blrType > DTYPE_BLR_MAX) + return false; + + SSHORT subType = field->fld_sub_type; + CSetId charSet = field->fld_character_set_id; + + if (field->fld_flags & FLD_array) + { + subType = 0; + charSet = CS_NONE; + } + else if (fixFssData && charSet == CS_UNICODE_FSS && + (blrType == blr_text || blrType == blr_varying || + (blrType == blr_blob && subType == isc_blob_text))) + { + // Same condition as the normal restore message builders: the stored data + // is in the given charset and is converted to UNICODE_FSS on store. + charSet = fixFssDataId; + } + + return DSC_make_descriptor(&desc, blrType, field->fld_scale, length, + subType, charSet, field->fld_collation_id); +} + +[[noreturn]] static void raiseInvalidLength(const burp_rel* relation) +{ + (Arg::Gds(isc_gbak_inv_record_length) << + Arg::Str(relation->rel_name.toQuotedString())).raise(); +} + +static RCRD_OFFSET alignField(const burp_rel* relation, RCRD_OFFSET offset, + USHORT alignment, USHORT length) +{ + if (offset > MAX_RECORD_SIZE) + raiseInvalidLength(relation); + + if (alignment) + offset = FB_ALIGN(offset, alignment); + + if (offset > MAX_RECORD_SIZE || length > MAX_RECORD_SIZE - offset) + raiseInvalidLength(relation); + + return offset; +} + + +void RestoreMessageLayout::build(burp_rel* relation, bool fixFssData, CSetId fixFssDataId) +{ + fb_assert(relation); + + clear(); + + RCRD_OFFSET offset = 0; + USHORT peakAlignment = sizeof(SSHORT); + + for (burp_fld* field = relation->rel_fields; field; field = field->fld_next) + { + if (field->fld_flags & FLD_computed) + continue; + + dsc descriptor; + if (!makeMessageDescriptor(descriptor, field, fixFssData, fixFssDataId)) + (Arg::Gds(isc_gbak_unk_type) << Arg::Num(field->fld_type)).raise(); + + const auto alignment = type_alignments[descriptor.dsc_dtype]; + + if (alignment > peakAlignment) + peakAlignment = alignment; + + const auto fieldOffset = alignField(relation, offset, alignment, descriptor.dsc_length); + field->fld_offset = fieldOffset; + offset = fieldOffset + descriptor.dsc_length; + + fields.add({field, descriptor, alignment, fieldOffset, 0}); + } + + SSHORT count = 0; + for (auto& field : fields) + { + const auto nullOffset = alignField(relation, offset, sizeof(SSHORT), sizeof(SSHORT)); + field.nullOffset = nullOffset; + field.metadata->fld_missing_parameter = count++; + field.metadata->fld_missing_offset = nullOffset; + offset = nullOffset + sizeof(SSHORT); + } + + length = offset; + maxAlignment = fields.hasData() ? peakAlignment : 0; +} + +RCRD_LENGTH RestoreMessageLayout::getStride() const noexcept +{ + if (!maxAlignment) + return length; + return FB_ALIGN(length, maxAlignment); +} + +void RestoreMessageLayout::clear() noexcept +{ + fields.clear(); + length = 0; + maxAlignment = 0; +} + + +} // namespace Burp diff --git a/src/burp/RestoreMessageLayout.h b/src/burp/RestoreMessageLayout.h new file mode 100644 index 00000000000..c0b27defd41 --- /dev/null +++ b/src/burp/RestoreMessageLayout.h @@ -0,0 +1,96 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2026 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#ifndef BURP_RESTORE_MESSAGE_LAYOUT_H +#define BURP_RESTORE_MESSAGE_LAYOUT_H + +#include "../common/classes/array.h" +#include "../common/dsc.h" + +struct burp_fld; +struct burp_rel; + +namespace Burp { + +class RestoreMessageLayout +{ +public: + struct Field + { + burp_fld* metadata; + dsc descriptor; + USHORT alignment; + RCRD_OFFSET offset; + RCRD_OFFSET nullOffset; + }; + +public: + explicit RestoreMessageLayout(Firebird::MemoryPool& pool) + : fields(pool) + { + } + + RestoreMessageLayout(const RestoreMessageLayout&) = delete; + RestoreMessageLayout& operator=(const RestoreMessageLayout&) = delete; + +public: + // Computes message offsets for all non-computed fields and writes them back + // into the field metadata (fld_offset, fld_missing_*) - the same convention + // as the BLR message builders. + void build(burp_rel* relation, bool fixFssData, CSetId fixFssDataId); + + void clear() noexcept; + + RCRD_LENGTH getLength() const noexcept + { + return length; + } + + USHORT getMaxAlignment() const noexcept + { + return maxAlignment; + } + + // In-memory batch stride: message length rounded up to the maximum field + // alignment so consecutive packed records keep descriptor alignment. + // The serialized backup format still uses getLength(). + RCRD_LENGTH getStride() const noexcept; + + const Field* begin() const noexcept + { + return fields.begin(); + } + + const Field* end() const noexcept + { + return fields.end(); + } + +private: + Firebird::HalfStaticArray fields; + RCRD_LENGTH length = 0; + USHORT maxAlignment = 0; +}; + +} // namespace Burp + +#endif // BURP_RESTORE_MESSAGE_LAYOUT_H diff --git a/src/burp/backup.epp b/src/burp/backup.epp index 6fa5b06f812..c1a8cc0ebac 100644 --- a/src/burp/backup.epp +++ b/src/burp/backup.epp @@ -255,9 +255,11 @@ int BACKUP_backup(const TEXT* dbb_file, const TEXT* file_name) EXEC SQL SET TRANSACTION NAME gds_trans; } - // get shared snapshot number if asked for parallel backup + // Readers that use a private attachment must join this snapshot via + // isc_tpb_at_snapshot_number. FAST_PATH always does (VIO needs a + // JAttachment, not the yvalve master handle), as does -parallel > 1. tdgbl->tr_snapshot = 0; - if (gds_trans && tdgbl->gbl_sw_par_workers > 1) + if (gds_trans && (tdgbl->gbl_fast_path || tdgbl->gbl_sw_par_workers > 1)) { UCHAR in_buf[] = {fb_info_tra_snapshot_number, isc_info_end}; UCHAR out_buf[16] = {0}; @@ -268,25 +270,37 @@ int BACKUP_backup(const TEXT* dbb_file, const TEXT* file_name) UCHAR* p = out_buf, *e = out_buf + sizeof(out_buf); while (p < e) { - SSHORT len; - switch (*p++) - { - case isc_info_error: - case isc_info_end: - p = e; + const UCHAR item = *p++; + if (item == isc_info_end || item == isc_info_error || p + 2 > e) break; - case fb_info_tra_snapshot_number: - len = isc_portable_integer(p, 2); - p += 2; - tdgbl->tr_snapshot = isc_portable_integer(p, len); - p += len; + const USHORT len = isc_portable_integer(p, 2); + p += 2; + if (p + len > e) break; + + switch (item) + { + case fb_info_tra_snapshot_number: + tdgbl->tr_snapshot = isc_portable_integer(p, len); + break; } + + p += len; } } + if (tdgbl->tr_snapshot == 0) - tdgbl->gbl_sw_par_workers = 1; + { + if (tdgbl->gbl_fast_path) + { + // msg 432 cannot obtain transaction snapshot number for FAST_PATH backup + BURP_error(432, true); + } + + if (tdgbl->gbl_sw_par_workers > 1) + tdgbl->gbl_sw_par_workers = 1; + } } // detect if MAKE_DBKEY is supported and decide kind of read relation query @@ -1565,7 +1579,7 @@ void put_data(burp_rel* relation, ReadRelationReq* request) count++; // eof FbLocalStatus status; - request->start(&status, gds_trans); + request->start(&status, DB, gds_trans); if (status->getState() & IStatus::STATE_ERRORS) { BURP_error_redirect(&status, 28); @@ -4211,6 +4225,9 @@ void write_relations() relation->rel_next = tdgbl->relations; tdgbl->relations = relation; relation->rel_id = X.RDB$RELATION_ID; + relation->rel_type = X.RDB$RELATION_TYPE.NULL ? + (!X.RDB$EXTERNAL_FILE.NULL ? rel_external : rel_persistent) : + static_cast(X.RDB$RELATION_TYPE); if (!X.RDB$SCHEMA_NAME.NULL) relation->rel_name.schema = X.RDB$SCHEMA_NAME; @@ -4270,6 +4287,7 @@ void write_relations() relation->rel_next = tdgbl->relations; tdgbl->relations = relation; relation->rel_id = X.RDB$RELATION_ID; + relation->rel_type = !X.RDB$EXTERNAL_FILE.NULL ? rel_external : rel_persistent; relation->rel_name = name; relation->rel_flags |= flags; put_relation (relation); @@ -4933,9 +4951,13 @@ namespace Burp { /// class ReadRelationMeta -void ReadRelationMeta::setRelation(const burp_rel* relation, bool partition) +void ReadRelationMeta::setRelation(burp_rel* relation, bool partition, bool fastPath) { m_relation = relation; + m_fastPath = fastPath; + + if (m_fastPath) + m_layout.build(relation, false, CS_NONE); // Build request BLR. There could be two kind of requests : // a) partition == true @@ -5004,6 +5026,16 @@ void ReadRelationMeta::setRelation(const burp_rel* relation, bool partition) if (field->fld_flags & FLD_array) blrType = blr_blob; + // Normalize text2 family like RestoreMessageLayout does, so tables + // using text2/varying2/cstring2 don't abort BLR building before + // fast-path gets a chance (VIO normalizes the same way). + if (blrType == blr_text2) + blrType = blr_text; + else if (blrType == blr_varying2) + blrType = blr_varying; + else if (blrType == blr_cstring2) + blrType = blr_cstring; + if (blrType <= DTYPE_BLR_MAX) { const auto dtype = gds_cvt_blr_dtype[blrType]; @@ -5014,7 +5046,8 @@ void ReadRelationMeta::setRelation(const burp_rel* relation, bool partition) { case blr_text: case blr_varying: - add_byte(blr, field->fld_type); + case blr_cstring: + add_byte(blr, blrType); add_word(blr, field->fld_length); if (blrType == blr_varying) length += sizeof(USHORT); @@ -5073,6 +5106,7 @@ void ReadRelationMeta::setRelation(const burp_rel* relation, bool partition) add_byte(blr, 0); offset = FB_ALIGN(offset, sizeof(SSHORT)); field->fld_missing_parameter = count++; + field->fld_missing_offset = offset; offset += sizeof(SSHORT); } @@ -5086,6 +5120,31 @@ void ReadRelationMeta::setRelation(const burp_rel* relation, bool partition) // To be used later for the buffer size to receive data m_outMsgLen = m_outEofOffset + sizeof(SSHORT); + // The fast-path and BLR paths must describe identical message + // layouts: VIO fills buffers at layout offsets while put_data() locates + // blob/array quads via fld_offset. Drift between the two computations + // would silently corrupt backups, so fail closed if they disagree. + if (m_fastPath) + { + bool layoutOk = m_layout.getLength() == m_outRecLen && + m_outMsgLen == m_layout.getLength() + sizeof(SSHORT); + + for (const auto& layoutField : m_layout) + { + layoutOk = layoutOk && + layoutField.metadata->fld_offset == layoutField.offset && + layoutField.metadata->fld_missing_offset == layoutField.nullOffset; + } + + if (!layoutOk) + { + fb_assert(false); + (Arg::Gds(isc_random) << + Arg::Str("FAST_PATH produced an inconsistent message layout") << + Arg::Str(relation->rel_name.toQuotedString())).raise(); + } + } + if (partition) { add_byte(blr, blr_receive); @@ -5209,6 +5268,8 @@ void ReadRelationMeta::clear() m_relation = NULL; m_fldCount = 0; m_blr.clear(); + m_layout.clear(); + m_fastPath = false; m_inMgsNum = m_outMgsNum = 0; m_outMsgLen = m_outRecLen = m_outEofOffset = 0; } @@ -5230,13 +5291,19 @@ void ReadRelationReq::reset(const ReadRelationMeta* meta) m_relation = m_meta->m_relation; BurpGlobals* tdgbl = BurpGlobals::getSpecific(); - FbLocalStatus status; - compile(&status, DB); - if (status->getState() & IStatus::STATE_ERRORS) + + if (!m_meta->isFastPath()) { - BURP_error_redirect(&status, 27); - // msg 27 isc_compile_request failed - fb_print_blr(m_meta->m_blr.begin(), m_meta->m_blr.getCount(), NULL, NULL, 0); + FbLocalStatus status; + + compile(&status, DB); + + if (status->getState() & IStatus::STATE_ERRORS) + { + BURP_error_redirect(&status, 27); + // msg 27 isc_compile_request failed + fb_print_blr(m_meta->m_blr.begin(), m_meta->m_blr.getCount(), NULL, NULL, 0); + } } UCHAR* data = m_outMsg.getBuffer(m_meta->m_outMsgLen); @@ -5254,10 +5321,15 @@ void ReadRelationReq::reset(const ReadRelationMeta* meta) void ReadRelationReq::clear() { + m_fastPathReader.clear(); + m_usingFastPath = false; + m_fastPathBatchRecords = 0; + m_fastPathBatchPosition = 0; m_relation = NULL; m_meta = NULL; m_eof = NULL; m_outMsg.clear(); + m_fastPathBatch.clear(); if (m_request) { @@ -5273,6 +5345,12 @@ void ReadRelationReq::compile(CheckStatusWrapper* status, IAttachment* att) void ReadRelationReq::release(CheckStatusWrapper* status) { + if (m_usingFastPath) + { + clear(); + return; + } + if (m_request) { m_request->free(status); @@ -5291,8 +5369,18 @@ void ReadRelationReq::setParams(ULONG loPP, ULONG hiPP) m_inMgs.hiPP = hiPP; } -void ReadRelationReq::start(CheckStatusWrapper* status, ITransaction* tran) +void ReadRelationReq::start(CheckStatusWrapper* status, IAttachment* att, ITransaction* tran) { + if (m_meta->isFastPath()) + { + m_fastPathReader.init(att, tran, m_relation, m_meta->getLayout(), m_meta->haveInputs()); + m_usingFastPath = true; + m_fastPathBatchRecords = 0; + m_fastPathBatchPosition = 0; + m_fastPathReader.start(m_inMgs.loPP, m_inMgs.hiPP); + return; + } + if (m_meta->haveInputs()) m_request->startAndSend(status, tran, 0, m_meta->m_inMgsNum, sizeof(m_inMgs), &m_inMgs); else @@ -5301,7 +5389,50 @@ void ReadRelationReq::start(CheckStatusWrapper* status, ITransaction* tran) void ReadRelationReq::receive(CheckStatusWrapper* status) { - m_request->receive(status, 0, m_meta->m_outMgsNum, m_meta->m_outMsgLen, m_outMsg.begin()); + if (!m_usingFastPath) + { + m_request->receive(status, 0, m_meta->m_outMgsNum, m_meta->m_outMsgLen, m_outMsg.begin()); + return; + } + + memset(m_outMsg.begin(), 0, m_meta->m_outMsgLen); + + // Aligned stride keeps descriptor alignment between packed records; + // serialization still uses m_outMsgLen. + FB_SIZE_T stride = m_meta->m_outMsgLen; + + if (const USHORT maxAlign = m_meta->getLayout().getMaxAlignment()) + stride = FB_ALIGN(stride, maxAlign); + + if (m_fastPathBatchPosition == m_fastPathBatchRecords) + { + unsigned batchRecords = FAST_PATH_BACKUP_BATCH_MAX_RECORDS; + + if (stride) + { + batchRecords = MIN(batchRecords, + static_cast(FAST_PATH_BACKUP_BATCH_MAX_BYTES / stride)); + } + + batchRecords = MAX(batchRecords, 1); + + m_fastPathBatchRecords = m_fastPathReader.readRecords( + m_fastPathBatch.getBuffer(static_cast(batchRecords) * stride, false), + static_cast(stride), batchRecords); + m_fastPathBatchPosition = 0; + } + + if (m_fastPathBatchPosition < m_fastPathBatchRecords) + { + memcpy(m_outMsg.begin(), + m_fastPathBatch.begin() + static_cast(m_fastPathBatchPosition) * stride, + m_meta->m_outMsgLen); + + ++m_fastPathBatchPosition; + *m_eof = 1; + } + else + *m_eof = 0; } diff --git a/src/burp/burp.cpp b/src/burp/burp.cpp index c02b5892969..33daec8c3b5 100644 --- a/src/burp/burp.cpp +++ b/src/burp/burp.cpp @@ -1042,6 +1042,11 @@ int gbak(Firebird::UtilSvc* uSvc) BURP_error(334, true, SafeArg() << in_sw_tab->in_sw_name); tdgbl->gbl_sw_deactivate_indexes = true; break; + case IN_SW_BURP_FAST_PATH: + if (tdgbl->gbl_fast_path) + BURP_error(334, true, SafeArg() << in_sw_tab->in_sw_name); + tdgbl->gbl_fast_path = true; + break; case IN_SW_BURP_IG: if (ignoreDamaged) BURP_error(334, true, SafeArg() << in_sw_tab->in_sw_name); @@ -1299,6 +1304,16 @@ int gbak(Firebird::UtilSvc* uSvc) if (!sw_replace) sw_replace = IN_SW_BURP_B; + if (tdgbl->gbl_fast_path) + { + // Single enforcement point for FAST_PATH: it requires -service. + if (!uSvc->isService()) + { + // msg 430 FAST_PATH requires the -service option + BURP_error(430, true); + } + } + if (sw_replace == IN_SW_BURP_B) { for (burp_fil* f = tdgbl->gbl_sw_files; f; f = f->fil_next) diff --git a/src/burp/burp.h b/src/burp/burp.h index e21e2c69613..bf57720c840 100644 --- a/src/burp/burp.h +++ b/src/burp/burp.h @@ -817,7 +817,9 @@ struct burp_rel burp_rel* rel_next; burp_fld* rel_fields; SSHORT rel_flags; + bool rel_system; // set on restore; backup relies on BURP_alloc_zero (false = user table) SSHORT rel_id; + SSHORT rel_type; // RDB$RELATION_TYPE (rel_persistent, rel_view, ...); zero == persistent Firebird::QualifiedMetaString rel_name; GDS_NAME rel_owner; // relation owner, if not us ULONG rel_max_pp; // max pointer page sequence number @@ -1305,6 +1307,7 @@ class BurpGlobals : public Firebird::ThreadData, public GblPool bool gbl_use_no_auto_undo = true; bool gbl_use_auto_release_temp_blobid = true; + bool gbl_fast_path = false; }; // CVC: This aux routine declared here to not force inclusion of burp.h with burp_proto.h diff --git a/src/burp/burpswi.h b/src/burp/burpswi.h index 10d67fd7ac5..60901a490b6 100644 --- a/src/burp/burpswi.h +++ b/src/burp/burpswi.h @@ -104,6 +104,7 @@ inline constexpr int IN_SW_BURP_DIRECT_IO = 55; // direct IO for backup files inline constexpr int IN_SW_BURP_SKIP_SCHEMA_DATA = 56; // skip data from schema inline constexpr int IN_SW_BURP_INCLUDE_SCHEMA_DATA = 57; // backup data from schemas +inline constexpr int IN_SW_BURP_FAST_PATH = 58; // fast-path backup/restore via VIO /**************************************************************************/ @@ -133,6 +134,8 @@ static inline constexpr Switches::in_sw_tab_t reference_burp_in_sw_table[] = // msg 97: @1EXPAND no data compression {IN_SW_BURP_FA, isc_spb_bkp_factor, "FACTOR", 0, 0, 0, false, false, 181, 2, NULL, boBackup}, /* msg 181; @1FACTOR blocking factor */ + {IN_SW_BURP_FAST_PATH, isc_spb_bkp_fast_path, "FAST_PATH", 0, 0, 0, false, true, 431, 4, NULL, boGeneral}, + // msg 431: @1FAST(_PATH) fast-path data transfer {IN_SW_BURP_FETCHPASS, 0, "FETCH_PASSWORD", 0, 0, 0, false, false, 306, 2, NULL, boGeneral}, // msg 306: @1FE(TCH_PASSWORD) fetch password from file {IN_SW_BURP_FIX_FSS_DATA, isc_spb_res_fix_fss_data, @@ -249,4 +252,3 @@ static inline constexpr const char* burp_repl_mode_sw_table[] = const int BURP_MAX_PARALLEL_WORKERS = 64; #endif // BURP_BURP_H - diff --git a/src/burp/engine/FastPathBurpUtil.cpp b/src/burp/engine/FastPathBurpUtil.cpp new file mode 100644 index 00000000000..3f53ab9160c --- /dev/null +++ b/src/burp/engine/FastPathBurpUtil.cpp @@ -0,0 +1,69 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2026 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#include "firebird.h" + +#include "../burp/FastPathBurpProvider.h" +#include "../common/classes/alloc.h" +#include "../common/classes/GetPlugins.h" +#include "../jrd/EngineInterface.h" + +using namespace Firebird; +using namespace Jrd; + +namespace Burp { + + +struct FastPathBurpProvider::EngineHolder +{ + AutoPlugin provider{JProvider::getInstance()}; +}; + + +FastPathBurpProvider::FastPathBurpProvider(bool useFastPath) + : provider(dispatcher) +{ + if (useFastPath) + { + engineHolder = FB_NEW EngineHolder(); + provider = engineHolder->provider; + } +} + +FastPathBurpProvider::~FastPathBurpProvider() = default; + +IAttachment* FastPathBurpProvider::attachWorker(CheckStatusWrapper* status, const char* dbName, + unsigned dpbLen, const unsigned char* dpb, ICryptKeyCallback* cryptCallback) +{ + AutoPlugin engineProvider(JProvider::getInstance()); + + if (cryptCallback) + engineProvider->setDbCryptCallback(status, cryptCallback); + + if (status->getState() & IStatus::STATE_ERRORS) + return nullptr; + + return engineProvider->attachDatabase(status, dbName, dpbLen, dpb); +} + + +} // namespace Burp diff --git a/src/burp/engine/FastPathRecordReader.cpp b/src/burp/engine/FastPathRecordReader.cpp new file mode 100644 index 00000000000..5ea7462d12d --- /dev/null +++ b/src/burp/engine/FastPathRecordReader.cpp @@ -0,0 +1,402 @@ +/* + * The contents of this file are subject to the Interbase Public + * License Version 1.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy + * of the License at http://www.Inprise.com/IPL.html + * + * Software distributed under the License is distributed on an + * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express + * or implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code was created by Inprise Corporation + * and its predecessors. Portions created by Inprise Corporation are + * Copyright (C) Inprise Corporation. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + * + * Adriano dos Santos Fernandes + */ + +#include "firebird.h" + +// burp.h and ini.h both define struct gfld with different layouts +#define gfld burp_gfld +#include "../burp/FastPathRecordReader.h" +#include "../burp/RestoreMessageLayout.h" +#include "../burp/burp.h" +#undef gfld + +#include "../burp/burp_proto.h" +#include "../common/dsc_proto.h" +#include "../jrd/EngineInterface.h" +#include "../jrd/Attachment.h" +#include "../jrd/Database.h" +#include "../jrd/jrd.h" +#include "../jrd/Record.h" +#include "../jrd/Relation.h" +#include "../jrd/met.h" +#include "../jrd/mov_proto.h" +#include "../jrd/req.h" +#include "../jrd/rlck_proto.h" +#include "../jrd/scl.h" +#include "../jrd/scl_proto.h" +#include "../jrd/tdbb.h" +#include "../jrd/tra.h" +#include "../jrd/vio_proto.h" +#include "../jrd/cch_proto.h" +#include "../jrd/blb.h" +#include "../jrd/evl_proto.h" + +using namespace Firebird; +using namespace Jrd; + +namespace Burp { + + +struct FastPathRecordReader::Imp +{ + struct Field + { + dsc target; + RCRD_OFFSET offset; + RCRD_OFFSET nullOffset; + USHORT fieldId; + bool array; + }; + + IAttachment* att = nullptr; + ITransaction* tra = nullptr; + const burp_rel* burpRelation = nullptr; + JAttachment* jAtt = nullptr; + Attachment* attachment = nullptr; + jrd_tra* transaction = nullptr; + jrd_rel* relation = nullptr; + const RestoreMessageLayout* layout = nullptr; + RCRD_LENGTH messageLength = 0; + + HalfStaticArray fields; + record_param rpb; + RecordNumber upper; + bool partition = false; + bool scanActive = false; + bool initialized = false; + + Imp() + : fields(*getDefaultMemoryPool()), + rpb() + {} + + // record_param is a plain struct: VIO_record() allocates rpb_record from + // the default pool on first use (see VIO_data) and reuses it afterwards, + // but nothing frees it (sweep code deletes it explicitly too). + // Always use this instead of assigning a fresh record_param. + void resetRpb() + { + delete rpb.rpb_record; + rpb = record_param(); + } + + // The direct scan does not compile a request, so check the same privileges + // that a BLR FOR over the relation would verify: schema USAGE, relation + // SELECT and SELECT on every read column. + void checkAccess(thread_db* tdbb) + { + const auto& relName = relation->getName(); + const auto& securityName = relation->getSecurityName(); + + SCL_check_schema(tdbb, relName.schema, SCL_usage); + + if (const auto sClass = SCL_get_class(tdbb, securityName.object)) + SCL_check_access(tdbb, sClass, 0, {}, SCL_select, obj_relations, false, relName); + + const auto relFields = relation->rel_fields; + + for (const auto& field : fields) + { + const jrd_fld* const jrdField = (relFields && field.fieldId < relFields->count()) ? + (*relFields)[field.fieldId] : nullptr; + + if (!jrdField) + continue; + + if (const auto sClass = SCL_get_class(tdbb, jrdField->fld_security_name)) + { + SCL_check_access(tdbb, sClass, 0, {}, SCL_select, obj_column, false, + relName, jrdField->fld_name); + } + } + } + + // Balance the rel_scan_count increment made in start(). Called on natural + // EOF and from clear(), so a finished scan does not keep the relation + // registered as actively scanned across pointer-page slices or until the + // reader is destroyed. + void finishScan() + { + if (!scanActive) + return; + + scanActive = false; + + if (relation && relation->getPermanent()->rel_scan_count) + --relation->getPermanent()->rel_scan_count; + } +}; + + +[[noreturn]] static void raiseInvalidField(const burp_rel* relation, const burp_fld* field, + const char* reason) +{ + (Arg::Gds(isc_gbak_inv_column) << Arg::Str(field->fld_name) << + Arg::Str(relation->rel_name.toQuotedString()) << Arg::Str(reason)).raise(); +} + + +FastPathRecordReader::FastPathRecordReader() + : imp(FB_NEW_POOL(*getDefaultMemoryPool()) Imp()) +{ +} + +FastPathRecordReader::~FastPathRecordReader() +{ + clear(); +} + +void FastPathRecordReader::init(IAttachment* att, ITransaction* tra, const burp_rel* relation, + const RestoreMessageLayout& layout, bool partition) +{ + fb_assert(att && tra && relation && imp); + + clear(); + imp->att = att; + imp->tra = tra; + imp->burpRelation = relation; + imp->layout = &layout; + imp->messageLength = layout.getLength(); + imp->partition = partition; + imp->jAtt = static_cast(att); + imp->attachment = imp->jAtt->getHandle(); + fb_assert(imp->attachment); + + FbLocalStatus status; + EngineContextHolder tdbb(&status, imp->jAtt, FB_FUNCTION); + + imp->transaction = imp->jAtt->getEngineTransaction(&status, tra); + + if (status->hasData()) + BURP_abort(&status); + + if (!imp->transaction) + Arg::Gds(isc_bad_trans_handle).raise(); + + tdbb->setTransaction(imp->transaction); + + imp->relation = MetadataCache::getVersioned(tdbb, relation->rel_id, + CacheFlag::AUTOCREATE); + + if (!imp->relation) + { + (Arg::Gds(isc_gbak_tbl_missing) << + Arg::Str(relation->rel_name.toQuotedString())).raise(); + } + + if (relation->rel_type != rel_persistent || + (relation->rel_flags & (::REL_view | ::REL_external)) || + imp->relation->isSystem() || imp->relation->isView() || + imp->relation->isVirtual() || imp->relation->isTemporary() || + imp->relation->getExtFile()) + { + fb_assert(false); + (Arg::Gds(isc_random) << + Arg::Str("FAST_PATH selected an ineligible relation") << + Arg::Str(relation->rel_name.toQuotedString())).raise(); + } + + const Format* const format = imp->relation->currentFormat(tdbb); + if (!format) + { + fb_assert(false); + (Arg::Gds(isc_random) << + Arg::Str("FAST_PATH relation has no current format") << + Arg::Str(relation->rel_name.toQuotedString())).raise(); + } + + for (const auto& layoutField : layout) + { + const burp_fld* field = layoutField.metadata; + if (field->fld_id < 0 || field->fld_id >= format->fmt_count) + raiseInvalidField(relation, field, "field is not present in the source table"); + + const dsc source = format->fmt_desc[field->fld_id]; + if (source.isUnknown() || (source.dsc_flags & DSC_computed)) + raiseInvalidField(relation, field, "invalid source descriptor"); + + Imp::Field mapping; + mapping.target = layoutField.descriptor; + mapping.offset = layoutField.offset; + mapping.nullOffset = layoutField.nullOffset; + mapping.fieldId = static_cast(field->fld_id); + mapping.array = (field->fld_flags & FLD_array) != 0; + imp->fields.add(mapping); + } + + imp->checkAccess(tdbb); + + RLCK_reserve_relation(tdbb, imp->transaction, imp->relation->getPermanent(), false); + imp->initialized = true; +} + +void FastPathRecordReader::start(ULONG loPP, ULONG hiPP) +{ + fb_assert(imp && imp->initialized); + + FbLocalStatus status; + EngineContextHolder tdbb(&status, imp->jAtt, FB_FUNCTION); + tdbb->setTransaction(imp->transaction); + + imp->resetRpb(); + imp->finishScan(); + imp->rpb.rpb_relation = imp->relation; + imp->rpb.rpb_stream_flags = 0; + imp->rpb.getWindow(tdbb).win_flags = WIN_large_scan; + imp->rpb.rpb_org_scans = imp->relation->getPermanent()->rel_scan_count++; + imp->rpb.rpb_number.setValue(BOF_NUMBER); + + if (imp->partition) + { + const auto dbb = tdbb->getDatabase(); + + imp->rpb.rpb_number.compose(dbb->dbb_max_records, dbb->dbb_dp_per_pp, + 0, 0, loPP); + imp->rpb.rpb_number.decrement(); + + imp->upper.compose(dbb->dbb_max_records, dbb->dbb_dp_per_pp, + 0, 0, hiPP); + imp->upper.decrement(); + } + + imp->scanActive = true; +} + +bool FastPathRecordReader::readRecord(UCHAR* messageBuffer, RCRD_LENGTH msgLength) +{ + fb_assert(imp && imp->initialized && messageBuffer); + return readRecords(messageBuffer, msgLength, 1) != 0; +} + +unsigned FastPathRecordReader::readRecords(UCHAR* messageBuffer, RCRD_LENGTH msgLength, unsigned maxRecords) +{ + fb_assert(imp && imp->initialized && messageBuffer); + + // A scan ended (EOF reached) and the caller asks again for records, e.g. + // when the last batch was partial. Report EOF without touching the + // finished scan. + if (!imp->scanActive) + return 0; + + if (!maxRecords) + return 0; + + if (msgLength < imp->messageLength) + { + // Internal caller invariant: the stride is derived from the same layout. + fb_assert(false); + (Arg::Gds(isc_random) << + Arg::Str("FAST_PATH message stride is smaller than the layout") << + Arg::Str(imp->burpRelation->rel_name.toQuotedString())).raise(); + } + + FbLocalStatus status; + // The attachment sync stays held for the whole batch; JRD_reschedule() + // inside the loop keeps shutdown/AST handling responsive. + EngineContextHolder tdbb(&status, imp->jAtt, FB_FUNCTION); + tdbb->setTransaction(imp->transaction); + + const auto scope = imp->partition ? DPM_next_pointer_page : DPM_next_all; + const RecordNumber* const upper = imp->partition ? &imp->upper : nullptr; + unsigned records = 0; + + while (records < maxRecords) + { + JRD_reschedule(tdbb); + + // With a pool given, VIO_next_record -> VIO_data copies the record out + // and releases the page window, so no CCH_RELEASE is needed here. + if (!VIO_next_record(tdbb, &imp->rpb, imp->transaction, tdbb->getDefaultPool(), scope, upper)) + { + imp->rpb.rpb_number.setValid(false); + imp->finishScan(); + break; + } + + UCHAR* const recordBuffer = messageBuffer + static_cast(records) * msgLength; + memset(recordBuffer, 0, msgLength); + + for (const auto& field : imp->fields) + { + dsc source; + const bool present = EVL_field(imp->relation, imp->rpb.rpb_record, field.fieldId, &source); + const SSHORT nullFlag = present ? 0 : -1; + memcpy(recordBuffer + field.nullOffset, &nullFlag, sizeof(nullFlag)); + + if (!present) + continue; + + dsc target = field.target; + target.dsc_address = recordBuffer + field.offset; + + if (field.array || DTYPE_IS_BLOB_OR_QUAD(target.dsc_dtype)) + blb::move(tdbb, &source, &target, nullptr, nullptr, 0); + else + MOV_move(tdbb, &source, &target); + } + + ++records; + } + + return records; +} + +void FastPathRecordReader::clear() +{ + if (!imp) + return; + + // Touching rel_scan_count requires the engine sync, also in this + // (possibly destructor) path. The attachment may already be shut down; + // never let that escape from here. + if (imp->scanActive && imp->jAtt && imp->relation) + { + try + { + FbLocalStatus status; + EngineContextHolder tdbb(&status, imp->jAtt, FB_FUNCTION); + imp->finishScan(); + } + catch (const Exception&) + { + imp->scanActive = false; + } + } + + imp->att = nullptr; + imp->tra = nullptr; + imp->burpRelation = nullptr; + imp->jAtt = nullptr; + imp->attachment = nullptr; + imp->transaction = nullptr; + imp->relation = nullptr; + imp->layout = nullptr; + imp->messageLength = 0; + imp->fields.clear(); + imp->resetRpb(); + imp->upper = RecordNumber(); + imp->partition = false; + imp->scanActive = false; + imp->initialized = false; +} + + +} // namespace Burp diff --git a/src/burp/engine/FastPathRecordWriter.cpp b/src/burp/engine/FastPathRecordWriter.cpp new file mode 100644 index 00000000000..5b862259a4f --- /dev/null +++ b/src/burp/engine/FastPathRecordWriter.cpp @@ -0,0 +1,390 @@ +/* + * The contents of this file are subject to the Interbase Public + * License Version 1.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy + * of the License at http://www.Inprise.com/IPL.html + * + * Software distributed under the License is distributed on an + * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express + * or implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code was created by Inprise Corporation + * and its predecessors. Portions created by Inprise Corporation are + * Copyright (C) Inprise Corporation. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + * + * Adriano dos Santos Fernandes + */ + +#include "firebird.h" + +// burp.h and ini.h both define struct gfld with different layouts +#define gfld burp_gfld +#include "../burp/FastPathRecordWriter.h" +#include "../burp/RestoreMessageLayout.h" +#include "../burp/burp.h" +#undef gfld + +#include "../burp/burp_proto.h" +#include "../common/dsc_proto.h" +#include "../common/msg_encode.h" +#include "../jrd/EngineInterface.h" +#include "../jrd/Attachment.h" +#include "../jrd/jrd.h" +#include "../jrd/Relation.h" +#include "../jrd/met.h" +#include "../jrd/met_proto.h" +#include "../jrd/align.h" +#include "../jrd/blb.h" +#include "../jrd/BulkInsert.h" +#include "../jrd/evl_proto.h" +#include "../jrd/mov_proto.h" +#include "../jrd/obj.h" +#include "../jrd/req.h" +#include "../jrd/rlck_proto.h" +#include "../jrd/tdbb.h" +#include "../jrd/tra.h" +#include "../jrd/vio_proto.h" +#include "../jrd/val.h" + +using namespace Firebird; +using namespace Jrd; + +namespace Burp { + + +struct FastPathRecordWriter::Imp +{ + struct Field + { + dsc source; + dsc target; + RCRD_OFFSET sourceOffset; + RCRD_OFFSET nullOffset; + USHORT targetId; + bool array; + }; + + IAttachment* att = nullptr; + ITransaction* tra = nullptr; + const burp_rel* burpRelation = nullptr; + ::JAttachment* jAtt = nullptr; + Attachment* attachment = nullptr; + jrd_tra* transaction = nullptr; + jrd_rel* relation = nullptr; + const Format* format = nullptr; + AutoPtr record; + + HalfStaticArray fields; + RCRD_LENGTH messageLength = 0; + record_param rpb; + bool initialized = false; + + Imp() + : fields(*getDefaultMemoryPool()), + rpb() + {} + + void setupRecord(thread_db* tdbb) + { + rpb.rpb_relation = relation; + // VIO_record() allocates rpb_record when null and returns it. + record = VIO_record(tdbb, &rpb, format, tdbb->getDefaultPool()); + rpb.rpb_format_number = format->fmt_version; + } + + void writeRecord(thread_db* tdbb, const UCHAR* messageBuffer) + { + // Register the bulk insert before field conversion so blobs/arrays + // use the batched secondary-page path (bulk=true) instead of + // row-at-a-time DPM_store_blob. The object persists across records and + // message batches; it is finished at table end (finishBulk) and at + // commit. + const auto bulk = transaction->getBulkInsert(tdbb, relation, true); + if (!bulk) + { + fb_assert(false); + (Arg::Gds(isc_random) << + Arg::Str("FAST_PATH could not start bulk insert") << + Arg::Str(burpRelation->rel_name.toQuotedString())).raise(); + } + + rpb.rpb_number.setValue(BOF_NUMBER); + rpb.rpb_record->nullify(); + + for (const auto& field : fields) + { + SSHORT nullFlag; + memcpy(&nullFlag, messageBuffer + field.nullOffset, sizeof(nullFlag)); + + if (nullFlag) + continue; + + rpb.rpb_record->clearNull(field.targetId); + + dsc src = field.source; + src.dsc_address = const_cast(messageBuffer + field.sourceOffset); + + dsc dst = field.target; + dst.dsc_address = rpb.rpb_record->getData() + (IPTR) field.target.dsc_address; + + if (DTYPE_IS_BLOB(dst.dsc_dtype) || field.array) + blb::move(tdbb, &src, &dst, relation, rpb.rpb_record, field.targetId, true); + else + MOV_move(tdbb, &src, &dst); + } + + rpb.rpb_address = rpb.rpb_record->getData(); + rpb.rpb_length = format->fmt_length; + rpb.rpb_record->setTransactionNumber(transaction->tra_number); + + // Buffer the record into the transaction's bulk-insert page images. + // BulkInsert performs no validation, matching the FAST_PATH + // design of trusting trusted backups. REPL_store() is + // intentionally not called: this path only populates freshly created + // databases. + bulk->putRecord(tdbb, &rpb, transaction); + } +}; + + +[[noreturn]] static void raiseInvalidField(const burp_rel* relation, const burp_fld* field, + const char* reason) +{ + (Arg::Gds(isc_gbak_inv_column) << Arg::Str(field->fld_name) << + Arg::Str(relation->rel_name.toQuotedString()) << Arg::Str(reason)).raise(); +} + + +FastPathRecordWriter::FastPathRecordWriter() + : imp(FB_NEW_POOL(*getDefaultMemoryPool()) Imp()) +{ +} + +FastPathRecordWriter::~FastPathRecordWriter() +{ + clear(); +} + +void FastPathRecordWriter::init(IAttachment* att, ITransaction* tra, const burp_rel* relation, + const RestoreMessageLayout& layout) +{ + fb_assert(att && tra && relation && imp); + + imp->initialized = false; + imp->fields.clear(); + + // Drop the record of a previous init(): VIO_record() would otherwise reuse + // rpb_record and assigning it back to the owning AutoPtr would free it. + imp->record = nullptr; + imp->rpb = record_param(); + imp->messageLength = layout.getLength(); + imp->att = att; + imp->tra = tra; + imp->burpRelation = relation; + imp->jAtt = static_cast<::JAttachment*>(att); + imp->attachment = imp->jAtt->getHandle(); + fb_assert(imp->attachment); + + FbLocalStatus status; + EngineContextHolder tdbb(&status, imp->jAtt, FB_FUNCTION); + imp->transaction = imp->jAtt->getEngineTransaction(&status, tra); + + if (status->hasData()) + BURP_abort(&status); + + if (!imp->transaction) + Arg::Gds(isc_bad_trans_handle).raise(); + + tdbb->setTransaction(imp->transaction); + + QualifiedName qualifiedName(relation->rel_name); + // Resolve old unqualified backup names through the target attachment's + // default schema; explicitly qualified names are left unchanged. + imp->attachment->qualifyExistingName(tdbb, qualifiedName, {obj_relation}); + + imp->relation = MetadataCache::getVersioned(tdbb, qualifiedName, CacheFlag::AUTOCREATE); + + if (!imp->relation) + { + (Arg::Gds(isc_gbak_tbl_missing) << + Arg::Str(qualifiedName.toQuotedString())).raise(); + } + + if (relation->rel_type != rel_persistent || + (relation->rel_flags & (::REL_view | ::REL_external)) || + imp->relation->isSystem() || imp->relation->isView() || + imp->relation->isVirtual() || imp->relation->isTemporary() || + imp->relation->getExtFile()) + { + fb_assert(false); + (Arg::Gds(isc_random) << + Arg::Str("FAST_PATH selected an ineligible relation") << + Arg::Str(relation->rel_name.toQuotedString())).raise(); + } + + imp->format = imp->relation->currentFormat(tdbb); + + if (!imp->format) + { + fb_assert(false); + (Arg::Gds(isc_random) << + Arg::Str("FAST_PATH relation has no current format") << + Arg::Str(relation->rel_name.toQuotedString())).raise(); + } + + imp->setupRecord(tdbb); + + for (const auto& layoutField : layout) + { + const burp_fld* field = layoutField.metadata; + const dsc source = layoutField.descriptor; + + const auto fieldId = MET_lookup_field(tdbb, imp->relation, MetaName(field->fld_name)); + if (fieldId < 0 || fieldId >= imp->format->fmt_count) + raiseInvalidField(relation, field, "field is not present in the target table"); + + for (const auto& mapping : imp->fields) + { + if (mapping.targetId == fieldId) + raiseInvalidField(relation, field, "field is mapped more than once"); + } + + const dsc target = imp->format->fmt_desc[fieldId]; + const auto targetOffset = (IPTR) target.dsc_address; + + // Computed fields are excluded from the message, so a computed/unknown descriptor here + // means inconsistent backup/target metadata. + if (target.isUnknown() || (target.dsc_flags & DSC_computed) || targetOffset <= 0 || + static_cast(targetOffset) > imp->format->fmt_length || + target.dsc_length > imp->format->fmt_length - static_cast(targetOffset)) + { + raiseInvalidField(relation, field, "invalid target descriptor"); + } + + Imp::Field mapping; + mapping.source = source; + mapping.target = target; + mapping.sourceOffset = layoutField.offset; + mapping.nullOffset = layoutField.nullOffset; + mapping.targetId = static_cast(fieldId); + mapping.array = (field->fld_flags & FLD_array) != 0; + imp->fields.add(mapping); + } + + RLCK_reserve_relation(tdbb, imp->transaction, imp->relation->getPermanent(), true); + + // Note: the transaction's bulk insert is registered lazily by the first + // stored row and finished at table end (see finishBulk), so + // nothing is set up here. + imp->initialized = true; +} + +void FastPathRecordWriter::writeRecords(const UCHAR* messageBuffer, RCRD_LENGTH msgLength, + unsigned recordCount) +{ + fb_assert(imp && imp->initialized && messageBuffer); + + if (!recordCount) + return; + + // msgLength is the in-memory stride and may exceed the serialized message + // length to preserve descriptor alignment between packed records. + if (msgLength < imp->messageLength) + { + // Internal caller invariant: the stride is derived from the same layout. + fb_assert(false); + (Arg::Gds(isc_random) << + Arg::Str("FAST_PATH message stride is smaller than the layout") << + Arg::Str(imp->burpRelation->rel_name.toQuotedString())).raise(); + } + + // One engine attachment sync processes the whole batch. The direct path is + // fail-fast: BulkInsert has no rollback for blob/array side effects, so + // continuing after a failed record could leave partial row data. + FbLocalStatus status; + EngineContextHolder tdbb(&status, imp->jAtt, FB_FUNCTION); + tdbb->setTransaction(imp->transaction); + + for (unsigned record = 0; record < recordCount; ++record) + { + const UCHAR* const recordBuffer = messageBuffer + static_cast(record) * msgLength; + imp->writeRecord(tdbb, recordBuffer); + } +} + +void FastPathRecordWriter::releaseRequestlessArrays() +{ + if (!imp || !imp->initialized || !imp->transaction) + return; + + // Touching the transaction's array list requires the engine sync. + FbLocalStatus status; + EngineContextHolder tdbb(&status, imp->jAtt, FB_FUNCTION); + tdbb->setTransaction(imp->transaction); + + blb::releaseRequestlessArrays(imp->transaction); +} + +void FastPathRecordWriter::finishBulk() +{ + if (!imp || !imp->initialized || !imp->transaction) + return; + + FbLocalStatus status; + EngineContextHolder tdbb(&status, imp->jAtt, FB_FUNCTION); + tdbb->setTransaction(imp->transaction); + + // Flush buffered pages and unregister from the transaction. Called on + // table end and at commit (as a safety net). Not called at every + // message-batch boundary: the bulk object auto-flushes full + // extents internally and its memory stays bounded (one extent plus blob + // pages), while forcing a flush per batch would strand a partial-extent + // tail each time and bloat the restored database. The next stored row + // transparently registers a fresh bulk object when needed. + imp->transaction->finiBulkInsert(tdbb, true); +} + +void FastPathRecordWriter::clear() +{ + if (!imp) + return; + + // Touching the transaction's array list requires the engine sync, also in + // this (possibly destructor) path. The attachment may already be shut + // down; never let that escape from here. + if (imp->initialized && imp->transaction && imp->jAtt) + { + try + { + FbLocalStatus status; + EngineContextHolder tdbb(&status, imp->jAtt, FB_FUNCTION); + tdbb->setTransaction(imp->transaction); + + blb::releaseRequestlessArrays(imp->transaction); + } + catch (const Exception&) + { + } + } + + imp->att = nullptr; + imp->tra = nullptr; + imp->burpRelation = nullptr; + imp->jAtt = nullptr; + imp->attachment = nullptr; + imp->transaction = nullptr; + imp->relation = nullptr; + imp->format = nullptr; + imp->record = nullptr; + imp->fields.clear(); + imp->messageLength = 0; + imp->rpb = record_param(); + imp->initialized = false; +} + + +} // namespace Burp diff --git a/src/burp/main/FastPathBurpProviderStub.cpp b/src/burp/main/FastPathBurpProviderStub.cpp new file mode 100644 index 00000000000..11880803b7d --- /dev/null +++ b/src/burp/main/FastPathBurpProviderStub.cpp @@ -0,0 +1,60 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2026 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#include "firebird.h" + +#include "../burp/FastPathBurpProvider.h" +#include "../common/utils_proto.h" + +using namespace Firebird; + +namespace Burp { + + +// Complete type for Firebird::AutoPtr. The real definition lives in +// burp/engine/FastPathBurpUtil.cpp, which is not linked into standalone gbak. +struct FastPathBurpProvider::EngineHolder +{ +}; + + +FastPathBurpProvider::FastPathBurpProvider(bool useFastPath) + : provider(dispatcher) +{ + if (useFastPath) + { + (Arg::Gds(isc_random) << + Arg::Str("FAST_PATH mode requires service manager")).raise(); + } +} + +FastPathBurpProvider::~FastPathBurpProvider() = default; + +IAttachment* FastPathBurpProvider::attachWorker(CheckStatusWrapper* status, const char* dbName, + unsigned dpbLen, const unsigned char* dpb, ICryptKeyCallback*) +{ + (Arg::Gds(isc_random) << + Arg::Str("FAST_PATH mode requires service manager")).raise(); +} + + +} // namespace Burp diff --git a/src/burp/main/FastPathRecordReaderStub.cpp b/src/burp/main/FastPathRecordReaderStub.cpp new file mode 100644 index 00000000000..18413201a50 --- /dev/null +++ b/src/burp/main/FastPathRecordReaderStub.cpp @@ -0,0 +1,71 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2026 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#include "firebird.h" +#include "../burp/FastPathRecordReader.h" +#include "../burp/burp_proto.h" + +namespace Burp { + + +// Complete type for Firebird::AutoPtr. The real definition lives in +// burp/engine/FastPathRecordReader.cpp, which is not linked into standalone gbak. +struct FastPathRecordReader::Imp +{ +}; + +FastPathRecordReader::FastPathRecordReader() + : imp(nullptr) +{ +} + +FastPathRecordReader::~FastPathRecordReader() = default; + +void FastPathRecordReader::init(Firebird::IAttachment*, Firebird::ITransaction*, const burp_rel*, + const RestoreMessageLayout&, bool) +{ + BURP_error(430, true); +} + +void FastPathRecordReader::start(ULONG, ULONG) +{ + fb_assert(false); +} + +bool FastPathRecordReader::readRecord(UCHAR*, RCRD_LENGTH) +{ + fb_assert(false); + return false; +} + +unsigned FastPathRecordReader::readRecords(UCHAR*, RCRD_LENGTH, unsigned) +{ + fb_assert(false); + return 0; +} + +void FastPathRecordReader::clear() +{ +} + + +} // namespace Burp diff --git a/src/burp/main/FastPathRecordWriterStub.cpp b/src/burp/main/FastPathRecordWriterStub.cpp new file mode 100644 index 00000000000..99d6465a455 --- /dev/null +++ b/src/burp/main/FastPathRecordWriterStub.cpp @@ -0,0 +1,66 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2026 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#include "firebird.h" +#include "../burp/FastPathRecordWriter.h" +#include "../burp/burp_proto.h" + +namespace Burp { + + +// Complete type for Firebird::AutoPtr. The real definition lives in +// burp/engine/FastPathRecordWriter.cpp, which is not linked into standalone gbak. +struct FastPathRecordWriter::Imp +{ +}; + + +FastPathRecordWriter::FastPathRecordWriter() = default; + +FastPathRecordWriter::~FastPathRecordWriter() = default; + +void FastPathRecordWriter::init(Firebird::IAttachment*, Firebird::ITransaction*, const burp_rel*, + const RestoreMessageLayout&) +{ + BURP_error(430, true); +} + +void FastPathRecordWriter::writeRecords(const UCHAR*, RCRD_LENGTH, unsigned) +{ + fb_assert(false); +} + +void FastPathRecordWriter::releaseRequestlessArrays() +{ +} + +void FastPathRecordWriter::finishBulk() +{ + fb_assert(false); +} + +void FastPathRecordWriter::clear() +{ +} + + +} // namespace Burp diff --git a/src/burp/restore.epp b/src/burp/restore.epp index 6b1e98e4bc7..ced03f6093d 100644 --- a/src/burp/restore.epp +++ b/src/burp/restore.epp @@ -49,6 +49,7 @@ #include "../burp/mvol_proto.h" #include "../burp/resto_proto.h" #include "../burp/BurpTasks.h" +#include "../burp/FastPathRecordWriter.h" #include "../common/gdsassert.h" #include "../jrd/constants.h" #include "../remote/protocol.h" @@ -66,6 +67,7 @@ #include "../common/ThreadStart.h" #include "../common/msg_encode.h" #include "../common/classes/BatchCompletionState.h" +#include "../burp/FastPathBurpProvider.h" using MsgFormat::SafeArg; using namespace Firebird; @@ -352,8 +354,26 @@ int RESTORE_restore (const TEXT* file_name, const TEXT* database_name) Firebird::IRequest* req_handle3 = nullptr; QualifiedMetaString indexName; - Firebird::DispatcherPtr provider; BurpGlobals* tdgbl = BurpGlobals::getSpecific(); + FastPathBurpProvider burpProvider(tdgbl->gbl_fast_path); + + IProvider* provider = burpProvider.get(); + + // The fast-path provider bypasses the y-valve, so the crypt callback held by + // the service (normally installed as y-valve TLS state by fillDpb) is not + // picked up automatically. Set it explicitly, unless the backup itself + // supplied the keys (KEYHOLDER). + if (tdgbl->gbl_fast_path && !tdgbl->gbl_sw_keyholder) + { + if (const auto cryptCb = tdgbl->uSvc->getCryptCallback()) + { + FbLocalStatus status; + provider->setDbCryptCallback(&status, cryptCb); + if (status->getState() & IStatus::STATE_ERRORS) + BURP_abort(&status); + } + } + tdgbl->gbl_sw_transportable = tdgbl->gbl_sw_compress = false; if (!restore(tdgbl, provider, file_name, database_name)) @@ -3500,11 +3520,25 @@ void get_data(BurpGlobals* tdgbl, burp_rel* relation, WriteRelationReq* req) if (batch) req->execBatch(tdgbl); } + + req->execFastPathBatch(); + + // Flush and destroy the fast-path writer while the transaction is + // still valid. Must run before the incremental commit below: the + // commit destroys the engine transaction held by the writer. + req->finishFastPathBulk(); } catch (const FbException& ex) { BURP_abort(ex.getStatus()); } + catch (const status_exception& ex) + { + // Engine errors raised by the FAST_PATH direct writer. + FbLocalStatus status; + ex.stuffException(&status); + BURP_abort(&status); + } if (tdgbl->gbl_sw_incremental) commit_relation_data(tdgbl, relation); @@ -8258,6 +8292,9 @@ bool get_relation(BurpGlobals* tdgbl, Coordinator* coord, RestoreRelationTask* t } } + relation->rel_system = (sys_flag != fb_sysflag_user); + relation->rel_type = (SSHORT) type; + // If this is a view and there is a global transaction then use it Firebird::ITransaction* local_trans; if (view_blr_null || !tdgbl->global_trans) @@ -14050,18 +14087,38 @@ void WriteRelationMeta::clear() { m_relation = nullptr; m_batchMode = false; + m_fastPathMode = false; m_batchOk = false; m_batchMeta = nullptr; m_sqlStatement.clear(); m_blr.clear(); + m_messageLayout.clear(); m_inMgsNum = 0; m_inMsgLen = 0; m_blobCount = 0; } -void WriteRelationMeta::setRelation(BurpGlobals* tdgbl, const burp_rel* relation) +void WriteRelationMeta::setRelation(BurpGlobals* tdgbl, burp_rel* relation) { m_relation = relation; + + if (tdgbl->gbl_fast_path && tdgbl->RESTORE_format >= 8 && + !relation->rel_system && + !(relation->rel_flags & (REL_view | REL_external)) && + relation->rel_type == rel_persistent) + { + m_fastPathMode = true; + m_batchMode = false; + prepareMessageLayout(tdgbl); + return; + } + + buildNonFastPathMode(tdgbl); +} + +void WriteRelationMeta::buildNonFastPathMode(BurpGlobals* tdgbl) +{ + m_fastPathMode = false; m_batchMode = true; m_batchOk = false; @@ -14079,7 +14136,7 @@ void WriteRelationMeta::setRelation(BurpGlobals* tdgbl, const burp_rel* relation bool sysDomFlag = false; burp_fld* field; - for (field = relation->rel_fields; field && !sysDomFlag; field = field->fld_next) + for (field = m_relation->rel_fields; field && !sysDomFlag; field = field->fld_next) { if (field->fld_flags & FLD_computed) continue; @@ -14347,6 +14404,16 @@ bool WriteRelationMeta::prepareBatch(BurpGlobals* tdgbl) return true; } +void WriteRelationMeta::prepareMessageLayout(BurpGlobals* tdgbl) +{ + m_batchMode = false; + m_inMgsNum = 0; + + m_messageLayout.build(m_relation, tdgbl->gbl_sw_fix_fss_data, + CSetId(tdgbl->gbl_sw_fix_fss_data_id)); + m_inMsgLen = m_messageLayout.getLength(); +} + void WriteRelationMeta::prepareRequest(BurpGlobals* tdgbl) { const bool use_bulk_insert = (tdgbl->runtimeODS >= DB_VERSION_DDL14); @@ -14610,6 +14677,7 @@ void WriteRelationReq::reset(WriteRelationMeta* meta) m_request = 0; m_recs = 0; m_batchRecs = 0; + m_fastPathBatchRecs = 0; m_resync = true; } } @@ -14620,6 +14688,7 @@ void WriteRelationReq::clear() m_meta = NULL; m_recs = 0; m_batchRecs = 0; + m_fastPathBatchRecs = 0; m_resync = true; if (m_batch) @@ -14633,10 +14702,76 @@ void WriteRelationReq::clear() release(); m_request = nullptr; } + + m_fastPathWriter = nullptr; + m_fastPathBatch.clear(); +} + +void WriteRelationReq::initFastPathWriter(IAttachment* att, ITransaction* tra, + const burp_rel* relation, const RestoreMessageLayout& layout) +{ + if (!m_fastPathWriter) + m_fastPathWriter = FB_NEW_POOL(*getDefaultMemoryPool()) FastPathRecordWriter(); + + m_fastPathWriter->init(att, tra, relation, layout); +} + +void WriteRelationReq::execFastPathBatch() +{ + if (!m_fastPathWriter || !m_fastPathBatchRecs) + return; + + const unsigned recordCount = m_fastPathBatchRecs; + const RCRD_LENGTH recordLength = m_inMsg.getCount(); + + // In-memory stride preserves descriptor alignment between packed records; + // the serialized backup format still uses recordLength. + RCRD_LENGTH stride = recordLength; + + if (m_meta) + { + const RCRD_LENGTH layoutStride = m_meta->getMessageLayout().getStride(); + if (layoutStride > stride) + stride = layoutStride; + } + + const UCHAR* const batchBuffer = m_fastPathBatch.begin(); + m_fastPathBatchRecs = 0; + + // The direct writer is fail-fast. Any conversion or engine error aborts + // the restore instead of continuing with a partially isolated row. + m_fastPathWriter->writeRecords(batchBuffer, stride, recordCount); + + // Do not finish the bulk insert here: the BulkInsert object auto-flushes + // full extents internally and stays alive across message batches. Forcing + // a flush per staging batch would strand a partial-extent tail each time + // (thousands of empty pages on large tables). Flushing happens at table end + // via finishFastPathBulk() and at commit. Arrays are released eagerly in blb::move; + // releaseRequestlessArrays() handles any remaining request-less arrays. + m_fastPathWriter->releaseRequestlessArrays(); + + m_fastPathBatch.clear(); +} + +void WriteRelationReq::finishFastPathBulk() +{ + if (m_fastPathWriter) + { + m_fastPathWriter->finishBulk(); + + // Destroy the writer after flushing: it caches the raw engine + // transaction, which gets replaced by commit_relation_data() in + // incremental restore. initFastPathWriter() creates a fresh writer for + // every table. + m_fastPathWriter = nullptr; + } } void WriteRelationReq::compile(BurpGlobals* tdgbl, IAttachment* att) { + if (m_meta->m_fastPathMode) + return; + if (m_meta->m_batchMode) m_batch = m_meta->createBatch(tdgbl, att); @@ -14657,6 +14792,40 @@ void WriteRelationReq::send(BurpGlobals* tdgbl, ITransaction* tran, bool lastRec { m_recs++; + if (m_meta->m_fastPathMode) + { + fb_assert(m_fastPathWriter); + + const RCRD_LENGTH recordLength = m_inMsg.getCount(); + RCRD_LENGTH stride = recordLength; + const RCRD_LENGTH layoutStride = m_meta->getMessageLayout().getStride(); + if (layoutStride > stride) + stride = layoutStride; + + if (m_fastPathBatchRecs && + (m_fastPathBatchRecs >= FAST_PATH_RESTORE_BATCH_MAX_RECORDS || + m_fastPathBatch.getCount() + stride > FAST_PATH_RESTORE_BATCH_MAX_BYTES)) + { + execFastPathBatch(); + } + + const FB_SIZE_T base = m_fastPathBatch.getCount(); + m_fastPathBatch.add(m_inMsg.begin(), recordLength); + // Pad to the aligned stride so the next record keeps field alignment. + // Array::grow zero-fills the new tail. + if (stride > recordLength) + m_fastPathBatch.grow(base + stride); + ++m_fastPathBatchRecs; + + if (lastRec || m_fastPathBatchRecs >= FAST_PATH_RESTORE_BATCH_MAX_RECORDS || + m_fastPathBatch.getCount() >= FAST_PATH_RESTORE_BATCH_MAX_BYTES) + { + execFastPathBatch(); + } + + return; + } + if (m_batch) { m_batch->add(&tdgbl->throwStatus, 1, m_batchMsg.begin()); @@ -14707,6 +14876,9 @@ void WriteRelationReq::execBatch(BurpGlobals * tdgbl) void WriteRelationReq::release() { + // Release fast-path record while the engine attachment is still valid. + m_fastPathWriter = nullptr; + if (m_batch) { m_batch->release(); @@ -15129,6 +15301,11 @@ bool RestoreRelationTask::tableWriter(BurpGlobals* tdgbl, Item& item) { item.m_request.reset(&m_metadata); + if (m_metadata.m_fastPathMode) + { + item.m_request.initFastPathWriter(tdgbl->db_handle, tdgbl->tr_handle, + item.m_relation, m_metadata.getMessageLayout()); + } get_data(tdgbl, item.m_relation, &item.m_request); item.getRestoreTask()->releaseBuffer(item); diff --git a/src/include/firebird/impl/consts_pub.h b/src/include/firebird/impl/consts_pub.h index b10250b0822..3831a26788a 100644 --- a/src/include/firebird/impl/consts_pub.h +++ b/src/include/firebird/impl/consts_pub.h @@ -445,6 +445,7 @@ #define isc_spb_bkp_no_triggers 0x8000 #define isc_spb_bkp_zip 0x010000 #define isc_spb_bkp_direct_io 0x020000 +#define isc_spb_bkp_fast_path 0x040000 /******************************************** * Parameters for isc_action_svc_properties * @@ -575,6 +576,7 @@ #define isc_spb_res_replace 0x1000 #define isc_spb_res_create 0x2000 #define isc_spb_res_use_all_space 0x4000 +#define isc_spb_res_fast_path isc_spb_bkp_fast_path #define isc_spb_res_direct_io isc_spb_bkp_direct_io #define isc_spb_res_replica_mode 20 diff --git a/src/include/firebird/impl/msg/gbak.h b/src/include/firebird/impl/msg/gbak.h index d678c187a57..7ff2b5855c8 100644 --- a/src/include/firebird/impl/msg/gbak.h +++ b/src/include/firebird/impl/msg/gbak.h @@ -423,3 +423,8 @@ FB_IMPL_MSG(GBAK, 424, gbak_writing_constant, -901, "00", "000", "writing consta FB_IMPL_MSG(GBAK, 425, gbak_constant, -901, "00", "000", "constant (in RDB$CONSTANTS)") FB_IMPL_MSG(GBAK, 426, gbak_restoring_constant, -901, "00", "000", "restoring constant %s") FB_IMPL_MSG_NO_SYMBOL(GBAK, 427, "skipping data for table @1") +FB_IMPL_MSG(GBAK, 428, gbak_inv_column, -901, "00", "000", "invalid column @1 in table @2: @3") +FB_IMPL_MSG(GBAK, 429, gbak_inv_record_length, -901, "00", "000", "invalid record length for table @1") +FB_IMPL_MSG(GBAK, 430, gbak_fast_path_needs_service, -901, "00", "000", "FAST_PATH requires the -service option") +FB_IMPL_MSG_NO_SYMBOL(GBAK, 431, " @1FAST(_PATH) fast-path data transfer (requires -service)") +FB_IMPL_MSG(GBAK, 432, gbak_fast_path_no_snapshot, -901, "00", "000", "cannot obtain transaction snapshot number for FAST_PATH backup") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 7510eaa9468..393399381a7 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -4513,6 +4513,7 @@ IPerformanceStatsImpl = class(IPerformanceStats) isc_spb_bkp_no_triggers = $8000; isc_spb_bkp_zip = $010000; isc_spb_bkp_direct_io = $020000; + isc_spb_bkp_fast_path = $040000; isc_spb_prp_page_buffers = byte(5); isc_spb_prp_sweep_interval = byte(6); isc_spb_prp_shutdown_db = byte(7); @@ -6343,6 +6344,10 @@ IPerformanceStatsImpl = class(IPerformanceStats) isc_gbak_writing_constant = 336331176; isc_gbak_constant = 336331177; isc_gbak_restoring_constant = 336331178; + isc_gbak_inv_column = 336331180; + isc_gbak_inv_record_length = 336331181; + isc_gbak_fast_path_needs_service = 336331182; + isc_gbak_fast_path_no_snapshot = 336331184; isc_dsql_too_old_ods = 336397205; isc_dsql_table_not_found = 336397206; isc_dsql_view_not_found = 336397207; diff --git a/src/jrd/blb.cpp b/src/jrd/blb.cpp index c0c72b97bc9..dfcb58164af 100644 --- a/src/jrd/blb.cpp +++ b/src/jrd/blb.cpp @@ -1122,7 +1122,7 @@ void blb::move(thread_db* tdbb, dsc* from_desc, dsc* to_desc, } record->clearNull(fieldId); - jrd_tra* transaction = request->req_transaction; + jrd_tra* transaction = request ? request->req_transaction : tdbb->getTransaction(); transaction = transaction->getOuter(); // Declared LTT records live only in an execution frame, while their BLOB @@ -1343,23 +1343,40 @@ void blb::move(thread_db* tdbb, dsc* from_desc, dsc* to_desc, blobIndex->bli_materialized = true; blobIndex->bli_blob_id = *destination; - // Assign temporary BLOB ownership to top-level request if it is not assigned yet - Request* own_request; - if (blobIndex->bli_request) { - own_request = blobIndex->bli_request; + + if (request) + { + // Assign temporary BLOB ownership to top-level request if it is not assigned yet + Request* own_request; + if (blobIndex->bli_request) { + own_request = blobIndex->bli_request; + } + else + { + own_request = request; + while (own_request->req_caller) + own_request = own_request->req_caller; + blobIndex->bli_request = own_request; + own_request->req_blobs.add(blob->blb_temp_id); + } + // Not sure that this ownership is entirely correct for arrays, but + // even if I make mistake here widening array lifetime this should not hurt much + if (array) + array->arr_request = own_request; } - else + else if (array) { - own_request = request; - while (own_request->req_caller) - own_request = own_request->req_caller; - blobIndex->bli_request = own_request; - own_request->req_blobs.add(blob->blb_temp_id); + // Direct-VIO burp path (no request exists). The array payload has + // already been copied into the blob by store_array and the blob + // stored above, so the ArrayField is no longer needed. Release it + // immediately to keep memory bounded to the current batch instead + // of accumulating full array buffers until transaction end (the + // request-owned path releases at request end; here there is no + // request, so without this the 1MB message-batch limit would not + // bound engine allocations). + release_array(array); + array = nullptr; } - // Not sure that this ownership is entirely correct for arrays, but - // even if I make mistake here widening array lifetime this should not hurt much - if (array) - array->arr_request = own_request; } const bool purgeBlob = !materialized_blob || @@ -1953,6 +1970,23 @@ void blb::release_array(ArrayField* array) } +void blb::releaseRequestlessArrays(jrd_tra* transaction) +{ + if (!transaction) + return; + + for (ArrayField** ptr = &transaction->tra_arrays; *ptr;) + { + const auto array = *ptr; + + if (!array->arr_request) + release_array(array); + else + ptr = &(*ptr)->arr_next; + } +} + + void blb::scalar(thread_db* tdbb, jrd_tra* transaction, const bid* blob_id, diff --git a/src/jrd/blb.h b/src/jrd/blb.h index ffecca6a8bf..b0b294d40b3 100644 --- a/src/jrd/blb.h +++ b/src/jrd/blb.h @@ -118,6 +118,12 @@ class blb : public pool_alloc void BLB_put_segment(thread_db*, const void*, USHORT); static void put_slice(thread_db*, jrd_tra*, bid*, const UCHAR*, USHORT, const UCHAR*, SLONG, UCHAR*); static void release_array(ArrayField*); + + // Release ArrayFields created on the request-less fast-path (arr_request + // == nullptr), e.g. arrays belonging to records skipped after a conversion + // failure. Safe to call after a batch: future batches have not created their + // arrays yet, so all remaining request-less arrays belong to processed records. + static void releaseRequestlessArrays(jrd_tra* transaction); static void scalar(thread_db*, jrd_tra*, const bid*, USHORT, const SLONG*, impure_value*); static void delete_blob_id(thread_db*, const bid*, ULONG, Jrd::jrd_rel*); diff --git a/src/jrd/svc.cpp b/src/jrd/svc.cpp index 88d42ddd573..f08a0b8024f 100644 --- a/src/jrd/svc.cpp +++ b/src/jrd/svc.cpp @@ -3163,6 +3163,8 @@ bool Service::process_switches(ClumpletReader& spb, string& switches) status_exception::raise(Arg::Gds(isc_unexp_spb_form) << Arg::Str(SPB_SEC_USERNAME)); } + // FAST_PATH requires -service; this is enforced in burp.cpp via uSvc->isService(). + // postfixes for burp & nbackup switch (svc_action) { diff --git a/src/utilities/fbsvcmgr/fbsvcmgr.cpp b/src/utilities/fbsvcmgr/fbsvcmgr.cpp index 00d17f99106..74281d9c1b2 100644 --- a/src/utilities/fbsvcmgr/fbsvcmgr.cpp +++ b/src/utilities/fbsvcmgr/fbsvcmgr.cpp @@ -427,6 +427,7 @@ constexpr SvcSwitches backupOptions[] = {"bkp_zip", putOption, 0, isc_spb_bkp_zip, 0 }, {"bkp_parallel_workers", putIntArgument, 0, isc_spb_bkp_parallel_workers, 0}, {"bkp_direct_io", putOption, 0, isc_spb_bkp_direct_io, 0}, + {"bkp_fast_path", putOption, 0, isc_spb_bkp_fast_path, 0}, {0, 0, 0, 0, 0} }; @@ -460,6 +461,7 @@ constexpr SvcSwitches restoreOptions[] = {"res_crypt", putStringArgument, 0, isc_spb_res_crypt, 0 }, {"res_replica_mode", putReplicaMode, 0, isc_spb_res_replica_mode, 0}, {"res_parallel_workers", putIntArgument, 0, isc_spb_res_parallel_workers, 0}, + {"res_fast_path", putOption, 0, isc_spb_res_fast_path, 0}, {"res_direct_io", putOption, 0, isc_spb_res_direct_io, 0}, {0, 0, 0, 0, 0} }; From 3ff4d8aac50f0f9d70135a9941089ed0195f9916 Mon Sep 17 00:00:00 2001 From: AlexPeshkoff Date: Mon, 28 Sep 2026 15:53:56 +0300 Subject: [PATCH 23/30] Fixed segfault in bugs/gh_8968_test.py --- src/jrd/Savepoint.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/jrd/Savepoint.cpp b/src/jrd/Savepoint.cpp index 215bffe1ab9..c86cd922cf9 100644 --- a/src/jrd/Savepoint.cpp +++ b/src/jrd/Savepoint.cpp @@ -513,8 +513,6 @@ Savepoint* Savepoint::rollback(thread_db* tdbb, Savepoint* prior, bool preserveL auto permanent = relation->getPermanent(); permanent->rollback(tdbb); - RelationPermanent::destroy(tdbb, permanent); - delete permanent; } delete *lttPtr; From 9011c81cdb33c4041d538b517c952e0f61894b1d Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 29 Sep 2026 00:10:55 +0000 Subject: [PATCH 24/30] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 2eb09e3597f..b7e37847900 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:2188 + FORMAL BUILD NUMBER:2190 */ -#define PRODUCT_VER_STRING "6.0.0.2188" -#define FILE_VER_STRING "WI-T6.0.0.2188" -#define LICENSE_VER_STRING "WI-T6.0.0.2188" -#define FILE_VER_NUMBER 6, 0, 0, 2188 +#define PRODUCT_VER_STRING "6.0.0.2190" +#define FILE_VER_STRING "WI-T6.0.0.2190" +#define LICENSE_VER_STRING "WI-T6.0.0.2190" +#define FILE_VER_NUMBER 6, 0, 0, 2190 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "2188" +#define FB_BUILD_NO "2190" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index a783efa4432..c0a2f28e011 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=2188 +BuildNum=2190 NowAt=`pwd` cd `dirname $0` From 5ab496e8681e73ee833ec29fb44d086341aa5ff9 Mon Sep 17 00:00:00 2001 From: ChudaykinAlex <130781208+ChudaykinAlex@users.noreply.github.com> Date: Tue, 29 Sep 2026 15:55:10 +0300 Subject: [PATCH 25/30] New feature non-decimal integer literals (SQL:2023 T661) and underscores in numeric literals (SQL:2023 T662) (#8564) * Adding SQL-compliant names to existing MAXVALUE/MINVALUE functions * Correcting the description. Adding words to keyword_or_column * Improved description * Add support non-decimal integer literals and underscores in numeric literals * Fix numeric literals lexer after merge with master - Restore introducer handling lost in the merge: the lexer returns INTRODUCER only and the charset name is parsed by the grammar (schema_opt_qualified_name). Keep the T662 check for an underscore before a digit or a period. - Restore constexpr/noexcept in chars.h and const in Parser.cpp lost in the merge. - Return LIMIT64_INT/LIMIT64_NUMBER for exactly MAX_SINT64 + 1 (decimal and non-decimal), so START WITH and SET GENERATOR accept -9223372036854775808 and -0x8000000000000000 again. - Keep data types of decimal literals unchanged: remove the DBL_DIG rule (16+ fractional digits produced DECFLOAT), use DECFLOAT for an approximate literal with a mantissa wider than 64 bits or |exponent| > DBL_MAX_10_EXP, and for scale < MIN_SCHAR. - Follow the README grammar: accept "1." and reject "1e", "1e+" and "1e-". - README: fix examples and the rule, describe data types of non-decimal literals and the incompatibility with old hexadecimal literals. --- ...EADME.decimal_and_non_decimal_literals.txt | 154 +++++ doc/sql.extensions/README.hex_literals.txt | 6 + src/dsql/Parser.cpp | 641 +++++++++--------- src/dsql/Parser.h | 8 + src/dsql/chars.h | 44 +- 5 files changed, 499 insertions(+), 354 deletions(-) create mode 100644 doc/sql.extensions/README.decimal_and_non_decimal_literals.txt diff --git a/doc/sql.extensions/README.decimal_and_non_decimal_literals.txt b/doc/sql.extensions/README.decimal_and_non_decimal_literals.txt new file mode 100644 index 00000000000..2b971acb15c --- /dev/null +++ b/doc/sql.extensions/README.decimal_and_non_decimal_literals.txt @@ -0,0 +1,154 @@ +============================================== +Decimal integer literals +Non-decimal integer literals (SQL:2023 T661) +Underscores in numeric literals (SQL:2023 T662) +============================================== + +Supports unsigned hexadecimal integers, unsigned octal integers, and unsigned binary integers. +Also support for underscores in numeric and non-decimal literals + +Authors: + Alexey Chudaykin + +Syntax rules: + + ::= + + !! U+002B + + ::= + - !! U+002D + + ::= + . !! U+002E + + ::= + _ !! U+005F + + ::= + 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | a | b | c | d | e | f + + ::= + 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 + + ::= + 0 | 1 + + ::= + [ ] + + ::= + | + + ::= + | [ ] | + + + ::= + | + + ::= + E + + ::= + [ [ ] ] | + + + ::= + + + ::= + [ ] + + ::= + [ ] + + ::= + | | | + + + ::= + [ { [ ] }... ] + + ::= + 0X { [ ] }... + + ::= + 0O { [ ] }... + + ::= + 0B { [ ] }... + +The letters X, O, B and E may be written in either case. + +Notes (non-decimal integer literals): + 1. The standard allows non-decimal literals in the mantissa of an exponential number entry. + For example (0xAAAe10), but there may be a conflict (0xEEE10). Therefore, non-decimal + literals are not allowed in the mantissa (see above): 0xAAAe10 is the + hexadecimal integer 0xAAAE10 = 11185680. + 2. The value is a numeric value defined by applying the usual + mathematical interpretation of positional hexadecimal notation to a string that is an + unsigned hexadecimal integer. Similarly for an unsigned octal integer and an + unsigned binary integer. + 3. To represent negative values, place a minus sign in front of an unsigned hexadecimal literal. + Similarly for an unsigned octal integer and an unsigned binary integer. + 4. There is no non-decimal literal of type SMALLINT: even 0x1 evaluates to INTEGER. However, + a value within the range 0x0000 (decimal zero) to 0x7FFF (decimal 32767) is converted to + SMALLINT transparently when it is assigned to a SMALLINT column, variable or parameter. + Similarly for an unsigned octal integer and an unsigned binary integer. + 5. The data type depends on the value of the literal, not on the number of digits + (leading zeros do not matter): + 0 .. 0x7FFFFFFF - INTEGER; + 0x80000000 .. 0x7FFFFFFFFFFFFFFF - BIGINT; + 0x8000000000000000 .. 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - INT128. + A greater value raises an error. Since the literal itself is unsigned, the minimal + INT128 value cannot be written as -0x80000000000000000000000000000000, use the decimal + literal -170141183460469231731687303715884105728 instead. + 6. Compatibility. Previously (see README.hex_literals) a hexadecimal literal was a signed + two's complement value, and its data type depended on the number of . Now the value + is never negative (see note 2) and the data type depends on the value (see note 5): + 0xF0000000 was -268435456 (INTEGER), now 4026531840 (BIGINT); + 0xFFFFFFFFFFFFFFFF was -1 (BIGINT), now 18446744073709551615 (INT128). + Use a minus sign to write negative values, e.g. -0x10000000 instead of 0xF0000000. + +Notes (decimal literals): + 1. Data types of decimal literals are not changed by this feature. Underscores do not affect + the data type and the scale: 1_000.5 is NUMERIC(18, 1), 12_345_678_901_234_567_890 is INT128. + +Notes (underscores in numeric literals): + 1. Limitations for non-decimal integer literals: + 1.1. It is considered unacceptable for there to be two or more consecutive underscores; + 1.2. Underscores are not allowed after the last character. + An underscore is allowed right after the prefix: 0x_FF. + 2. Limitations for decimal literals: + 2.1. Underscores before the first character and after the last character are not allowed; + 2.2. It is considered unacceptable for there to be two or more consecutive underscores; + 2.3. Underscores are not permitted before or after the symbol; + 2.4. Underscores are not allowed before or after the character and the exponent sign. + +Examples (non-decimal integer literals): + 1. Unsigned binary integer: + 1.1. select 0b11010100, 0B11010100 from rdb$database; --> 212; + 1.2. select 0b0000000 from rdb$database; --> 0; + 1.3. select -0b11010100, -0B11010100 from rdb$database; --> -212. + 2. Unsigned octal integer: + 2.1. select 0o12345670, 0O12345670 from rdb$database; --> 2739128; + 2.2. select 0o00000000 from rdb$database; --> 0; + 2.3. select -0o12345670, -0O12345670 from rdb$database; --> -2739128. + 3. Unsigned hexadecimal integer: + 3.1. select 0xABC123, 0XABC123 from rdb$database; --> 11256099; + 3.2. select 0x00000000 from rdb$database; --> 0; + 3.3. select -0xABC123, -0XABC123 from rdb$database; --> -11256099. + 3.4. select 0x7FFFFFFF from rdb$database; --> 2147483647 type INTEGER + 3.5. select 0x80000000 from rdb$database; --> 2147483648 type BIGINT + 3.6. select 0x7FFFFFFFFFFFFFFF from rdb$database; --> 9223372036854775807 type BIGINT + 3.7. select 0x8000000000000000 from rdb$database; --> 9223372036854775808 type INT128 + 3.8. select -0x80000000 from rdb$database; --> -2147483648 type INTEGER + 3.9. select -0x8000000000000000 from rdb$database; --> -9223372036854775808 type BIGINT + +Examples (underscores in numeric literals): + 1. For non-decimal integer literals: + 1.1 Permitted: select 0x_FFFF, 0xFF_FF, 0x_FF_FF from rdb$database; + 1.2 Forbidden: select 0x_FF__FF, 0xFFFFFF_, 0x_FF_FF_FF_ from rdb$database; + 2. For decimal literals: + 2.1 Permitted: select 10_10, 10_10.10_10, 10.10E-10_0 from rdb$database; + 2.2 Forbidden: select _1010, 100_, 10__10, 1010._1010, 1010_.1, 10.10E_-100_ from rdb$database; diff --git a/doc/sql.extensions/README.hex_literals.txt b/doc/sql.extensions/README.hex_literals.txt index add4a1b3acd..67093ae60b2 100644 --- a/doc/sql.extensions/README.hex_literals.txt +++ b/doc/sql.extensions/README.hex_literals.txt @@ -7,6 +7,7 @@ Support for hexadecimal numeric and binary string literals. Authors: Bill Oliver Adriano dos Santos Fernandes + Alexey Chudaykin Syntax: @@ -35,3 +36,8 @@ Notes (binary string literal): Example: select 0x10, cast('0x0F0000000' as bigint) from rdb$database; select x'deadbeef' from rdb$database; + +Notes by Alexey Chudaykin: + ISO/IEC 9075-2:2023(E) introduces rules for hexadecimal numbers, so this document should be + considered obsolete in the part. Refer to the new document + README.decimal_and_non_decimal_literals. diff --git a/src/dsql/Parser.cpp b/src/dsql/Parser.cpp index 6a2c9db5fa4..1e6da087deb 100644 --- a/src/dsql/Parser.cpp +++ b/src/dsql/Parser.cpp @@ -292,7 +292,7 @@ int Parser::yylex() bool Parser::yylexSkipSpaces() { - UCHAR tok_class; + USHORT tok_class; SSHORT c; // Find end of white space and skip comments @@ -399,7 +399,7 @@ int Parser::yylexAux() MemoryPool& pool = *tdbb->getDefaultPool(); SSHORT c = lex.ptr[-1]; - const UCHAR tok_class = classes(c); + const USHORT tok_class = classes(c); char string[MAX_TOKEN_LEN]; // Depending on tok_class of token, parse token @@ -411,6 +411,10 @@ int Parser::yylexAux() if (lex.ptr >= lex.end) return -1; + // restriction for underscores before numeric literals + if ((classes(*lex.ptr) & CHR_DIGIT) || *lex.ptr == '.') + exceptionNumericLiterals(Firebird::string(lex.last_token, lex.ptr - lex.last_token + 1)); + if (classes(*lex.ptr) & (CHR_IDENT | CHR_QUOTE)) return TOK_INTRODUCER; @@ -782,416 +786,385 @@ int Parser::yylexAux() lex.ptr = lex.last_token + 1; } - // Hexadecimal numeric constants - 0xBBBBBB - // - // where the '0' and the 'X' (or 'x') are literal, followed - // by a set of nibbles, using 0-9, a-f, or A-F. Odd numbers - // of nibbles assume a leading '0'. The result is converted - // to an integer, and the result returned to the caller. The - // token is identified as a NUMBER32BIT if it's a 32-bit or less - // value, or a NUMBER64INT if it requires a 64-bit number. - if (c == '0' && lex.ptr + 1 < lex.end && (*lex.ptr == 'x' || *lex.ptr == 'X') && - (classes(lex.ptr[1]) & CHR_HEX)) - { - bool hexerror = false; - - // Remember where we start from, to rescan later. - // Also we'll need to know the length of the buffer. - - ++lex.ptr; // Skip the 'X' and point to the first digit - const char* hexstring = lex.ptr; - int charlen = 0; + // Non-decimal integer literals (SQL:2023 T661) + // Underscores in numeric literal support (SQL:2023 T662) + // See README.decimal_and_non_decimal_literals - // Time to scan the string. Make sure the characters are legal, - // and find out how long the hex digit string is. + if (c == '0' && lex.ptr + 1 < lex.end) + { + auto base = 0; + SSHORT currExpcChar = 0; - while (lex.ptr < lex.end) + if (*lex.ptr == 'x' || *lex.ptr == 'X') { - c = *lex.ptr; - - if (!(classes(c) & CHR_HEX)) // End of digit string - break; - - ++charlen; // Okay, just count 'em - ++lex.ptr; // and advance... - - if (charlen > 32) // Too many digits... - { - hexerror = true; - break; - } + base = 4; // 2^4 0b1111 + currExpcChar = CHR_HEX; } - - // we have a valid hex token. Now give it back, either as - // an NUMBER32BIT or NUMBER64BIT. - if (!hexerror) + else if (*lex.ptr == 'o' || *lex.ptr == 'O') { - if (charlen > 16) - { - // we deal with int128 - fb_assert(charlen <= 32); // charlen is always <= 32, see 10-15 lines upper - - Firebird::string sbuff(hexstring, charlen); - sbuff.insert(0, "0X"); + base = 3; // 2^3 0b111 + currExpcChar = CHR_OCT; + } + else if (*lex.ptr == 'b' || *lex.ptr == 'B') + { + base = 1; // 2^1 0b1 + currExpcChar = CHR_BIN; + } - yylval.lim64ptr = newLim64String(sbuff, 0); + if (base) + { + const auto decimalConversion = 10; + auto isLastIntroducer = false; + Int128 value128; + value128.set(0.0); - return TOK_NUM128; - } + const CInt128 MAX_VALUE(MAX_Int128 >> base); - // if charlen > 8 (something like FFFF FFFF 0, w/o the spaces) - // then we have to return a NUMBER64BIT. We'll make a string - // node here, and let make.cpp worry about converting the - // string to a number and building the node later. - else if (charlen > 8) + // Skip the 'X' or 'O' or 'B' and point to the first digit + for (++lex.ptr; lex.ptr < lex.end; lex.ptr++) { - char cbuff[32]; - fb_assert(charlen <= 16); // charlen is always <= 16, see 10-15 lines upper - cbuff[0] = 'X'; - fb_utils::copy_terminate(&cbuff[1], hexstring, charlen + 1); - - char* p = &cbuff[1]; - UCHAR byte = 0; - bool nibble = strlen(p) & 1; - - yylval.scaledNumber.number = 0; - yylval.scaledNumber.scale = 0; - yylval.scaledNumber.hex = true; + c = *lex.ptr; - while (*p) + if ((classes(c) & CHR_INTRODUCER)) { - if ((*p >= 'a') && (*p <= 'f')) - *p = UPPER(*p); - - // Now convert the character to a nibble - SSHORT c; - - if (*p >= 'A') - c = (*p - 'A') + 10; - else - c = (*p - '0'); - - if (nibble) - { - byte = (byte << 4) + (UCHAR) c; - nibble = false; - yylval.scaledNumber.number = (yylval.scaledNumber.number << 8) + byte; - } - else + if (isLastIntroducer) { - byte = c; - nibble = true; + exceptionNumericLiterals( + Firebird::string(lex.last_token, lex.ptr - lex.last_token + 1)); } - ++p; + isLastIntroducer = true; } + else if ((classes(c) & currExpcChar)) + { + // check overflow + if (value128 > MAX_VALUE) + exceptionNumericLiterals(Firebird::string("Overflow of the number")); - // The return value can be a negative number. - return TOK_NUMBER64BIT; - } - else - { - // we have an integer value. we'll return NUMBER32BIT. - // but we have to make a number value to be compatible - // with existing code. - - // See if the string length is odd. If so, - // we'll assume a leading zero. Then figure out the length - // of the actual resulting hex string. Allocate a second - // temporary buffer for it. - - bool nibble = (charlen & 1); // IS_ODD(temp.length) - - // Re-scan over the hex string we got earlier, converting - // adjacent bytes into nibble values. Every other nibble, - // write the saved byte to the temp space. At the end of - // this, the temp.space area will contain the binary - // representation of the hex constant. + auto ch = UPPER(c); + if (ch >= 'A') + ch = (ch - 'A') + decimalConversion; + else + ch = (ch - '0'); - UCHAR byte = 0; - SINT64 value = 0; + value128 *= 1 << base; + value128 += ch; - for (int i = 0; i < charlen; i++) + isLastIntroducer = false; + } + else if ((classes(c) & CHR_IDENT) && !(classes(c) & CHR_BRACE)) { - c = UPPER(hexstring[i]); - - // Now convert the character to a nibble + exceptionNumericLiterals( + Firebird::string(lex.last_token, lex.ptr - lex.last_token + 1)); + } + else // We have reached the separator + break; + } - if (c >= 'A') - c = (c - 'A') + 10; - else - c = (c - '0'); + // Error 0x + const auto minimalLength = 3U; + if ((lex.ptr - lex.last_token) < minimalLength) + { + exceptionNumericLiterals( + Firebird::string(lex.last_token, lex.ptr - lex.last_token)); + } - if (nibble) - { - byte = (byte << 4) + (UCHAR) c; - nibble = false; - value = (value << 8) + byte; - } - else - { - byte = c; - nibble = true; - } - } + // Error of having '_' at the end + if (isLastIntroducer) + { + exceptionNumericLiterals( + Firebird::string(lex.last_token, lex.ptr - lex.last_token)); + } - yylval.int32Val = (SLONG) value; - return TOK_NUMBER32BIT; - } // integer value - } // if (!hexerror)... + Int128 tmp; + tmp.set(MAX_SINT64, 0); + if (value128 > tmp) + { + Firebird::string strValue; + value128.toString(0, strValue); + yylval.lim64ptr = newLim64String(strValue, 0); - // If we got here, there was a parsing error. Set the - // position back to where it was before we messed with - // it. Then fall through to the next thing we might parse. + // Special case - exactly (MAX_SINT64 + 1), see decimal literals below + tmp += 1U; + return (value128 == tmp) ? TOK_LIMIT64_INT : TOK_NUM128; + } - c = *lex.last_token; - lex.ptr = lex.last_token + 1; - } // headecimal numeric constants + tmp.set(MAX_SLONG); + if (value128 > tmp) + { + yylval.scaledNumber.number = value128.toInt64(0); + yylval.scaledNumber.scale = 0; + yylval.scaledNumber.hex = false; + return TOK_NUMBER64BIT; + } + else + { + yylval.int32Val = (SLONG)value128.toInteger(0); + return TOK_NUMBER32BIT; + } + } + } if ((tok_class & CHR_DIGIT) || ((c == '.') && (lex.ptr < lex.end) && (classes(*lex.ptr) & CHR_DIGIT))) { - // The following variables are used to recognize kinds of numbers. - - bool have_error = false; // syntax error or value too large - bool have_digit = false; // we've seen a digit - bool have_decimal = false; // we've seen a '.' - bool have_exp = false; // digit ... [eE] - bool have_exp_sign = false; // digit ... [eE] {+-] - bool have_exp_digit = false; // digit ... [eE] ... digit - bool have_overflow = false; // value of digits > MAX_SINT64 - bool positive_overflow = false; // number is exactly (MAX_SINT64 + 1) - bool have_128_over = false; // value of digits > MAX_INT128 - FB_UINT64 number = 0; - Int128 num128; - int expVal = 0; - constexpr FB_UINT64 limit_by_10 = MAX_SINT64 / 10; + Firebird::string pureString; + auto isLastIntroducer = false; int scale = 0; - int expSign = 1; + auto exponentValue = 0; + auto isOverExponent64b = false; + auto isOverMantisa64b = false; + auto isOverMantisa128b = false; + auto signExponent = 0; + auto hasExponentDigit = false; + + Int128 mantisaValue; + mantisaValue.set(0.0); + + const auto decimalConversion = 10; + static const CInt128 MAX_MANTISA_128(MAX_Int128 / decimalConversion); + static const CInt128 MAX_MANTISA_64(MAX_SINT64 / decimalConversion); + + enum + { + state_mantisa = 0, + state_precision, + state_exponent, + } state = state_mantisa; for (--lex.ptr; lex.ptr < lex.end; lex.ptr++) { c = *lex.ptr; - if (have_exp_digit && (! (classes(c) & CHR_DIGIT))) - // First non-digit after exponent and digit terminates the token. - break; - if (have_exp_sign && (! (classes(c) & CHR_DIGIT))) + if (classes(c) & CHR_INTRODUCER) { - // only digits can be accepted after "1E-" - have_error = true; - break; - } - - if (have_exp) - { - // We've seen e or E, but nothing beyond that. - if ( ('-' == c) || ('+' == c) ) + if (isLastIntroducer) { - have_exp_sign = true; - if ('-' == c) - expSign = -1; + exceptionNumericLiterals( + Firebird::string(lex.last_token, lex.ptr - lex.last_token + 1)); } - else if ( classes(c) & CHR_DIGIT ) - { - // We have a digit: we haven't seen a sign yet, but it's too late now. - have_exp_digit = have_exp_sign = true; - if (!have_overflow) - { - expVal = expVal * 10 + (c - '0'); - if (expVal > DBL_MAX_10_EXP) - have_overflow = true; - } - } - else - { - // end of the token - have_error = true; - break; - } - } - else if ('.' == c) - { - if (!have_decimal) - have_decimal = true; - else + + const char lastSymbol = *(lex.ptr - 1); + if ((lastSymbol == '.') || (UPPER(lastSymbol) == 'E') || (lastSymbol == '-') || + (lastSymbol == '+')) { - have_error = true; - break; + exceptionNumericLiterals( + Firebird::string(lex.last_token, lex.ptr - lex.last_token + 1)); } + + isLastIntroducer = true; + continue; } - else if (classes(c) & CHR_DIGIT) + if (classes(c) & CHR_DIGIT) { - // Before computing the next value, make sure there will be no overflow. + pureString += static_cast(c); + auto ch = (c - '0'); - if (!have_overflow) + if (state == state_exponent) { - have_digit = true; + hasExponentDigit = true; + + if (signExponent == 0) + signExponent = 1; + + exponentValue *= decimalConversion; - if (number >= limit_by_10) + if (signExponent == 1) + exponentValue += ch; + else + exponentValue -= ch; + + if (!isOverExponent64b) { - // possibility of an overflow - if ((number > limit_by_10) || (c >= '8')) - { - have_overflow = true; - fb_assert(number <= MAX_SINT64); - num128.set((SINT64)number, 0); - if ((number == limit_by_10) && (c == '8')) - positive_overflow = true; - } + if (exponentValue > DBL_MAX_10_EXP || exponentValue < -DBL_MAX_10_EXP) + isOverExponent64b = true; } + else if (exponentValue > DECQUAD_Emax || exponentValue < DECQUAD_Emin) + exceptionNumericLiterals(Firebird::string("Overflow of the exponent")); } else { - positive_overflow = false; - if (!have_128_over) + if (!isOverMantisa64b) { - static const CInt128 MAX_BY10(MAX_Int128 / 10); - if ((num128 >= MAX_BY10) && ((num128 > MAX_BY10) || (c >= '8'))) - have_128_over = true; + if (mantisaValue >= MAX_MANTISA_64 && + ((mantisaValue > MAX_MANTISA_64) || (c >= '8'))) + { + isOverMantisa64b = true; + } + } + else if (!isOverMantisa128b) + { + if ((mantisaValue >= MAX_MANTISA_128) && + ((mantisaValue > MAX_MANTISA_128) || (c >= '8'))) + { + isOverMantisa128b = true; + isOverExponent64b = true; + } } - } - if (!have_overflow) - number = number * 10 + (c - '0'); - else if (!have_128_over) - { - num128 *= 10; - num128 += (c - '0'); - } + if (!isOverMantisa64b || !isOverMantisa128b) + { + mantisaValue *= decimalConversion; + mantisaValue += ch; + } - if (have_decimal) - --scale; + if (state == state_precision) + --scale; + } } - else if ( (('E' == c) || ('e' == c)) && have_digit ) - have_exp = true; - else - // Unexpected character: this is the end of the number. - break; - } + else if (c == '.') + { + if (isLastIntroducer) + { + exceptionNumericLiterals( + Firebird::string(lex.last_token, lex.ptr - lex.last_token + 1)); + } - // We're done scanning the characters: now return the right kind - // of number token, if any fits the bill. + pureString += static_cast(c); - if (!have_error) - { - fb_assert(have_digit); + if (state == state_mantisa) + state = state_precision; + else + { + exceptionNumericLiterals( + Firebird::string(lex.last_token, lex.ptr - lex.last_token + 1)); + } + } + else if (UPPER(c) == 'E') + { + if (isLastIntroducer) + { + exceptionNumericLiterals( + Firebird::string(lex.last_token, lex.ptr - lex.last_token + 1)); + } - if (positive_overflow) - have_overflow = false; + pureString += static_cast(c); - if (scale < MIN_SCHAR || scale > MAX_SCHAR) + if (state != state_exponent) + state = state_exponent; + else + { + exceptionNumericLiterals( + Firebird::string(lex.last_token, lex.ptr - lex.last_token + 1)); + } + } + else if ((classes(c) & CHR_IDENT) && !(classes(c) & CHR_BRACE)) { - have_overflow = true; - positive_overflow = false; - have_128_over = true; + exceptionNumericLiterals( + Firebird::string(lex.last_token, lex.ptr - lex.last_token + 1)); } - - // check for a more complex overflow case - if ((!have_overflow) && (expSign > 0) && (expVal > -scale)) + else // We have reached the separator { - expVal += scale; - double maxNum = DBL_MAX / pow(10.0, expVal); - if (double(number) > maxNum) + if ((c == '-') || (c == '+')) { - have_overflow = true; - positive_overflow = false; - have_128_over = true; + if (state == state_exponent && signExponent == 0) + { + pureString += static_cast(c); + + if (c == '-') + signExponent = -1; + else + signExponent = 1; + continue; + } } + break; } - // Special case - on the boarder of positive number - if (positive_overflow) - { - yylval.lim64ptr = newLim64String( - Firebird::string(lex.last_token, lex.ptr - lex.last_token), scale); - lex.last_token_bk = lex.last_token; - lex.line_start_bk = lex.line_start; - lex.lines_bk = lex.lines; + isLastIntroducer = false; + } + // We have reached the separator or the end of the line - return scale ? TOK_LIMIT64_NUMBER : TOK_LIMIT64_INT; - } + if (isLastIntroducer) + exceptionNumericLiterals(Firebird::string(lex.last_token, lex.ptr - lex.last_token)); - // Should we use floating point type? - if (have_exp_digit || have_128_over) - { - yylval.stringPtr = newString( - Firebird::string(lex.last_token, lex.ptr - lex.last_token)); - lex.last_token_bk = lex.last_token; - lex.line_start_bk = lex.line_start; - lex.lines_bk = lex.lines; - - return have_overflow ? TOK_DECIMAL_NUMBER : TOK_FLOAT_NUMBER; - } + // The exponent must contain at least one digit: 1e, 1e+, 1e- are invalid + if (state == state_exponent && !hasExponentDigit) + exceptionNumericLiterals(Firebird::string(lex.last_token, lex.ptr - lex.last_token)); - // May be 128-bit integer? - if (have_overflow) - { - yylval.lim64ptr = newLim64String( - Firebird::string(lex.last_token, lex.ptr - lex.last_token), scale); - lex.last_token_bk = lex.last_token; - lex.line_start_bk = lex.line_start; - lex.lines_bk = lex.lines; + // Too many digits after the period for an exact numeric - use DECFLOAT + if (scale < MIN_SCHAR) + { + isOverMantisa128b = true; + isOverExponent64b = true; + } - return TOK_NUM128; - } + lex.last_token_bk = lex.last_token; + lex.line_start_bk = lex.line_start; + lex.lines_bk = lex.lines; - if (!have_exp) + // Any with an exponent "E" or a very big number + if (state == state_exponent || isOverMantisa128b) + { + // Check for a more complex overflow case + if ((!isOverExponent64b) && (signExponent == 1) && (exponentValue > (-scale))) { - // We should return some kind (scaled-) integer type - // except perhaps in dialect 1. + const auto degreeBase = 10.0; + exponentValue += scale; + double check_num = DBL_MAX / pow(degreeBase, exponentValue); + if (mantisaValue.toDouble() > check_num) + isOverExponent64b = true; + } - if (!have_decimal && (number <= MAX_SLONG)) - { - yylval.int32Val = (SLONG) number; - return TOK_NUMBER32BIT; - } - else - { - /* We have either a decimal point with no exponent - or a string of digits whose value exceeds MAX_SLONG: - the returned type depends on the client dialect, - so warn of the difference if the client dialect is - SQL_DIALECT_V6_TRANSITION. - */ - - if (SQL_DIALECT_V6_TRANSITION == client_dialect) - { - /* Issue a warning about the ambiguity of the numeric - * numeric literal. There are multiple calls because - * the message text exceeds the 119-character limit - * of our message database. - */ - ERRD_post_warning(Arg::Warning(isc_dsql_warning_number_ambiguous) << - Arg::Str(Firebird::string(lex.last_token, lex.ptr - lex.last_token))); - ERRD_post_warning(Arg::Warning(isc_dsql_warning_number_ambiguous1)); - } + yylval.stringPtr = newString(pureString); + // Mantissa wider than 64 bits does not fit into double without loss + return (isOverExponent64b || isOverMantisa64b) ? TOK_DECIMAL_NUMBER : TOK_FLOAT_NUMBER; + } - lex.last_token_bk = lex.last_token; - lex.line_start_bk = lex.line_start; - lex.lines_bk = lex.lines; + // 128-bit + if (isOverMantisa64b) + { + yylval.lim64ptr = newLim64String(pureString, scale); - if (client_dialect < SQL_DIALECT_V6_TRANSITION) - { - yylval.stringPtr = newString( - Firebird::string(lex.last_token, lex.ptr - lex.last_token)); - return TOK_FLOAT_NUMBER; - } + // Special case - on the border of positive number: exactly (MAX_SINT64 + 1) + Int128 limit; + limit.set(MAX_SINT64, 0); + limit += 1U; + if (mantisaValue == limit) + return scale ? TOK_LIMIT64_NUMBER : TOK_LIMIT64_INT; - yylval.scaledNumber.number = number; - yylval.scaledNumber.scale = scale; - yylval.scaledNumber.hex = false; + return TOK_NUM128; + } - if (have_decimal) - return TOK_SCALEDINT; + Int128 tmp; + if (state != state_precision) + { + tmp.set(MAX_SLONG, 0); + if (tmp >= mantisaValue) + { + // A natural 32 bit number + yylval.int32Val = (SLONG)mantisaValue.toInteger(0); + return TOK_NUMBER32BIT; + } + } + /* We have either a decimal point with no exponent + or a string of digits whose value exceeds MAX_SLONG: + the returned type depends on the client dialect, + so warn of the difference if the client dialect is + SQL_DIALECT_V6_TRANSITION. + */ + if (SQL_DIALECT_V6_TRANSITION == client_dialect) + { + /* Issue a warning about the ambiguity of the numeric + * numeric literal. There are multiple calls because + * the message text exceeds the 119-character limit + * of our message database. + */ + ERRD_post_warning(Arg::Warning(isc_dsql_warning_number_ambiguous) << Arg::Str( + Firebird::string(lex.last_token, lex.ptr - lex.last_token))); + ERRD_post_warning(Arg::Warning(isc_dsql_warning_number_ambiguous1)); + } - return TOK_NUMBER64BIT; - } - } // else if (!have_exp) - } // if (!have_error) + if (client_dialect < SQL_DIALECT_V6_TRANSITION) + { + yylval.stringPtr = newString(pureString); + return TOK_FLOAT_NUMBER; + } - // we got some kind of error or overflow, so don't recognize this - // as a number: just pass it through to the next part of the lexer. + yylval.scaledNumber.number = mantisaValue.toInt64(0); + yylval.scaledNumber.scale = scale; + yylval.scaledNumber.hex = false; + return state == state_precision ? TOK_SCALEDINT : TOK_NUMBER64BIT; } // Restore the status quo ante, before we started our unsuccessful diff --git a/src/dsql/Parser.h b/src/dsql/Parser.h index 265a125cc9a..598cb8888bc 100644 --- a/src/dsql/Parser.h +++ b/src/dsql/Parser.h @@ -359,6 +359,14 @@ class Parser : public Firebird::PermanentStorage } void checkTimeDialect(); + void exceptionNumericLiterals(const Firebird::string& text) + { + using namespace Firebird; + ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << Arg::Gds(isc_dsql_token_unk_err) + << Arg::Num(yyposn.firstLine) << Arg::Num(yyposn.firstColumn) + << Arg::Gds(isc_random) << Arg::Str(text)); + } + // start - defined in btyacc_fb.ske private: static void yySCopy(YYSTYPE* to, YYSTYPE* from, int size); diff --git a/src/dsql/chars.h b/src/dsql/chars.h index 8c5f899dae6..a6ddf7cdb83 100644 --- a/src/dsql/chars.h +++ b/src/dsql/chars.h @@ -21,17 +21,21 @@ * Contributor(s): ______________________________________. */ -inline constexpr SCHAR CHR_LETTER = 1; -inline constexpr SCHAR CHR_DIGIT = 2; -inline constexpr SCHAR CHR_IDENT = 4; -inline constexpr SCHAR CHR_QUOTE = 8; -inline constexpr SCHAR CHR_WHITE = 16; -inline constexpr SCHAR CHR_HEX = 32; -inline constexpr SCHAR CHR_INTRODUCER = 64; +inline constexpr SSHORT CHR_LETTER = (1 << 0); +inline constexpr SSHORT CHR_DIGIT = (1 << 1); +inline constexpr SSHORT CHR_IDENT = (1 << 2); +inline constexpr SSHORT CHR_QUOTE = (1 << 3); +inline constexpr SSHORT CHR_WHITE = (1 << 4); +inline constexpr SSHORT CHR_HEX = (1 << 5); +inline constexpr SSHORT CHR_INTRODUCER = (1 << 6); +inline constexpr SSHORT CHR_BIN = (1 << 7); +inline constexpr SSHORT CHR_OCT = (1 << 8); +inline constexpr SSHORT CHR_BRACE = (1 << 9); + // Use the functions at the end of this file; do not reference the array directly. -static inline constexpr SCHAR classes_array[] = { +static inline constexpr SSHORT classes_array[] = { /* 000 */ 0, /* 001 */ 0, /* 002 */ 0, @@ -80,14 +84,14 @@ static inline constexpr SCHAR classes_array[] = { /* 045 - */ 0, /* 046 . */ 0, /* 047 / */ 0, -/* 048 0 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX, -/* 049 1 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX, -/* 050 2 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX, -/* 051 3 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX, -/* 052 4 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX, -/* 053 5 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX, -/* 054 6 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX, -/* 055 7 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX, +/* 048 0 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX | CHR_OCT | CHR_BIN, +/* 049 1 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX | CHR_OCT | CHR_BIN, +/* 050 2 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX | CHR_OCT, +/* 051 3 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX | CHR_OCT, +/* 052 4 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX | CHR_OCT, +/* 053 5 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX | CHR_OCT, +/* 054 6 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX | CHR_OCT, +/* 055 7 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX | CHR_OCT, /* 056 8 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX, /* 057 9 */ 0 | CHR_DIGIT | CHR_IDENT | CHR_HEX, /* 058 : */ 0, @@ -155,9 +159,9 @@ static inline constexpr SCHAR classes_array[] = { /* 120 x */ 0 | CHR_LETTER | CHR_IDENT, /* 121 y */ 0 | CHR_LETTER | CHR_IDENT, /* 122 z */ 0 | CHR_LETTER | CHR_IDENT, -/* 123 { */ 0 | CHR_LETTER | CHR_IDENT, +/* 123 { */ 0 | CHR_LETTER | CHR_IDENT | CHR_BRACE, /* 124 | */ 0, -/* 125 } */ 0 | CHR_LETTER | CHR_IDENT, +/* 125 } */ 0 | CHR_LETTER | CHR_IDENT | CHR_BRACE, /* 126 ~ */ 0, /* 127 */ 0, /* 128 */ 0, @@ -290,12 +294,12 @@ static inline constexpr SCHAR classes_array[] = { /* 255 */ 0 }; -inline SCHAR classes(int idx) noexcept +inline SSHORT classes(int idx) noexcept { return classes_array[(UCHAR) idx]; } -inline SCHAR classes(UCHAR idx) noexcept +inline SSHORT classes(UCHAR idx) noexcept { return classes_array[idx]; } From 3d07cc6daf959c3eaecc4dd28f70eafd6cde7427 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 29 Sep 2026 23:26:14 +0000 Subject: [PATCH 26/30] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index b7e37847900..afd461dfe3f 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:2190 + FORMAL BUILD NUMBER:2191 */ -#define PRODUCT_VER_STRING "6.0.0.2190" -#define FILE_VER_STRING "WI-T6.0.0.2190" -#define LICENSE_VER_STRING "WI-T6.0.0.2190" -#define FILE_VER_NUMBER 6, 0, 0, 2190 +#define PRODUCT_VER_STRING "6.0.0.2191" +#define FILE_VER_STRING "WI-T6.0.0.2191" +#define LICENSE_VER_STRING "WI-T6.0.0.2191" +#define FILE_VER_NUMBER 6, 0, 0, 2191 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "2190" +#define FB_BUILD_NO "2191" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index c0a2f28e011..29b212ee51d 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=2190 +BuildNum=2191 NowAt=`pwd` cd `dirname $0` From 5c474d22022924c98e29c7bdd7c29b923864d31b Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Fri, 25 Sep 2026 16:41:04 +0300 Subject: [PATCH 27/30] WIP --- src/dsql/BoolNodes.h | 6 + src/jrd/optimizer/InnerJoin.cpp | 6 +- src/jrd/optimizer/Optimizer.cpp | 26 +-- src/jrd/optimizer/OuterJoin.cpp | 8 +- src/jrd/recsrc/HashJoin.cpp | 44 ++--- src/jrd/recsrc/MergeJoin.cpp | 309 ++++++++++++++++-------------- src/jrd/recsrc/NestedLoopJoin.cpp | 8 +- src/jrd/recsrc/RecordSource.h | 63 ++++-- 8 files changed, 264 insertions(+), 206 deletions(-) diff --git a/src/dsql/BoolNodes.h b/src/dsql/BoolNodes.h index bdf0301f654..35c76088c66 100644 --- a/src/dsql/BoolNodes.h +++ b/src/dsql/BoolNodes.h @@ -39,6 +39,12 @@ class BinaryBoolNode final : public TypedNode keys; keys.add(FB_NEW_POOL(getPool()) NestValueArray(getPool())); @@ -573,6 +574,7 @@ River* InnerJoin::formRiver() keys[1]->add(node2); equiMatches.add(match); + BinaryBoolNode::compose(getPool(), boolean, match); } // Ensure the smallest stream is the one to be hashed, @@ -588,7 +590,7 @@ River* InnerJoin::formRiver() // Create a hash join rsb = FB_NEW_POOL(getPool()) - HashJoin(tdbb, csb, JoinType::INNER, 2, hashJoinRsbs, keys.begin(), stream.selectivity); + HashJoin(tdbb, csb, JoinType::INNER, boolean, 2, hashJoinRsbs, keys.begin(), stream.selectivity); // Clear priorly processed rsb's, as they're already incorporated into a hash join rsbs.clear(); @@ -607,7 +609,7 @@ River* InnerJoin::formRiver() rsb = (rsbs.getCount() == 1) ? rsbs[0] : FB_NEW_POOL(getPool()) NestedLoopJoin(csb, JoinType::INNER, rsbs.getCount(), rsbs.begin()); - // Ensure matching booleans are rechecked early + // Mark matching booleans as used as they're checked inside the hash join if (equiMatches.hasData()) { auto iter = optimizer->getConjuncts(); diff --git a/src/jrd/optimizer/Optimizer.cpp b/src/jrd/optimizer/Optimizer.cpp index 450d03f1864..af89857223c 100644 --- a/src/jrd/optimizer/Optimizer.cpp +++ b/src/jrd/optimizer/Optimizer.cpp @@ -265,12 +265,6 @@ namespace } }; - inline void compose(MemoryPool& pool, BoolExprNode** node1, BoolExprNode* node2) - { - if (node2) - *node1 = (*node1) ? FB_NEW_POOL(pool) BinaryBoolNode(pool, blr_and, *node1, node2) : node2; - } - void classMask(unsigned count, ValueExprNode** eq_class, ULONG* mask) { // Given an sort/merge join equivalence class (vector of node pointers @@ -883,7 +877,7 @@ RecordSource* Optimizer::compile(BoolExprNodeStack* parentStack) iter->deterministic(tdbb) && iter->computable(csb, INVALID_STREAM, false)) { - compose(getPool(), &invariantBoolean, iter); + BinaryBoolNode::compose(getPool(), invariantBoolean, iter); iter |= CONJUNCT_USED; } } @@ -1063,7 +1057,7 @@ RecordSource* Optimizer::compile(BoolExprNodeStack* parentStack) if (rivers.isEmpty() && dependentRivers.isEmpty()) { // This case may look weird, but it's possible for recursive unions - rsb = FB_NEW_POOL(csb->csb_pool) NestedLoopJoin(csb, JoinType::INNER, 0, nullptr); + rsb = FB_NEW_POOL(csb->csb_pool) DummyJoin(csb); } else { @@ -2791,10 +2785,14 @@ bool Optimizer::generateEquiJoin(RiverList& rivers, JoinType joinType) if (orgCount < 2) return false; + fb_assert(joinType == JoinType::INNER || orgCount == 2); + HalfStaticArray scratch; scratch.grow(baseConjuncts * orgCount); ValueExprNode** classes = scratch.begin(); + BoolExprNode* boolean = nullptr; + // Compute equivalence classes among streams. This involves finding groups // of streams joined by field equalities. @@ -2848,7 +2846,9 @@ bool Optimizer::generateEquiJoin(RiverList& rivers, JoinType joinType) if (eq_class == last_class) last_class += orgCount; + // Mark matching booleans as used as they're checked inside the hash/merge join iter |= Optimizer::CONJUNCT_JOINED; + BinaryBoolNode::compose(getPool(), boolean, *iter); } } } @@ -2975,7 +2975,7 @@ bool Optimizer::generateEquiJoin(RiverList& rivers, JoinType joinType) } finalRsb = FB_NEW_POOL(getPool()) - MergeJoin(csb, rsbs.getCount(), (SortedStream**) rsbs.begin(), keys.begin()); + MergeJoin(csb, boolean, rsbs.getCount(), (SortedStream**) rsbs.begin(), keys.begin()); } else { @@ -2997,7 +2997,7 @@ bool Optimizer::generateEquiJoin(RiverList& rivers, JoinType joinType) rsbs.add(river->getRecordSource()); finalRsb = FB_NEW_POOL(getPool()) - HashJoin(tdbb, csb, joinType, rsbs.getCount(), rsbs.begin(), keys.begin()); + HashJoin(tdbb, csb, joinType, boolean, rsbs.getCount(), rsbs.begin(), keys.begin()); } // Pick up any boolean that may apply @@ -3191,7 +3191,7 @@ RecordSource* Optimizer::generateRetrieval(StreamType stream, if ((inversion && iter->containsStream(stream)) || (!inversion && iter->computable(csb, stream, true))) { - compose(getPool(), &boolean, iter); + BinaryBoolNode::compose(getPool(), boolean, iter); iter |= CONJUNCT_USED; if (!(iter & CONJUNCT_MATCHED)) @@ -3299,7 +3299,7 @@ RecordSource* Optimizer::applyResidualBoolean(RecordSource* rsb) { if (!(iter & CONJUNCT_USED)) { - compose(getPool(), &boolean, iter); + BinaryBoolNode::compose(getPool(), boolean, iter); iter |= CONJUNCT_USED; if (!(iter & (CONJUNCT_MATCHED | CONJUNCT_JOINED))) @@ -3323,7 +3323,7 @@ BoolExprNode* Optimizer::composeBoolean(ConjunctIterator& iter, BooleanList& fil !(iter->nodFlags & ExprNode::FLAG_RESIDUAL) && iter->computable(csb, INVALID_STREAM, false)) { - compose(getPool(), &boolean, iter); + BinaryBoolNode::compose(getPool(), boolean, iter); iter |= CONJUNCT_USED; if (!(iter & (CONJUNCT_MATCHED | CONJUNCT_JOINED))) diff --git a/src/jrd/optimizer/OuterJoin.cpp b/src/jrd/optimizer/OuterJoin.cpp index 0011c3959b1..b8d6376604e 100644 --- a/src/jrd/optimizer/OuterJoin.cpp +++ b/src/jrd/optimizer/OuterJoin.cpp @@ -150,7 +150,7 @@ RecordSource* OuterJoin::generate() RecordSource* OuterJoin::process() { - BoolExprNode* boolean = nullptr; + BoolExprNode* outerBoolean = nullptr; auto& outer = joinStreams[0]; auto& inner = joinStreams[1]; @@ -164,7 +164,7 @@ RecordSource* OuterJoin::process() if (outer.number != INVALID_STREAM) { outerRsb = optimizer->generateRetrieval(outer.number, - optimizer->isFullJoin() ? nullptr : sortPtr, true, false, &boolean); + optimizer->isFullJoin() ? nullptr : sortPtr, true, false, &outerBoolean); } else { @@ -178,7 +178,7 @@ RecordSource* OuterJoin::process() } // Collect booleans computable for the outer sub-stream, it must be active now - boolean = optimizer->composeBoolean(); + outerBoolean = optimizer->composeBoolean(); } fb_assert(outerRsb); @@ -214,5 +214,5 @@ RecordSource* OuterJoin::process() // Allocate and return the join record source - return FB_NEW_POOL(getPool()) NestedLoopJoin(csb, outerRsb, innerRsb, boolean); + return FB_NEW_POOL(getPool()) NestedLoopJoin(csb, outerBoolean, outerRsb, innerRsb); }; diff --git a/src/jrd/recsrc/HashJoin.cpp b/src/jrd/recsrc/HashJoin.cpp index 8b416c15742..d7fceae8e92 100644 --- a/src/jrd/recsrc/HashJoin.cpp +++ b/src/jrd/recsrc/HashJoin.cpp @@ -249,10 +249,11 @@ class HashJoin::HashTable final : public PermanentStorage }; -HashJoin::HashJoin(thread_db* tdbb, CompilerScratch* csb, JoinType joinType, +HashJoin::HashJoin(thread_db* tdbb, CompilerScratch* csb, + JoinType joinType, NestConst joinBoolean, FB_SIZE_T count, RecordSource* const* args, NestValueArray* const* keys, double selectivity) - : Join(csb, count, joinType), + : Join(csb, count, joinType, joinBoolean), m_subs(csb->csb_pool, count - 1) { fb_assert(count >= 2); @@ -261,10 +262,10 @@ HashJoin::HashJoin(thread_db* tdbb, CompilerScratch* csb, JoinType joinType, } HashJoin::HashJoin(thread_db* tdbb, CompilerScratch* csb, - BoolExprNode* boolean, + NestConst joinBoolean, NestConst outerBoolean, RecordSource* const* args, NestValueArray* const* keys, double selectivity) - : Join(csb, 2, JoinType::OUTER, boolean), + : Join(csb, 2, JoinType::OUTER, joinBoolean, outerBoolean), m_subs(csb->csb_pool, 1) { init(tdbb, csb, 2, args, keys, selectivity); @@ -419,7 +420,7 @@ bool HashJoin::internalGetRecord(thread_db* tdbb) const if (!m_leader.source->getRecord(tdbb)) return false; - if (m_boolean && m_boolean->execute(tdbb, request) != TriState(true)) + if (!checkOuterBoolean(tdbb)) { // The boolean pertaining to the left sub-stream is false // so just join sub-stream to a null valued right sub-stream @@ -639,35 +640,30 @@ ULONG HashJoin::computeHash(thread_db* tdbb, bool HashJoin::fetchRecord(thread_db* tdbb, Impure* impure, FB_SIZE_T stream) const { + Request* const request = tdbb->getRequest(); HashTable* const hashTable = impure->irsb_hash_table; const BufferedStream* const arg = m_subs[stream].buffer; - ULONG position; - if (hashTable->iterate(stream, impure->irsb_leader_hash, position)) - { - arg->locate(tdbb, position); - - if (arg->getRecord(tdbb)) - return true; - } - - if (m_joinType == JoinType::SEMI || m_joinType == JoinType::ANTI) - return false; - while (true) { - if (stream == 0 || !fetchRecord(tdbb, impure, stream - 1)) - return false; - - hashTable->reset(stream, impure->irsb_leader_hash); - - if (hashTable->iterate(stream, impure->irsb_leader_hash, position)) + ULONG position; + while (hashTable->iterate(stream, impure->irsb_leader_hash, position)) { arg->locate(tdbb, position); - if (arg->getRecord(tdbb)) + if (arg->getRecord(tdbb) && checkJoinBoolean(tdbb)) return true; } + + if (m_joinType == JoinType::SEMI || m_joinType == JoinType::ANTI) + break; + + if (stream == 0 || !fetchRecord(tdbb, impure, stream - 1)) + break; + + hashTable->reset(stream, impure->irsb_leader_hash); } + + return false; } diff --git a/src/jrd/recsrc/MergeJoin.cpp b/src/jrd/recsrc/MergeJoin.cpp index 2259731ddad..e8da87c7380 100644 --- a/src/jrd/recsrc/MergeJoin.cpp +++ b/src/jrd/recsrc/MergeJoin.cpp @@ -36,9 +36,9 @@ static const char* const SCRATCH = "fb_merge_"; // Data access: merge join // ----------------------- -MergeJoin::MergeJoin(CompilerScratch* csb, FB_SIZE_T count, - SortedStream* const* args, const NestValueArray* const* keys) - : Join(csb, count, JoinType::INNER), +MergeJoin::MergeJoin(CompilerScratch* csb, NestConst joinBoolean, + FB_SIZE_T count, SortedStream* const* args, const NestValueArray* const* keys) + : Join(csb, count, JoinType::INNER, joinBoolean), m_keys(csb->csb_pool, count) { const size_t size = sizeof(struct Impure) + count * sizeof(Impure::irsb_mrg_repeat); @@ -132,13 +132,173 @@ bool MergeJoin::internalGetRecord(thread_db* tdbb) const if (!(impure->irsb_flags & irsb_open)) return false; - // If there is a record group already formed, fetch the next combination + while (true) + { + // If there is a record group already formed, fetch the next combination + + while (fetchRecord(tdbb, m_args.getCount() - 1)) + { + if (checkJoinBoolean(tdbb)) + return true; + } + + // Once consumed the whole record group, form the new one and return first record from there + + if (!formGroup(tdbb)) + break; + + if (checkJoinBoolean(tdbb)) + return true; + } + + return false; +} + +void MergeJoin::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const +{ + level++; + plan += "MERGE ("; + Join::getLegacyPlan(tdbb, plan, level); + plan += ")"; +} + +void MergeJoin::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const +{ + planEntry.className = "MergeJoin"; + + planEntry.lines.add().text = "Merge Join " + printType(); + + string extras; + extras.printf(" (keys: %" ULONGFORMAT", total key length: %" ULONGFORMAT")", + m_keys[0]->getCount(), m_args[0]->getKeyLength()); + + planEntry.lines.back().text += extras; + + printOptInfo(planEntry.lines); + + Join::internalGetPlan(tdbb, planEntry, level, recurse); +} + +int MergeJoin::compare(thread_db* tdbb, const NestValueArray* node1, + const NestValueArray* node2) const +{ + const auto request = tdbb->getRequest(); + + const NestConst* ptr1 = node1->begin(); + const NestConst* ptr2 = node2->begin(); + + for (const NestConst* const end = node1->end(); ptr1 != end; ++ptr1, ++ptr2) + { + const auto desc1 = EVL_expr(tdbb, request, *ptr1); + const auto desc2 = EVL_expr(tdbb, request, *ptr2); + + if (!desc1 && desc2) + return -1; + + if (desc1 && !desc2) + return 1; + + if (desc1 && desc2) + { + if (const int result = MOV_compare(tdbb, desc1, desc2)) + return result; + } + } + + return 0; +} + +UCHAR* MergeJoin::getData(thread_db* /*tdbb*/, MergeFile* mfb, SLONG record) const +{ + fb_assert(record >= 0 && record < (SLONG) mfb->mfb_equal_records); + + const ULONG merge_block = record / mfb->mfb_blocking_factor; + if (merge_block != mfb->mfb_current_block) + { + Sort::readBlock(mfb->mfb_space, mfb->mfb_block_size * merge_block, + mfb->mfb_block_data, mfb->mfb_block_size); + mfb->mfb_current_block = merge_block; + } + + const ULONG merge_offset = (record % mfb->mfb_blocking_factor) * mfb->mfb_record_size; + return mfb->mfb_block_data + merge_offset; +} + +SLONG MergeJoin::getRecordByIndex(thread_db* tdbb, FB_SIZE_T index) const +{ + Request* const request = tdbb->getRequest(); + Impure* const impure = request->getImpure(m_impure); + + const SortedStream* const sort_rsb = m_args[index]; + Impure::irsb_mrg_repeat* const tail = &impure->irsb_mrg_rpt[index]; + + const UCHAR* sort_data = sort_rsb->getData(tdbb); + if (!sort_data) + return -1; + + MergeFile* const mfb = &tail->irsb_mrg_file; + const SLONG record = mfb->mfb_equal_records; + + const ULONG merge_block = record / mfb->mfb_blocking_factor; + if (merge_block != mfb->mfb_current_block) + { + if (!mfb->mfb_space) + { + MemoryPool& pool = *getDefaultMemoryPool(); + mfb->mfb_space = FB_NEW_POOL(pool) TempSpace(pool, SCRATCH, false); + } + + Sort::writeBlock(mfb->mfb_space, mfb->mfb_block_size * mfb->mfb_current_block, + mfb->mfb_block_data, mfb->mfb_block_size); + mfb->mfb_current_block = merge_block; + } + + const ULONG merge_offset = (record % mfb->mfb_blocking_factor) * mfb->mfb_record_size; + UCHAR* merge_data = mfb->mfb_block_data + merge_offset; + + memcpy(merge_data, sort_data, sort_rsb->getLength()); + ++mfb->mfb_equal_records; + + return record; +} + +bool MergeJoin::fetchRecord(thread_db* tdbb, FB_SIZE_T index) const +{ + Request* const request = tdbb->getRequest(); + Impure* const impure = request->getImpure(m_impure); + Impure::irsb_mrg_repeat* tail = &impure->irsb_mrg_rpt[index]; + + const SSHORT m = tail->irsb_mrg_order; + tail = &impure->irsb_mrg_rpt[m]; + const SortedStream* const sort_rsb = m_args[m]; + + SLONG record = tail->irsb_mrg_equal_current; + ++record; + + if (record > tail->irsb_mrg_equal_end) + { + if (index == 0 || !fetchRecord(tdbb, index - 1)) + return false; + + record = tail->irsb_mrg_equal; + } + + tail->irsb_mrg_equal_current = record; + + MergeFile* const mfb = &tail->irsb_mrg_file; + sort_rsb->mapData(tdbb, request, getData(tdbb, mfb, record)); - if (fetchRecord(tdbb, m_args.getCount() - 1)) - return true; + return true; +} +bool MergeJoin::formGroup(thread_db* tdbb) const +{ // Assuming we are done with the current value group, advance each // stream one record. If any comes up dry, we're done. + + Request* const request = tdbb->getRequest(); + Impure* const impure = request->getImpure(m_impure); + const NestConst* highest_ptr = m_args.begin(); FB_SIZE_T highest_index = 0; @@ -321,140 +481,3 @@ bool MergeJoin::internalGetRecord(thread_db* tdbb) const return true; } - -void MergeJoin::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const -{ - level++; - plan += "MERGE ("; - Join::getLegacyPlan(tdbb, plan, level); - plan += ")"; -} - -void MergeJoin::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const -{ - planEntry.className = "MergeJoin"; - - planEntry.lines.add().text = "Merge Join " + printType(); - - string extras; - extras.printf(" (keys: %" ULONGFORMAT", total key length: %" ULONGFORMAT")", - m_keys[0]->getCount(), m_args[0]->getKeyLength()); - - planEntry.lines.back().text += extras; - - printOptInfo(planEntry.lines); - - Join::internalGetPlan(tdbb, planEntry, level, recurse); -} - -int MergeJoin::compare(thread_db* tdbb, const NestValueArray* node1, - const NestValueArray* node2) const -{ - const auto request = tdbb->getRequest(); - - const NestConst* ptr1 = node1->begin(); - const NestConst* ptr2 = node2->begin(); - - for (const NestConst* const end = node1->end(); ptr1 != end; ++ptr1, ++ptr2) - { - const auto desc1 = EVL_expr(tdbb, request, *ptr1); - const auto desc2 = EVL_expr(tdbb, request, *ptr2); - - if (!desc1 && desc2) - return -1; - - if (desc1 && !desc2) - return 1; - - if (desc1 && desc2) - { - if (const int result = MOV_compare(tdbb, desc1, desc2)) - return result; - } - } - - return 0; -} - -UCHAR* MergeJoin::getData(thread_db* /*tdbb*/, MergeFile* mfb, SLONG record) const -{ - fb_assert(record >= 0 && record < (SLONG) mfb->mfb_equal_records); - - const ULONG merge_block = record / mfb->mfb_blocking_factor; - if (merge_block != mfb->mfb_current_block) - { - Sort::readBlock(mfb->mfb_space, mfb->mfb_block_size * merge_block, - mfb->mfb_block_data, mfb->mfb_block_size); - mfb->mfb_current_block = merge_block; - } - - const ULONG merge_offset = (record % mfb->mfb_blocking_factor) * mfb->mfb_record_size; - return mfb->mfb_block_data + merge_offset; -} - -SLONG MergeJoin::getRecordByIndex(thread_db* tdbb, FB_SIZE_T index) const -{ - Request* const request = tdbb->getRequest(); - Impure* const impure = request->getImpure(m_impure); - - const SortedStream* const sort_rsb = m_args[index]; - Impure::irsb_mrg_repeat* const tail = &impure->irsb_mrg_rpt[index]; - - const UCHAR* sort_data = sort_rsb->getData(tdbb); - if (!sort_data) - return -1; - - MergeFile* const mfb = &tail->irsb_mrg_file; - const SLONG record = mfb->mfb_equal_records; - - const ULONG merge_block = record / mfb->mfb_blocking_factor; - if (merge_block != mfb->mfb_current_block) - { - if (!mfb->mfb_space) - { - MemoryPool& pool = *getDefaultMemoryPool(); - mfb->mfb_space = FB_NEW_POOL(pool) TempSpace(pool, SCRATCH, false); - } - - Sort::writeBlock(mfb->mfb_space, mfb->mfb_block_size * mfb->mfb_current_block, - mfb->mfb_block_data, mfb->mfb_block_size); - mfb->mfb_current_block = merge_block; - } - - const ULONG merge_offset = (record % mfb->mfb_blocking_factor) * mfb->mfb_record_size; - UCHAR* merge_data = mfb->mfb_block_data + merge_offset; - - memcpy(merge_data, sort_data, sort_rsb->getLength()); - ++mfb->mfb_equal_records; - - return record; -} - -bool MergeJoin::fetchRecord(thread_db* tdbb, FB_SIZE_T index) const -{ - Request* const request = tdbb->getRequest(); - Impure* const impure = request->getImpure(m_impure); - Impure::irsb_mrg_repeat* tail = &impure->irsb_mrg_rpt[index]; - - const SSHORT m = tail->irsb_mrg_order; - tail = &impure->irsb_mrg_rpt[m]; - const SortedStream* const sort_rsb = m_args[m]; - - SLONG record = tail->irsb_mrg_equal_current; - ++record; - - if (record > tail->irsb_mrg_equal_end) - { - if (index == 0 || !fetchRecord(tdbb, index - 1)) - return false; - - record = tail->irsb_mrg_equal; - } - - tail->irsb_mrg_equal_current = record; - - MergeFile* const mfb = &tail->irsb_mrg_file; - sort_rsb->mapData(tdbb, request, getData(tdbb, mfb, record)); - - return true; -} diff --git a/src/jrd/recsrc/NestedLoopJoin.cpp b/src/jrd/recsrc/NestedLoopJoin.cpp index 715690c333a..a9105c43a6c 100644 --- a/src/jrd/recsrc/NestedLoopJoin.cpp +++ b/src/jrd/recsrc/NestedLoopJoin.cpp @@ -57,9 +57,9 @@ NestedLoopJoin::NestedLoopJoin(CompilerScratch* csb, JoinType joinType, } NestedLoopJoin::NestedLoopJoin(CompilerScratch* csb, - RecordSource* outer, RecordSource* inner, - BoolExprNode* boolean) - : Join(csb, 2, JoinType::OUTER, boolean) + NestConst outerBoolean, + RecordSource* outer, RecordSource* inner) + : Join(csb, 2, JoinType::OUTER, nullptr, outerBoolean) { fb_assert(outer && inner); @@ -205,7 +205,7 @@ bool NestedLoopJoin::internalGetRecord(thread_db* tdbb) const if (!outer->getRecord(tdbb)) return false; - if (m_boolean && m_boolean->execute(tdbb, request) != TriState(true)) + if (!checkOuterBoolean(tdbb)) { // The boolean pertaining to the left sub-stream is false // so just join sub-stream to a null valued right sub-stream diff --git a/src/jrd/recsrc/RecordSource.h b/src/jrd/recsrc/RecordSource.h index a07e808fa43..432ee3ecf21 100644 --- a/src/jrd/recsrc/RecordSource.h +++ b/src/jrd/recsrc/RecordSource.h @@ -1309,11 +1309,15 @@ namespace Jrd class Join : public RecordSource { public: - Join(CompilerScratch* csb, FB_SIZE_T count, JoinType joinType, BoolExprNode* boolean = nullptr) - : RecordSource(csb), m_joinType(joinType), m_boolean(boolean), - m_args(csb->csb_pool, count) - { - fb_assert(!m_boolean || m_joinType == JoinType::OUTER); + Join(CompilerScratch* csb, FB_SIZE_T count, JoinType joinType, + NestConst joinBoolean = nullptr, NestConst outerBoolean = nullptr) + : RecordSource(csb), + m_joinType(joinType), + m_args(csb->csb_pool, count), + m_joinBoolean(joinBoolean), + m_outerBoolean(outerBoolean) + { + fb_assert(!m_outerBoolean || m_joinType == JoinType::OUTER); } void close(thread_db* tdbb) const override @@ -1355,7 +1359,8 @@ namespace Jrd return true; } - return (m_boolean && m_boolean->containsAnyStream(streams)); + return (m_joinBoolean && m_joinBoolean->containsAnyStream(streams)) || + (m_outerBoolean && m_outerBoolean->containsAnyStream(streams)); } void invalidateRecords(Request* request) const override @@ -1416,10 +1421,27 @@ namespace Jrd return ""; } + bool checkJoinBoolean(thread_db* tdbb) const + { + return m_joinBoolean ? + m_joinBoolean->execute(tdbb, tdbb->getRequest()) == Firebird::TriState(true) : + true; + } + + bool checkOuterBoolean(thread_db* tdbb) const + { + return m_outerBoolean ? + m_outerBoolean->execute(tdbb, tdbb->getRequest()) == Firebird::TriState(true) : + true; + } + protected: const JoinType m_joinType; - const NestConst m_boolean; Firebird::Array> m_args; + + private: + const NestConst m_joinBoolean; + const NestConst m_outerBoolean; }; class NestedLoopJoin : public Join @@ -1427,8 +1449,8 @@ namespace Jrd public: NestedLoopJoin(CompilerScratch* csb, JoinType joinType, FB_SIZE_T count, RecordSource* const* args); - NestedLoopJoin(CompilerScratch* csb, RecordSource* outer, RecordSource* inner, - BoolExprNode* boolean); + NestedLoopJoin(CompilerScratch* csb, NestConst outerBoolean, + RecordSource* outer, RecordSource* inner); void close(thread_db* tdbb) const override; void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; @@ -1442,6 +1464,14 @@ namespace Jrd bool fetchRecord(thread_db*, FB_SIZE_T) const; }; + class DummyJoin : public NestedLoopJoin + { + public: + explicit DummyJoin(CompilerScratch* csb) + : NestedLoopJoin(csb, JoinType::INNER, 0, nullptr) + {} + }; + class FullOuterJoin : public Join { public: @@ -1485,13 +1515,14 @@ namespace Jrd }; public: - HashJoin(thread_db* tdbb, CompilerScratch* csb, JoinType joinType, - FB_SIZE_T count, RecordSource* const* args, NestValueArray* const* keys, - double selectivity = 0); HashJoin(thread_db* tdbb, CompilerScratch* csb, - BoolExprNode* boolean, + NestConst joinBoolean, NestConst outerBoolean, RecordSource* const* args, NestValueArray* const* keys, double selectivity = 0); + HashJoin(thread_db* tdbb, CompilerScratch* csb, + JoinType joinType, NestConst joinBoolean, + FB_SIZE_T count, RecordSource* const* args, NestValueArray* const* keys, + double selectivity = 0); void close(thread_db* tdbb) const override; void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; @@ -1546,9 +1577,8 @@ namespace Jrd static const FB_SIZE_T MERGE_BLOCK_SIZE = 65536; public: - MergeJoin(CompilerScratch* csb, FB_SIZE_T count, - SortedStream* const* args, - const NestValueArray* const* keys); + MergeJoin(CompilerScratch* csb, NestConst joinBoolean, + FB_SIZE_T count, SortedStream* const* args, const NestValueArray* const* keys); void close(thread_db* tdbb) const override; void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; @@ -1564,6 +1594,7 @@ namespace Jrd UCHAR* getData(thread_db* tdbb, MergeFile* mfb, SLONG record) const; SLONG getRecordByIndex(thread_db* tdbb, FB_SIZE_T index) const; bool fetchRecord(thread_db* tdbb, FB_SIZE_T index) const; + bool formGroup(thread_db* tdbb) const; Firebird::Array m_keys; }; From 2a3e6bfde130c945f33aca0eb82250aeafefe2b2 Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Wed, 30 Sep 2026 16:34:53 +0300 Subject: [PATCH 28/30] Fix #9173: Wrong result: partial index WHERE col IS NOT NULL is applied when a comparison references a field with the same field id from another table --- src/jrd/optimizer/Retrieval.cpp | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/jrd/optimizer/Retrieval.cpp b/src/jrd/optimizer/Retrieval.cpp index 4b40d50cee2..a735aa30efb 100644 --- a/src/jrd/optimizer/Retrieval.cpp +++ b/src/jrd/optimizer/Retrieval.cpp @@ -84,16 +84,16 @@ namespace return newValue; } - bool matchSubset(const BoolExprNode* boolean, const BoolExprNode* sub) + bool matchSubset(const BoolExprNode* boolean, const BoolExprNode* sub, StreamType stream) { - if (boolean->sameAs(sub, true)) + if (boolean->sameAs(sub, true) && boolean->containsStream(stream)) return true; auto binaryNode = nodeAs(boolean); if (binaryNode && binaryNode->blrOp == blr_or) { - if (matchSubset(binaryNode->arg1, sub) || - matchSubset(binaryNode->arg2, sub)) + if (matchSubset(binaryNode->arg1, sub, stream) || + matchSubset(binaryNode->arg2, sub, stream)) { return true; } @@ -101,8 +101,8 @@ namespace binaryNode = nodeAs(sub); if (binaryNode && binaryNode->blrOp == blr_or) { - if (matchSubset(boolean, binaryNode->arg1) && - matchSubset(boolean, binaryNode->arg2)) + if (matchSubset(boolean, binaryNode->arg1, stream) && + matchSubset(boolean, binaryNode->arg2, stream)) { return true; } @@ -888,7 +888,7 @@ bool Retrieval::checkIndexCondition(index_desc& idx, BooleanList& matches) const if (!iter->containsStream(stream)) continue; - if (matchSubset(boolean, *iter)) + if (matchSubset(boolean, *iter, stream)) { matches.add(*iter); break; @@ -911,15 +911,23 @@ bool Retrieval::checkIndexCondition(index_desc& idx, BooleanList& matches) const const auto cmpNode = nodeAs(*iter); if (cmpNode && cmpNode->blrOp != blr_equiv) { - if (cmpNode->arg1->sameAs(missingNode->arg, true) || - cmpNode->arg2->sameAs(missingNode->arg, true)) + if (cmpNode->arg1->sameAs(missingNode->arg, true) && + cmpNode->arg1->containsStream(stream)) + { + matches.add(*iter); + break; + } + + if (cmpNode->arg2->sameAs(missingNode->arg, true) && + cmpNode->arg2->containsStream(stream)) { matches.add(*iter); break; } if (cmpNode->arg3 && - cmpNode->arg3->sameAs(missingNode->arg, true)) + cmpNode->arg3->sameAs(missingNode->arg, true) && + cmpNode->arg3->containsStream(stream)) { matches.add(*iter); break; From 28ff9db51b268f723c46ef0350d03e1c8de52ca2 Mon Sep 17 00:00:00 2001 From: AlexPeshkoff Date: Wed, 30 Sep 2026 16:39:20 +0300 Subject: [PATCH 29/30] Fixed regression caused by commit acf041e50264c0ab4e53aebbfd1616fc1bf5fa2a (Disable DDL under user savepoint) --- src/dsql/DdlNodes.epp | 26 ++++++-------------------- src/jrd/tra.cpp | 39 ++++++++++++++++++++++++++++++++++++--- src/jrd/tra.h | 11 +++++++++-- 3 files changed, 51 insertions(+), 25 deletions(-) diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp index 407f59ce1af..0f0eada69da 100644 --- a/src/dsql/DdlNodes.epp +++ b/src/dsql/DdlNodes.epp @@ -164,33 +164,15 @@ void ExecInSecurityDb::executeInSecurityDb(jrd_tra* localTransaction) secDbContext = localTransaction->setSecDbContext(att, tra); } - // run all statements under savepoint control - string savePoint; - savePoint.printf("ExecInSecurityDb%d", secDbContext->savePoint++); - secDbContext->att->execute(&statusWrapper, secDbContext->tra, 0, ("SAVEPOINT " + savePoint).c_str(), - SQL_DIALECT_V6, NULL, NULL, NULL, NULL); - check(&statusWrapper); - try { runInSecurityDb(secDbContext); - secDbContext->att->execute(&statusWrapper, secDbContext->tra, 0, - ("RELEASE SAVEPOINT " + savePoint).c_str(), - SQL_DIALECT_V6, NULL, NULL, NULL, NULL); - savePoint.erase(); - check(&statusWrapper); + secDbContext->releaseSavePoint(); } catch (const Exception&) { - if (savePoint.hasData()) - { - LocalStatus tmp; - CheckStatusWrapper tmpCheckStatusWrapper(&tmp); - secDbContext->att->execute(&tmpCheckStatusWrapper, secDbContext->tra, 0, - ("ROLLBACK TO SAVEPOINT " + savePoint).c_str(), - SQL_DIALECT_V6, NULL, NULL, NULL, NULL); - } + secDbContext->rollbackSavePoint(); throw; } @@ -15689,6 +15671,8 @@ void MappingNode::runInSecurityDb(SecDbContext* secDbContext) { try { + secDbContext->setSavePoint(); + // try direct access to rdb$auth_mapping table in secure db LocalStatus s2; CheckStatusWrapper statusWrapper2(&s2); @@ -16369,6 +16353,8 @@ void GrantRevokeNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, void GrantRevokeNode::runInSecurityDb(SecDbContext* secDbContext) { + secDbContext->setSavePoint(); + for (unsigned n = 0; n < createDbJobs.getCount(); ++n) { CreateDbJob& j = createDbJobs[n]; diff --git a/src/jrd/tra.cpp b/src/jrd/tra.cpp index 3e9d24e9c5e..a103fc22de7 100644 --- a/src/jrd/tra.cpp +++ b/src/jrd/tra.cpp @@ -4415,8 +4415,8 @@ void TraceSweepEvent::report(ntrace_process_state_t state) m_need_trace = false; } -SecDbContext::SecDbContext(IAttachment* a, ITransaction* t) noexcept - : att(a), tra(t), savePoint(0) +SecDbContext::SecDbContext(MemoryPool& p, IAttachment* a, ITransaction* t) noexcept + : att(a), tra(t), savePoint(p) { } SecDbContext::~SecDbContext() @@ -4444,7 +4444,7 @@ SecDbContext* jrd_tra::setSecDbContext(IAttachment* att, ITransaction* tra) { fb_assert(!tra_sec_db_context); - tra_sec_db_context = FB_NEW_POOL(*getDefaultMemoryPool()) SecDbContext(att, tra); + tra_sec_db_context = FB_NEW_POOL(*getDefaultMemoryPool()) SecDbContext(getPool(), att, tra); return tra_sec_db_context; } @@ -4565,3 +4565,36 @@ void jrd_tra::processCommits(thread_db* tdbb) throw; } } + +void SecDbContext::setSavePoint() +{ + fb_assert(savePoint.isEmpty()); + FbLocalStatus status; + + savePoint.printf("ExecInSecurityDb%d", savePointNumber++); + att->execute(&status, tra, 0, ("SAVEPOINT " + savePoint).c_str(), SQL_DIALECT_V6, NULL, NULL, NULL, NULL); + check(&status); +} + +void SecDbContext::releaseSavePoint() +{ + if (savePoint.hasData()) + { + FbLocalStatus status; + att->execute(&status, tra, 0, ("RELEASE SAVEPOINT " + savePoint).c_str(), + SQL_DIALECT_V6, NULL, NULL, NULL, NULL); + check(&status); + rollbackSavePoint(); + } +} + +void SecDbContext::rollbackSavePoint() noexcept +{ + if (savePoint.hasData()) + { + FbLocalStatus status; + att->execute(&status, tra, 0, ("ROLLBACK TO SAVEPOINT " + savePoint).c_str(), + SQL_DIALECT_V6, NULL, NULL, NULL, NULL); + savePoint.erase(); + } +} diff --git a/src/jrd/tra.h b/src/jrd/tra.h index 02c3c981813..ec94ea85ed8 100644 --- a/src/jrd/tra.h +++ b/src/jrd/tra.h @@ -76,12 +76,19 @@ class BulkInsert; class SecDbContext { public: - SecDbContext(Firebird::IAttachment* a, Firebird::ITransaction* t) noexcept; + SecDbContext(MemoryPool& p, Firebird::IAttachment* a, Firebird::ITransaction* t) noexcept; ~SecDbContext(); Firebird::IAttachment* att; Firebird::ITransaction* tra; - int savePoint; + + void setSavePoint(); + void releaseSavePoint(); + void rollbackSavePoint() noexcept; + +private: + int savePointNumber = 0; + Firebird::string savePoint; }; // Blobs active in transaction identified by bli_temp_id. Please keep this From 04b57d38a10478f39819aac7ccfa0fdbf0ffcd9a Mon Sep 17 00:00:00 2001 From: AlexPeshkoff Date: Tue, 6 Oct 2026 12:23:52 +0300 Subject: [PATCH 30/30] Work in progress --- src/dsql/DdlNodes.epp | 43 +++--- src/jrd/CacheVector.h | 4 + src/jrd/Relation.cpp | 33 +++-- src/jrd/Relation.h | 113 +++++++++++---- src/jrd/btr.cpp | 43 +++--- src/jrd/btr.h | 7 +- src/jrd/idx.cpp | 50 +++---- src/jrd/met.epp | 244 ++++++++++++++++++++++---------- src/jrd/optimizer/Retrieval.cpp | 16 +-- 9 files changed, 353 insertions(+), 200 deletions(-) diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp index 0f0eada69da..ac47804fb7a 100644 --- a/src/dsql/DdlNodes.epp +++ b/src/dsql/DdlNodes.epp @@ -13134,7 +13134,7 @@ void StoreIndexNode::step2(thread_db* tdbb, jrd_tra* transaction) auto id = expressionIndex ? createExpression(tdbb, transaction) : create(tdbb, transaction); if (id < tdbb->getDatabase()->dbb_max_idx) - indexRelation->newIndexVersion(tdbb, id, CacheFlag::MINISCAN); + indexRelation->newIndexVersion(tdbb, id); } @@ -13161,8 +13161,11 @@ MetaId StoreIndexNode::create(thread_db* tdbb, jrd_tra* transaction) index_desc idx; idx.idx_count = 0; + idx.idx_code = nullptr; int key_count = 0; + Cleanup idxCode([&]() {if (idx.idx_code) IndexCode::release(tdbb, idx.idx_code);}); + SET_TDBB(tdbb); Attachment* attachment = tdbb->getAttachment(); Database* dbb = tdbb->getDatabase(); @@ -13235,9 +13238,6 @@ MetaId StoreIndexNode::create(thread_db* tdbb, jrd_tra* transaction) END_FOR } - idx.idx_condition_node = nullptr; - idx.idx_condition_statement = nullptr; - if (!IDX.RDB$CONDITION_BLR.NULL) { // Allocate a new pool to contain the expression tree @@ -13253,8 +13253,9 @@ MetaId StoreIndexNode::create(thread_db* tdbb, jrd_tra* transaction) nullptr, &csb, indexName, obj_index_condition, 0, transaction); - idx.idx_condition_statement = Statement::makeBoolExpression(tdbb, - idx.idx_condition_node, csb, false); + idx.idx_code = FB_NEW_POOL(*dbb->dbb_permanent) IndexCode; + idx.idx_code->condition_statement = Statement::makeBoolExpression(tdbb, + idx.idx_code->condition, csb, false); idx.idx_flags |= idx_condition; } @@ -13431,6 +13432,7 @@ MetaId StoreIndexNode::create(thread_db* tdbb, jrd_tra* transaction) fb_assert(idxId <= dbb->dbb_max_idx); idx.idx_id = idxId; SelectivityList selectivity(*tdbb->getDefaultPool()); + IDX_create_index(tdbb, concurrently ? IdxCreate::Concurrently : IdxCreate::ForRollback, relation, &idx, indexName, &idxId, transaction, selectivity); @@ -13445,9 +13447,6 @@ MetaId StoreIndexNode::create(thread_db* tdbb, jrd_tra* transaction) else fb_assert(idxId == dbb->dbb_max_idx); - if (idx.idx_condition_statement) - idx.idx_condition_statement->release(tdbb); - if (partnerRelation) { indexRelation->checkPartners(tdbb); @@ -13490,6 +13489,9 @@ MetaId StoreIndexNode::createExpression(thread_db* tdbb, jrd_tra* transaction) CompilerScratch* csb = nullptr; const auto dbb = tdbb->getDatabase(); + idx.idx_code = FB_NEW_POOL(*dbb->dbb_permanent) IndexCode; + Cleanup idxCode([&]() {IndexCode::release(tdbb, idx.idx_code);}); + const auto attachment = tdbb->getAttachment(); MetaId idxId = dbb->dbb_max_idx; bool active = false; @@ -13543,8 +13545,8 @@ MetaId StoreIndexNode::createExpression(thread_db* tdbb, jrd_tra* transaction) nullptr, &csb, indexName, obj_index_expression, 0, transaction); - idx.idx_expression_statement = Statement::makeValueExpression(tdbb, - idx.idx_expression_node, idx.idx_expression_desc, csb, false); + idx.idx_code->expression_statement = Statement::makeValueExpression(tdbb, + idx.idx_code->expression, idx.idx_code->expression_desc, csb, false); // fake a description of the index @@ -13552,8 +13554,8 @@ MetaId StoreIndexNode::createExpression(thread_db* tdbb, jrd_tra* transaction) idx.idx_flags |= idx_expression; idx.idx_rpt[0].idx_itype = DFW_assign_index_type(tdbb, indexName, - idx.idx_expression_desc.dsc_dtype, - idx.idx_expression_desc.getTextType()); + idx.idx_code->expression_desc.dsc_dtype, + idx.idx_code->expression_desc.getTextType()); idx.idx_rpt[0].idx_selectivity = 0; } catch (const Exception&) @@ -13576,8 +13578,8 @@ MetaId StoreIndexNode::createExpression(thread_db* tdbb, jrd_tra* transaction) nullptr, &csb, indexName, obj_index_condition, 0, transaction); - idx.idx_condition_statement = Statement::makeBoolExpression(tdbb, - idx.idx_condition_node, csb, false); + idx.idx_code->condition_statement = Statement::makeBoolExpression(tdbb, + idx.idx_code->condition, csb, false); idx.idx_flags |= idx_condition; } @@ -13620,6 +13622,7 @@ MetaId StoreIndexNode::createExpression(thread_db* tdbb, jrd_tra* transaction) { fb_assert(idxId <= dbb->dbb_max_idx); idx.idx_id = idxId; + IDX_create_index(tdbb, concurrently ? IdxCreate::Concurrently : IdxCreate::ForRollback, relation, &idx, indexName, &idxId, transaction, selectivity); @@ -13629,9 +13632,7 @@ MetaId StoreIndexNode::createExpression(thread_db* tdbb, jrd_tra* transaction) catch (const Exception&) { // Get rid of the expression/condition statements - idx.idx_expression_statement->release(tdbb); - if (idx.idx_condition_statement) - idx.idx_condition_statement->release(tdbb); + IndexCode::release(tdbb, idx.idx_code); throw; } @@ -13646,9 +13647,7 @@ MetaId StoreIndexNode::createExpression(thread_db* tdbb, jrd_tra* transaction) fb_assert(idxId == dbb->dbb_max_idx); // Get rid of the expression/condition statements - idx.idx_expression_statement->release(tdbb); - if (idx.idx_condition_statement) - idx.idx_condition_statement->release(tdbb); + IndexCode::release(tdbb, idx.idx_code); return idxId; } @@ -14496,7 +14495,7 @@ void AlterIndexNode::step2(thread_db* tdbb, jrd_tra* transaction) else IDX_mark_index(tdbb, indexRelation, idxId.value()); - indexRelation->newIndexVersion(tdbb, idxId.value(), CacheFlag::MINISCAN); + indexRelation->newIndexVersion(tdbb, idxId.value()); } // Alter an index on a Local Temporary Table (ACTIVE/INACTIVE). diff --git a/src/jrd/CacheVector.h b/src/jrd/CacheVector.h index c0527e7b61a..af4710ce31b 100644 --- a/src/jrd/CacheVector.h +++ b/src/jrd/CacheVector.h @@ -53,6 +53,10 @@ class ObjectBase { public: typedef unsigned Flag; + +public: + virtual ~ObjectBase() + { } }; diff --git a/src/jrd/Relation.cpp b/src/jrd/Relation.cpp index 7192d265deb..377445ab01e 100644 --- a/src/jrd/Relation.cpp +++ b/src/jrd/Relation.cpp @@ -736,6 +736,8 @@ IndexVersion::IndexVersion(MemoryPool& p, Cached::Index* idp) void IndexVersion::destroy(thread_db* tdbb, IndexVersion* idv) { + if (idv->idv_code) + IndexCode::release(tdbb, idv->idv_code); delete idv; } @@ -744,6 +746,18 @@ ObjectType IndexVersion::objectType() noexcept return obj_index; } +void IndexVersion::replaceCodeConditional(thread_db* tdbb, IndexCode* newCode) +{ + if (newCode && (newCode != idv_code)) + { + auto* oldCode = idv_code; + newCode->addRef(); + idv_code = newCode; + if (oldCode) + IndexCode::release(tdbb, oldCode); + } +} + void jrd_rel::releaseTriggers(thread_db* tdbb, bool destroy) { @@ -1114,20 +1128,13 @@ FB_UINT64 IndexPermanent::makeLockId(MetaId relId, MetaId indexId) return (FB_UINT64(relId) << REL_ID_KEY_OFFSET) + indexId; } -void IndexPermanent::releaseStatements(thread_db* tdbb) +void IndexCode::releaseStatements(thread_db* tdbb) { - if (idp_expression_statement) - { - idp_expression_statement->release(tdbb); - idp_expression_statement = nullptr; - idp_expression = nullptr; - } - if (idp_condition_statement) - { - idp_condition_statement->release(tdbb); - idp_condition_statement = nullptr; - idp_condition = nullptr; - } + if (expression_statement) + expression_statement->release(tdbb); + + if (condition_statement) + condition_statement->release(tdbb); } void IndexPermanent::reloadAst(thread_db* tdbb, TraNumber tran, bool erase) diff --git a/src/jrd/Relation.h b/src/jrd/Relation.h index bd915621728..3cb57ab241d 100644 --- a/src/jrd/Relation.h +++ b/src/jrd/Relation.h @@ -457,6 +457,46 @@ enum IndexStatus // Index block +struct IndexCode +{ +public: + IndexCode() + { + expression_bid.clear(); + condition_bid.clear(); + addRef(); // first reference always comes from IndexPermanent + } + + bid expression_bid; + ValueExprNode* expression = nullptr; // node tree for index expression + Statement* expression_statement = nullptr; // statement for index expression evaluation + dsc expression_desc; // descriptor for expression result + + bid condition_bid; + BoolExprNode* condition = nullptr; // node tree for index condition + Statement* condition_statement = nullptr; // statement for index condition evaluation + + void addRef() + { + ++cnt; + } + + static void release(thread_db* tdbb, IndexCode*& code) + { + if (code && --code->cnt == 0) + { + code->releaseStatements(tdbb); + delete code; + code = nullptr; + } + } + +private: + void releaseStatements(thread_db* tdbb); + std::atomic cnt = 0; +}; + + class IndexPermanent : public Firebird::PermanentStorage { public: @@ -465,8 +505,6 @@ class IndexPermanent : public Firebird::PermanentStorage idp_relation(rel), idp_id(id) { - idp_expression_bid.clear(); - idp_condition_bid.clear(); } ~IndexPermanent() @@ -474,7 +512,8 @@ class IndexPermanent : public Firebird::PermanentStorage static bool destroy(thread_db* tdbb, IndexPermanent* idp) { - idp->releaseStatements(tdbb); + if (idp->idp_latest_code) + IndexCode::release(tdbb, idp->idp_latest_code); return false; } @@ -494,8 +533,12 @@ class IndexPermanent : public Firebird::PermanentStorage static FB_UINT64 makeLockId(MetaId relId, MetaId indexId); const QualifiedName& getName(); +public: + Firebird::Mutex idp_code_mutex; // Delays concurrent threads till the end of code refresh + private: RelationPermanent* idp_relation; + IndexCode* idp_latest_code = nullptr; MetaId idp_id; TraNumber idp_tranum = 0; UCHAR idp_state = 0; // Makes limited sense for segmented indices @@ -503,26 +546,25 @@ class IndexPermanent : public Firebird::PermanentStorage [[noreturn]] void errIndexGone(); - void releaseStatements(thread_db* tdbb); - public: - void lookupIndexCode(thread_db* tdbb, Cached::Relation* relation, index_desc* idx, - const Ods::index_root_page::irt_repeat* irt_desc) + void setState(UCHAR state) noexcept { - if ((irt_desc->getState() != idp_state) || (irt_desc->getTransaction() != idp_tranum)) - refreshIndexCode(tdbb, relation, idx, irt_desc); + idp_state = state; + } - idx->idx_condition_node = idp_condition; - idx->idx_condition_statement = idp_condition_statement; + UCHAR getState() const noexcept + { + return idp_state; + } - idx->idx_expression_node = idp_expression; - idx->idx_expression_statement = idp_expression_statement; - memcpy(&idx->idx_expression_desc, &idp_expression_desc, sizeof(struct dsc)); + void setTraNum(TraNumber num) noexcept + { + idp_tranum = num; } - void setState(UCHAR state) noexcept + TraNumber getTraNum() const noexcept { - idp_state = state; + return idp_tranum; } UCHAR getFormat() const noexcept @@ -535,20 +577,13 @@ class IndexPermanent : public Firebird::PermanentStorage idp_formatNumber = fmt; } -private: - void refreshIndexCode(thread_db* tdbb, Cached::Relation* relation, - index_desc* idx, const Ods::index_root_page::irt_repeat* irt_desc); - - Firebird::Mutex idp_code_mutex; // Delays concurrent threads till the end of code refresh - - bid idp_expression_bid; - ValueExprNode* idp_expression = nullptr; // node tree for index expression - Statement* idp_expression_statement = nullptr; // statement for index expression evaluation - dsc idp_expression_desc; // descriptor for expression result + IndexCode* refreshIndexCode(thread_db* tdbb, Cached::Relation* relation, + const Ods::index_root_page::irt_repeat* irt_desc); - bid idp_condition_bid; - BoolExprNode* idp_condition = nullptr; // node tree for index condition - Statement* idp_condition_statement = nullptr; // statement for index condition evaluation + IndexCode* getCode() const noexcept + { + return idp_latest_code; + } }; @@ -614,11 +649,17 @@ class IndexVersion final : public ObjectBase void setLtt(thread_db* tdbb, const QualifiedName& name, bool unique, bool descending, USHORT segmentCount, bool inactive = false); + void lookupIndexCode(thread_db* tdbb, Cached::Relation* relation, index_desc* idx, + const Ods::index_root_page::irt_repeat* irt_desc); + void replaceCodeConditional(thread_db* tdbb, IndexCode* newCode); + IndexCode* refreshIndexCode(thread_db* tdbb, Cached::Relation* relation, + const Ods::index_root_page::irt_repeat* irt_desc); static const enum lck_t LOCKTYPE = LCK_idx_rescan; private: Cached::Index* perm; + IndexCode* idv_code = nullptr; QualifiedName idv_name; SSHORT idv_uniqFlag = 0; SSHORT idv_segmentCount = 0; @@ -862,7 +903,7 @@ class RelationPermanent : public Firebird::PermanentStorage Cached::Index* lookupIndex(thread_db* tdbb, const QualifiedName& name, ObjectBase::Flag flags); Cached::Index* ensureIndex(thread_db* tdbb, MetaId id); - void newIndexVersion(thread_db* tdbb, MetaId id, ObjectBase::Flag scanType) + void newIndexVersion(thread_db* tdbb, MetaId id) { [[maybe_unused]] auto chk = rel_indices.newVersion(tdbb, id); fb_assert(chk); @@ -1207,6 +1248,18 @@ inline void GCLock::Exclusive::release() } +/// class IndexVersion + +inline void IndexVersion::lookupIndexCode(thread_db* tdbb, Cached::Relation* relation, index_desc* idx, + const Ods::index_root_page::irt_repeat* irt_desc) +{ + if ((irt_desc->getState() != perm->getState()) || (irt_desc->getTransaction() != perm->getTraNum())) + idx->idx_code = perm->refreshIndexCode(tdbb, relation, irt_desc); + else + idx->idx_code = idv_code; +} + + // Field block, one for each field in a scanned relation inline constexpr USHORT FLD_parse_computed = 0x0001; // computed expression is being parsed diff --git a/src/jrd/btr.cpp b/src/jrd/btr.cpp index 3ca62e6ff5a..e3b58eeece6 100644 --- a/src/jrd/btr.cpp +++ b/src/jrd/btr.cpp @@ -502,12 +502,12 @@ IndexCondition::IndexCondition(thread_db* tdbb, index_desc* idx) if (!(idx->idx_flags & idx_condition)) return; - fb_assert(idx->idx_condition_node); - m_condition = idx->idx_condition_node; + fb_assert(idx->idx_code && idx->idx_code->condition); + m_condition = idx->idx_code->condition; - fb_assert(idx->idx_condition_statement); + fb_assert(idx->idx_code->condition_statement); const auto orgRequest = tdbb->getRequest(); - m_request = idx->idx_condition_statement->findRequest(tdbb, true); + m_request = idx->idx_code->condition_statement->findRequest(tdbb, true); if (!m_request) ERR_post(Arg::Gds(isc_random) << "Attempt to evaluate index condition recursively"); @@ -605,12 +605,12 @@ IndexExpression::IndexExpression(thread_db* tdbb, index_desc* idx) if (!(idx->idx_flags & idx_expression)) return; - fb_assert(idx->idx_expression_node); - m_expression = idx->idx_expression_node; + fb_assert(idx->idx_code->expression); + m_expression = idx->idx_code->expression; - fb_assert(idx->idx_expression_statement); + fb_assert(idx->idx_code->expression_statement); const auto orgRequest = tdbb->getRequest(); - m_request = idx->idx_expression_statement->findRequest(tdbb, true); + m_request = idx->idx_code->expression_statement->findRequest(tdbb, true); if (!m_request) ERR_post(Arg::Gds(isc_random) << "Attempt to evaluate index expression recursively"); @@ -1691,10 +1691,7 @@ bool BTR_description(thread_db* tdbb, Cached::Relation* relation, const index_ro idx->idx_foreign_dep.clear(); idx->idx_primary_relation = 0; idx->idx_primary_index = 0; - idx->idx_expression_node = nullptr; - idx->idx_expression_statement = nullptr; - idx->idx_condition_node = nullptr; - idx->idx_condition_statement = nullptr; + idx->idx_code = nullptr; idx->idx_fraction = 1.0; idx->idx_state = irt_desc->getState(); @@ -1714,11 +1711,17 @@ bool BTR_description(thread_db* tdbb, Cached::Relation* relation, const index_ro ISC_STATUS error = 0; if (idx->idx_flags & (idx_expression | idx_condition)) { - auto* idp = relation->ensureIndex(tdbb, idx->idx_id); - if (idp) - idp->lookupIndexCode(tdbb, relation, idx, irt_desc); + auto* idv = relation->lookup_index(tdbb, idx->idx_id, CacheFlag::AUTOCREATE); + if (idv) + idv->lookupIndexCode(tdbb, relation, idx, irt_desc); + else + { + auto* idp = relation->ensureIndex(tdbb, idx->idx_id); + fb_assert(idp); + idx->idx_code = idp->refreshIndexCode(tdbb, relation, irt_desc, nullptr); + } - if (idx->idx_flags & idx_expression && !idx->idx_expression_node) + if (idx->idx_flags & idx_expression && !idx->idx_code->expression) { if (tdbb->tdbb_flags & TDBB_sweeper) return false; @@ -1726,7 +1729,7 @@ bool BTR_description(thread_db* tdbb, Cached::Relation* relation, const index_ro // Definition of index expression is not found for index @1 error = isc_idx_expr_not_found; } - else if (idx->idx_flags & idx_condition && !idx->idx_condition_node) + else if (idx->idx_flags & idx_condition && !idx->idx_code->condition) { if (tdbb->tdbb_flags & TDBB_sweeper) return false; @@ -2382,9 +2385,9 @@ USHORT BTR_key_length(thread_db* tdbb, jrd_rel* relation, index_desc* idx) default: if (idx->idx_flags & idx_expression) { - fb_assert(idx->idx_expression_node); - length = idx->idx_expression_desc.dsc_length; - if (idx->idx_expression_desc.dsc_dtype == dtype_varying) + fb_assert(idx->idx_code->expression); + length = idx->idx_code->expression_desc.dsc_length; + if (idx->idx_code->expression_desc.dsc_dtype == dtype_varying) { length = length - sizeof(SSHORT); } diff --git a/src/jrd/btr.h b/src/jrd/btr.h index 41f3092471a..4d5b5398018 100644 --- a/src/jrd/btr.h +++ b/src/jrd/btr.h @@ -55,6 +55,7 @@ class PartitionedSort; struct sort_key_def; struct record_param; struct win; +struct IndexCode; // Dependencies from/to foreign references @@ -95,11 +96,7 @@ struct index_desc MetaId idx_primary_relation; // id for primary key partner relation USHORT idx_count; // number of keys dep idx_foreign_dep; // foreign key partner - ValueExprNode* idx_expression_node; // node tree for indexed expression - dsc idx_expression_desc; // descriptor for expression result - Statement* idx_expression_statement; // stored statement for expression evaluation - BoolExprNode* idx_condition_node; // node tree for index condition - Statement* idx_condition_statement; // stored statement for index condition + IndexCode* idx_code; // support of expression / condition index float idx_fraction; // fraction of keys included in the index UCHAR idx_state; // state from irt_rpt // This structure should exactly match IRTD structure for current ODS diff --git a/src/jrd/idx.cpp b/src/jrd/idx.cpp index 24941dd9ae4..de1361e4caf 100644 --- a/src/jrd/idx.cpp +++ b/src/jrd/idx.cpp @@ -596,24 +596,12 @@ class IndexCreateTask : public Task } FbLocalStatus status; - if (m_tra || m_idx.idx_expression_statement || m_idx.idx_condition_statement) + if (m_tra) { BackgroundContextHolder tdbb(att->att_database, att, &status, FB_FUNCTION); if (m_tra) TRA_commit(tdbb, m_tra, false); - - if (m_idx.idx_expression_statement) - { - m_idx.idx_expression_statement->release(tdbb); - m_idx.idx_expression_statement = NULL; - } - - if (m_idx.idx_condition_statement) - { - m_idx.idx_condition_statement->release(tdbb); - m_idx.idx_condition_statement = NULL; - } } WorkerAttachment::releaseAttachment(&status, m_attStable); @@ -665,15 +653,6 @@ class IndexCreateTask : public Task if (!m_sort) { m_idx = *creation->index; // copy - if (m_ownAttach) - { - m_idx.idx_expression_node = NULL; - m_idx.idx_expression_statement = NULL; - m_idx.idx_foreign_dep.clear(); - m_idx.idx_condition_node = NULL; - m_idx.idx_condition_statement = NULL; - } - FPTR_REJECT_DUP_CALLBACK callback = NULL; void* callback_arg = NULL; @@ -815,32 +794,43 @@ bool IndexCreateTask::handler(WorkItem& _item) partner_index_id = idx->idx_primary_index; } - if ((idx->idx_flags & idx_expression) && (idx->idx_expression_node == NULL)) + // Assertion checks block + if (idx->idx_flags & (idx_expression | idx_condition)) + fb_assert(idx->idx_code); + if (idx->idx_flags & idx_expression) + fb_assert(idx->idx_code->expression); + if (idx->idx_flags & idx_condition) + fb_assert(idx->idx_code->condition); +/* + if ((idx->idx_flags & (idx_expression|idx_condition)) && (idx->idx_code == NULL)) + idx->idx_code = FB_NEW_POOL(*dbb->dbb_permanent) IndexCode(); + + if ((idx->idx_flags & idx_expression) && (idx->idx_code->expression == NULL)) { fb_assert(!m_exprBlob.isEmpty()); CompilerScratch* csb = NULL; Jrd::ContextPoolHolder context(tdbb, dbb->createPool()); - idx->idx_expression_node = static_cast (MET_parse_blob(tdbb, &relation->getName().schema, - getPermanent(relation), &m_exprBlob, &csb, &idx->idx_expression_statement, false, false)); + idx->idx_code->expression = static_cast (MET_parse_blob(tdbb, &relation->getName().schema, + getPermanent(relation), &m_exprBlob, &csb, &idx->idx_code->expression_statement, false, false)); delete csb; } - if ((idx->idx_flags & idx_condition) && (idx->idx_condition_node == NULL)) + if ((idx->idx_flags & idx_condition) && (idx->idx_code->condition == NULL)) { fb_assert(!m_condBlob.isEmpty()); CompilerScratch* csb = NULL; Jrd::ContextPoolHolder context(tdbb, dbb->createPool()); - idx->idx_condition_node = static_cast (MET_parse_blob(tdbb, &relation->getName().schema, - getPermanent(relation), &m_condBlob, &csb, &idx->idx_condition_statement, false, false)); + idx->idx_code->condition = static_cast (MET_parse_blob(tdbb, &relation->getName().schema, + getPermanent(relation), &m_condBlob, &csb, &idx->idx_code->condition_statement, false, false)); delete csb; } - +*/ RecordStack stack(*transaction->tra_pool), free(*transaction->tra_pool); if (m_flags & IS_LARGE_SCAN) @@ -2044,7 +2034,7 @@ static bool cmpRecordKeys(thread_db* tdbb, // So we must save the first result into another dsc. tempDesc = *idxDesc; - const auto idxDscLength = idx2->idx_expression_desc.dsc_length; + const auto idxDscLength = idx2->idx_code->expression_desc.dsc_length; tempDesc.dsc_address = tmp.getBuffer(idxDscLength + FB_DOUBLE_ALIGN); tempDesc.dsc_address = FB_ALIGN(tempDesc.dsc_address, FB_DOUBLE_ALIGN); fb_assert(idxDesc->dsc_length <= idxDscLength); diff --git a/src/jrd/met.epp b/src/jrd/met.epp index 5cd51517e42..b72b5743daf 100644 --- a/src/jrd/met.epp +++ b/src/jrd/met.epp @@ -2224,9 +2224,79 @@ ElementBase::ReturnedId MetadataCache::lookup_index_name(thread_db* tdbb, const return id; } - -void IndexPermanent::refreshIndexCode(thread_db* tdbb, Cached::Relation* relation, index_desc* idx, +/* +IndexCode* IndexVersion::refreshIndexCode(thread_db* tdbb, Cached::Relation* relation, const Ods::index_root_page::irt_repeat* irt_desc) +{ + auto* code = perm->refreshIndexCode(tdbb, relation, irt_desc); + + if (code) + { + auto* idv = relation->lookup_index(tdbb, getId(), CacheFlag::AUTOCREATE); + if (!idv->idv_code) + { + code->addRef(); + idv->idv_code = code; + } + } + + return code; +} + +*/ +IndexCode* IndexPermanent::refreshIndexCode(thread_db* tdbb, RelationPermanent* relation, + const Ods::index_root_page::irt_repeat& irt_desc) +{ + SET_TDBB(tdbb); + IndexCode* code = nullptr; + + MutexLockGuard g(idp_code_mutex, FB_FUNCTION); +/* + if (iCode && irt_desc) + { + if ((idp_state == Ods::irt_unused) && // permanent index block in cache just created + (getTraNum() == 0) && // recheck just created fact + (irt_desc->getState() == Ods::irt_normal)) // no current DDL activity on index + { + setTraNum(irt_desc->getTransaction()); + setState(Ods::irt_normal); + return iCode; + } + } +*/ + if ((irt_desc.getState() != idp_state) || (irt_desc.getTransaction() != idp_tranum)) + { + if (irt_desc.getState() == Ods::) + { + relation->newIndexVersion(tdbb, getId()); + auto* idv = relation->lookup_index(tdbb, getId(), CacheFlag::AUTOCREATE); + return + + // Переделать в buildIndexCode который вызывать из lookupIndexCode и Index::scan() + + if (!idv->idv_code) + { + code->addRef(); + idv->idv_code = code; + } + } + + if (irt_desc) + { + idp_state = irt_desc.getState(); + idp_tranum = irt_desc.getTransaction(); + } + + if (code) + { + auto* old_code = idp_latest_code; + idp_latest_code = code; + if (old_code) + IndexCode::release(tdbb, old_code); + } + + +void IndexPermanent::buildIndexCode(thread_db* tdbb, Cached::Relation* relation) { /*********************************************** * @@ -2239,85 +2309,84 @@ void IndexPermanent::refreshIndexCode(thread_db* tdbb, Cached::Relation* relatio * **************************************/ SET_TDBB(tdbb); + Database* dbb = tdbb->getDatabase(); + Attachment* attachment = tdbb->getAttachment(); - MutexLockGuard g(idp_code_mutex, FB_FUNCTION); + bid expression, condition; + expression.clear(); + condition.clear(); + + MetaId relId = relation->getId(); - if ((irt_desc->getState() != idp_state) || (irt_desc->getTransaction() != idp_tranum)) + Jrd::ContextPoolHolder context(tdbb, dbb->dbb_permanent); + + AUTO_HANDLE(handle); + FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE attachment->getMetaTransaction(tdbb)) + IND IN RDB$INDICES + CROSS REL IN RDB$RELATIONS + WITH IND.RDB$INDEX_ID EQ getId() + 1 AND + REL.RDB$RELATION_ID EQ relId AND + REL.RDB$SCHEMA_NAME EQ IND.RDB$SCHEMA_NAME AND + REL.RDB$PACKAGE_NAME EQUIV IND.RDB$PACKAGE_NAME AND + REL.RDB$RELATION_NAME EQ IND.RDB$RELATION_NAME { - Database* dbb = tdbb->getDatabase(); - Attachment* attachment = tdbb->getAttachment(); + if (!IND.RDB$EXPRESSION_BLR.NULL) + expression = IND.RDB$EXPRESSION_BLR; + if (!IND.RDB$CONDITION_BLR.NULL) + condition = IND.RDB$CONDITION_BLR; + } + END_FOR - bid expression, condition; - expression.clear(); - condition.clear(); + if (!(expression.hasData() || condition.hasData())) + return; - MetaId relId = relation->getId(); + auto* code = FB_NEW_POOL(*dbb->dbb_permanent) IndexCode; + code->expression_bid = expression; + code->condition_bid = condition; - Jrd::ContextPoolHolder context(tdbb, dbb->dbb_permanent); + if (expression.hasData()) + { + MemoryPool* stmtPool = dbb->createPool(); + try + { + Jrd::ContextPoolHolder context(tdbb, stmtPool); - AUTO_HANDLE(handle); - FOR(REQUEST_HANDLE handle) // Use system transaction - IND IN RDB$INDICES - CROSS REL IN RDB$RELATIONS - WITH IND.RDB$INDEX_ID EQ getId() + 1 AND - REL.RDB$RELATION_ID EQ relId AND - REL.RDB$SCHEMA_NAME EQ IND.RDB$SCHEMA_NAME AND - REL.RDB$PACKAGE_NAME EQUIV IND.RDB$PACKAGE_NAME AND - REL.RDB$RELATION_NAME EQ IND.RDB$RELATION_NAME + CompilerScratch* csb = nullptr; + Cleanup cc([csb]() {delete csb;}); + MET_parse_blob(tdbb, &relation->getName().schema, relation, &expression, &csb, nullptr, false, false); + code->expression_statement = + Statement::makeValueExpression(tdbb, code->expression, code->expression_desc, csb, false); + } + catch (const Exception&) { - if (!IND.RDB$EXPRESSION_BLR.NULL) - expression = IND.RDB$EXPRESSION_BLR; - if (!IND.RDB$CONDITION_BLR.NULL) - condition = IND.RDB$CONDITION_BLR; + dbb->deletePool(stmtPool); + throw; } - END_FOR - - releaseStatements(tdbb); - - idp_expression_bid = expression; - idp_condition_bid = condition; + } - if (expression.hasData()) + if (condition.hasData()) + { + MemoryPool* stmtPool = dbb->createPool(); + try { - MemoryPool* stmtPool = dbb->createPool(); - try - { - Jrd::ContextPoolHolder context(tdbb, stmtPool); + Jrd::ContextPoolHolder context(tdbb, stmtPool); - CompilerScratch* csb = nullptr; - Cleanup cc([csb]() {delete csb;}); - MET_parse_blob(tdbb, &relation->getName().schema, relation, &expression, &csb, nullptr, false, false); - idp_expression_statement = Statement::makeValueExpression(tdbb, idp_expression, idp_expression_desc, csb, false); - } - catch (const Exception&) - { - dbb->deletePool(stmtPool); - throw; - } + CompilerScratch* csb = nullptr; + Cleanup cc([csb]() {delete csb;}); + MET_parse_blob(tdbb, &relation->getName().schema, relation, &condition, &csb, nullptr, false, false); + code->condition_statement = Statement::makeBoolExpression(tdbb, code->condition, csb, false); } - - if (condition.hasData()) + catch (const Exception&) { - MemoryPool* stmtPool = dbb->createPool(); - try - { - Jrd::ContextPoolHolder context(tdbb, stmtPool); - - CompilerScratch* csb = nullptr; - Cleanup cc([csb]() {delete csb;}); - MET_parse_blob(tdbb, &relation->getName().schema, relation, &condition, &csb, nullptr, false, false); - idp_condition_statement = Statement::makeBoolExpression(tdbb, idp_condition, csb, false); - } - catch (const Exception&) - { - dbb->deletePool(stmtPool); - throw; - } + dbb->deletePool(stmtPool); + throw; } - - idp_state = irt_desc->getState(); - idp_tranum = irt_desc->getTransaction(); } + + auto* old_code = idp_latest_code; + idp_latest_code = code; + if (old_code) + IndexCode::release(tdbb, old_code); } @@ -5158,14 +5227,16 @@ ScanResult IndexVersion::scan(thread_db* tdbb, ObjectBase::Flag flags) } END_FOR - if (idv_name.isEmpty()) + if (idv_name.isEmpty() || !found) { idv_active = MET_index_inactive; return ScanResult::MISS; } + auto* code = buildIndexCode(tdbb, MetadataCache::getPerm(tdbb, relId, 0), nullptr); + replaceCodeConditional(tdbb, code); - return found ? ScanResult::COMPLETE : ScanResult::MISS; + return ScanResult::COMPLETE; } void IndexVersion::setLtt(thread_db* tdbb, const QualifiedName& name, bool unique, bool descending, @@ -5686,6 +5757,7 @@ void ElementBase::fillDeps(thread_db* tdbb, bool forceRecompile) REDUCED TO D.RDB$DEPENDENT_TYPE, D.RDB$DEPENDENT_SCHEMA_NAME, D.RDB$PACKAGE_NAME, D.RDB$DEPENDENT_NAME { ElementBase* element = nullptr; + AutoRequest request; QualifiedName name; name.schema = D.RDB$DEPENDENT_SCHEMA_NAME; @@ -5711,9 +5783,7 @@ void ElementBase::fillDeps(thread_db* tdbb, bool forceRecompile) case obj_trigger: { // Distinguish between triggers on table and database-wide - bool found = false; - AUTO_HANDLE(trigHndl); - FOR (REQUEST_HANDLE trigHndl TRANSACTION_HANDLE metaTransaction) + FOR (REQUEST_HANDLE request TRANSACTION_HANDLE metaTransaction) TRG IN RDB$TRIGGERS WITH TRG.RDB$SCHEMA_NAME EQ D.RDB$DEPENDENT_SCHEMA_NAME AND TRG.RDB$TRIGGER_NAME EQ D.RDB$DEPENDENT_NAME @@ -5735,8 +5805,7 @@ void ElementBase::fillDeps(thread_db* tdbb, bool forceRecompile) case obj_computed: { // should refresh ralations that have such computed fields - AUTO_HANDLE(compHndl); - FOR (REQUEST_HANDLE compHndl TRANSACTION_HANDLE metaTransaction) + FOR (REQUEST_HANDLE request TRANSACTION_HANDLE metaTransaction) FLD IN RDB$RELATION_FIELDS WITH FLD.RDB$FIELD_SOURCE EQ D.RDB$DEPENDENT_NAME AND FLD.RDB$FIELD_SOURCE_SCHEMA_NAME EQ D.RDB$DEPENDENT_SCHEMA_NAME @@ -5752,6 +5821,37 @@ void ElementBase::fillDeps(thread_db* tdbb, bool forceRecompile) } break; + case obj_index_expression: + case obj_index_condition: + { + // should reload that index + + // skip dependencies from relation - expect to be processed in DDL node + if (D.RDB$DEPENDED_ON_TYPE == obj_relation) + break; + + FOR (REQUEST_HANDLE request TRANSACTION_HANDLE metaTransaction) + IND IN RDB$INDICES + WITH IND.RDB$INDEX_NAME EQ D.RDB$DEPENDENT_NAME + AND IND.RDB$SCHEMA_NAME EQ D.RDB$DEPENDENT_SCHEMA_NAME + AND IND.RDB$PACKAGE_NAME EQUIV NULLIF(D.RDB$PACKAGE_NAME, "") + { + name.object = IND.RDB$RELATION_NAME; + name.schema = IND.RDB$SCHEMA_NAME; + name.package = IND.RDB$PACKAGE_NAME.NULL ? "" : IND.RDB$PACKAGE_NAME; + auto rel = MetadataCache::getPerm(tdbb, name, CacheFlag::AUTOCREATE); + if (rel) + { + fb_assert(IND.RDB$INDEX_ID > 0); + auto index = rel->lookupIndex(tdbb, IND.RDB$INDEX_ID - 1, CacheFlag::AUTOCREATE); + printf("Process index\n"); + transaction->storeUpdate(index, forceRecompile); + } + } + END_FOR + } + break; + default: break; } diff --git a/src/jrd/optimizer/Retrieval.cpp b/src/jrd/optimizer/Retrieval.cpp index a735aa30efb..245d1399694 100644 --- a/src/jrd/optimizer/Retrieval.cpp +++ b/src/jrd/optimizer/Retrieval.cpp @@ -859,9 +859,9 @@ bool Retrieval::betterInversion(const InversionCandidate* inv1, bool Retrieval::checkIndexCondition(index_desc& idx, BooleanList& matches) const { - fb_assert(idx.idx_condition_node); + fb_assert(idx.idx_code->condition); - if (!idx.idx_condition_node->containsStream(0, true)) + if (!idx.idx_code->condition->containsStream(0, true)) return false; fb_assert(matches.isEmpty()); @@ -869,7 +869,7 @@ bool Retrieval::checkIndexCondition(index_desc& idx, BooleanList& matches) const auto iter = optimizer->getConjuncts(outerFlag, innerFlag); BoolExprNodeStack idxConjuncts; - const auto conjunctCount = optimizer->decomposeBoolean(idx.idx_condition_node, idxConjuncts); + const auto conjunctCount = optimizer->decomposeBoolean(idx.idx_code->condition, idxConjuncts); fb_assert(conjunctCount); idx.idx_fraction = MAXIMUM_SELECTIVITY; @@ -944,11 +944,11 @@ bool Retrieval::checkIndexCondition(index_desc& idx, BooleanList& matches) const bool Retrieval::checkIndexExpression(const index_desc* idx, ValueExprNode* node) const { - fb_assert(idx && idx->idx_expression_node); + fb_assert(idx && idx->idx_code->expression); // The desired expression can be hidden inside a derived expression node, // so try to recover it (see CORE-4118). - while (!idx->idx_expression_node->sameAs(node, true)) + while (!idx->idx_code->expression->sameAs(node, true)) { const auto derivedExpr = nodeAs(node); const auto cast = nodeAs(node); @@ -963,7 +963,7 @@ bool Retrieval::checkIndexExpression(const index_desc* idx, ValueExprNode* node) // Check the index for matching both the given stream and the given expression tree - return idx->idx_expression_node->containsStream(0, true) && + return idx->idx_code->expression->containsStream(0, true) && node->containsStream(stream, true); } @@ -1501,7 +1501,7 @@ InversionCandidate* Retrieval::makeInversion(InversionCandidateList& inversions) for (auto otherInversion : inversions) { if (otherInversion->boolean && - idx->idx_condition_node->sameAs(otherInversion->boolean, true)) + idx->idx_code->condition->sameAs(otherInversion->boolean, true)) { otherInversion->used = true; } @@ -1806,7 +1806,7 @@ bool Retrieval::matchBoolean(IndexScratch* indexScratch, { // If index condition matches the boolean, this should not be // considered a match. Full index scan will be used instead. - if (idx->idx_condition_node->sameAs(boolean, true)) + if (idx->idx_code->condition->sameAs(boolean, true)) return false; }