diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp index 3648df1305c..d24e4618e56 100644 --- a/src/dsql/DdlNodes.epp +++ b/src/dsql/DdlNodes.epp @@ -99,6 +99,7 @@ static const UCHAR nonnull_validation_blr[] = blr_eoc }; +static void checkDeferredDdlInReadOnlyReplica(thread_db* tdbb); static void checkForeignKeyTempScope(thread_db* tdbb, jrd_tra* transaction, const QualifiedName& childRelName, const QualifiedName& masterIndexName); static void checkLttNotInUse(thread_db* tdbb, jrd_tra* transaction, const LocalTemporaryTable* ltt); @@ -209,6 +210,16 @@ void ExecInSecurityDb::executeInSecurityDb(jrd_tra* localTransaction) //---------------------- +// Check if we are trying to execute prohibited DDL in read-only replica and raise an error if so. +static void checkDeferredDdlInReadOnlyReplica(thread_db* tdbb) +{ + if (tdbb->getDatabase()->isReplica(REPLICA_READ_ONLY) && + !(tdbb->tdbb_flags & TDBB_replicator)) + { + ERRD_post(Arg::Gds(isc_read_only_trans)); + } +} + // Check temporary table reference rules between given child relation and master // relation (owner of given PK/UK index). static void checkForeignKeyTempScope(thread_db* tdbb, jrd_tra* transaction, @@ -9233,6 +9244,7 @@ void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScrat if (tempFlag == REL_temp_ltt) { + dsqlScratch->flags |= DsqlCompilerScratch::FLAG_ACTUAL_LTT_DDL; defineLocalTempTable(tdbb, dsqlScratch, transaction); dsqlScratch->relation->rel_flags &= ~REL_creating; @@ -9619,6 +9631,7 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc // Handle Local Temporary Tables differently if (relation->rel_flags & REL_ltt_created) { + dsqlScratch->flags |= DsqlCompilerScratch::FLAG_ACTUAL_LTT_DDL; alterLocalTempTable(tdbb, dsqlScratch, transaction); // Update DSQL cache @@ -9627,6 +9640,8 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc return; } + checkDeferredDdlInReadOnlyReplica(tdbb); + bool beforeTriggerWasExecuted = false; const auto executeBeforeTrigger = [&]() @@ -10940,6 +10955,8 @@ void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch if (const auto lttIt = attachment->att_local_temporary_tables.get(name)) { + dsqlScratch->flags |= DsqlCompilerScratch::FLAG_ACTUAL_LTT_DDL; + if (view) { status_exception::raise( @@ -10977,6 +10994,8 @@ void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch if (!rel && silent) return; + checkDeferredDdlInReadOnlyReplica(tdbb); + if (tdbb->getDatabase()->readOnly()) ERRD_post(Arg::Gds(isc_read_only_database)); @@ -13197,11 +13216,14 @@ void CreateIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, // Check if target relation is a Local Temporary Table if (const auto lttPtr = attachment->att_local_temporary_tables.get(relation->dsqlName)) { + dsqlScratch->flags |= DsqlCompilerScratch::FLAG_ACTUAL_LTT_DDL; defineLocalTempIndex(tdbb, dsqlScratch, transaction, *lttPtr); savePoint.release(); // everything is ok return; } + checkDeferredDdlInReadOnlyReplica(tdbb); + if (tdbb->getDatabase()->readOnly()) ERRD_post(Arg::Gds(isc_read_only_database)); @@ -13430,11 +13452,14 @@ void AlterIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, if (MET_get_ltt_index(transaction->getAttachment(), indexName, <t, <tIndex)) { + dsqlScratch->flags |= DsqlCompilerScratch::FLAG_ACTUAL_LTT_DDL; alterLocalTempIndex(tdbb, dsqlScratch, transaction, ltt, lttIndex); savePoint.release(); // everything is ok return; } + checkDeferredDdlInReadOnlyReplica(tdbb); + if (tdbb->getDatabase()->readOnly()) ERRD_post(Arg::Gds(isc_read_only_database)); @@ -13629,11 +13654,14 @@ void SetStatisticsNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc if (MET_get_ltt_index(transaction->getAttachment(), indexName, <t, <tIndex)) { + dsqlScratch->flags |= DsqlCompilerScratch::FLAG_ACTUAL_LTT_DDL; setStatisticsLocalTempIndex(tdbb, dsqlScratch, transaction, ltt, lttIndex); savePoint.release(); // everything is ok return; } + checkDeferredDdlInReadOnlyReplica(tdbb); + AutoCacheRequest request(tdbb, drq_m_set_statistics, DYN_REQUESTS); bool found = false; @@ -13744,11 +13772,14 @@ void DropIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, j if (MET_get_ltt_index(transaction->getAttachment(), indexName, <t, <tIndex)) { + dsqlScratch->flags |= DsqlCompilerScratch::FLAG_ACTUAL_LTT_DDL; dropLocalTempIndex(tdbb, dsqlScratch, transaction, ltt, lttIndex); savePoint.release(); // everything is ok return; } + checkDeferredDdlInReadOnlyReplica(tdbb); + if (tdbb->getDatabase()->readOnly()) ERRD_post(Arg::Gds(isc_read_only_database)); diff --git a/src/dsql/DdlNodes.h b/src/dsql/DdlNodes.h index 389a04abe3d..a2d97633df5 100644 --- a/src/dsql/DdlNodes.h +++ b/src/dsql/DdlNodes.h @@ -237,6 +237,11 @@ class RecreateNode : public DdlNode return createNode->disallowedInReadOnlyDatabase(); } + bool mustBeReplicated() const override + { + return createNode->mustBeReplicated(); + } + protected: void putErrorPrefix(Firebird::Arg::StatusVector& statusVector) override { @@ -1760,6 +1765,11 @@ class CreateRelationNode final : public RelationNode return RelationNode::dsqlPass(dsqlScratch); } + bool mustBeReplicated() const override + { + return tempFlag != REL_temp_ltt; + } + bool disallowedInReadOnlyDatabase() const override { return tempFlag != REL_temp_ltt; @@ -2128,6 +2138,11 @@ class SetStatisticsNode final : public DdlNode return DdlNode::dsqlPass(dsqlScratch); } + bool disallowedInReadOnlyDatabase() const override + { + return false; // Deferred to execute() - LTT status unknown at parse time + } + private: void setStatisticsLocalTempIndex(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction, LocalTemporaryTable* ltt, LocalTemporaryTable::Index* lttIndex); diff --git a/src/dsql/DsqlCompilerScratch.h b/src/dsql/DsqlCompilerScratch.h index ccebe02ba02..bf0c0ac1fd2 100644 --- a/src/dsql/DsqlCompilerScratch.h +++ b/src/dsql/DsqlCompilerScratch.h @@ -78,6 +78,7 @@ class DsqlCompilerScratch : public BlrDebugWriter static const unsigned FLAG_EXEC_BLOCK = 0x010000; static const unsigned FLAG_ALLOW_LTT_REFERENCES = 0x020000; static const unsigned FLAG_USING_STATEMENT = 0x040000; + static const unsigned FLAG_ACTUAL_LTT_DDL = 0x080000; static const unsigned MAX_NESTING = 512; diff --git a/src/dsql/DsqlRequests.cpp b/src/dsql/DsqlRequests.cpp index 00522d975ad..54e13e8064a 100644 --- a/src/dsql/DsqlRequests.cpp +++ b/src/dsql/DsqlRequests.cpp @@ -972,6 +972,7 @@ void DsqlDdlRequest::execute(thread_db* tdbb, jrd_tra** traHandle, try { AutoSetRestoreFlag execDdl(&tdbb->tdbb_flags, TDBB_repl_in_progress, true); + internalScratch->flags &= ~DsqlCompilerScratch::FLAG_ACTUAL_LTT_DDL; //// 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); @@ -981,7 +982,9 @@ void DsqlDdlRequest::execute(thread_db* tdbb, jrd_tra** traHandle, const bool isInternalRequest = (internalScratch->flags & DsqlCompilerScratch::FLAG_INTERNAL_REQUEST); - if (!isInternalRequest && node->mustBeReplicated()) + if (!isInternalRequest && + node->mustBeReplicated() && + !(internalScratch->flags & DsqlCompilerScratch::FLAG_ACTUAL_LTT_DDL)) { REPL_exec_sql(tdbb, req_transaction, getDsqlStatement()->getOrgText(), *getDsqlStatement()->getSchemaSearchPath()); diff --git a/src/dsql/DsqlStatements.cpp b/src/dsql/DsqlStatements.cpp index 34aec832e7b..9521c194bee 100644 --- a/src/dsql/DsqlStatements.cpp +++ b/src/dsql/DsqlStatements.cpp @@ -261,7 +261,8 @@ void DsqlDdlStatement::dsqlPass(thread_db* tdbb, DsqlCompilerScratch* scratch, n // As an exception, not replicated DDL statements are also allowed. if (dbb->isReplica(REPLICA_READ_ONLY) && !(tdbb->tdbb_flags & TDBB_replicator) && - node->mustBeReplicated()) + node->mustBeReplicated() && + node->disallowedInReadOnlyDatabase()) { ERRD_post(Arg::Gds(isc_read_only_trans)); }