diff --git a/doc/sql.extensions/README.ddl.txt b/doc/sql.extensions/README.ddl.txt index 140e2810c2..66edce3532 100644 --- a/doc/sql.extensions/README.ddl.txt +++ b/doc/sql.extensions/README.ddl.txt @@ -675,12 +675,16 @@ already exists. For ALTER TABLE ... ADD subclause, DDL triggers are not fired if there are only IF NOT EXISTS subclauses and all of them are related to existing columns or constraints. -For others commands where IF NOT EXISTS is part of the main command, DDL triggers are not fired when the object -already exists. +For others commands (except users currently) where IF NOT EXISTS is part of the main command, +DDL triggers are not fired when the object already exists. The engine only verifies if the name (object, column or constraint) already exists, and if yes, do nothing. It never tries to match the existing object with the one being created. +Some objects share the same "namespace", for example, there cannot be a table and a procedure with the same name. +In this case, if there is table XYZ and CREATE PROCEDURE IF NOT EXISTS XYZ is tried, the procedure will not be created +and no error will be raised. + The following statements are supported: CREATE EXCEPTION [IF NOT EXISTS] ... diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp index 31bf5bf643..0c2a26992f 100644 --- a/src/dsql/DdlNodes.epp +++ b/src/dsql/DdlNodes.epp @@ -1754,8 +1754,8 @@ void CreateAlterFunctionNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsql status_exception::raise(Arg::Gds(isc_dyn_func_not_found) << Arg::Str(name)); } } - else - executeCreate(tdbb, dsqlScratch, transaction); + else if (!executeCreate(tdbb, dsqlScratch, transaction)) + return; compile(tdbb, dsqlScratch); @@ -1781,7 +1781,7 @@ void CreateAlterFunctionNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsql } } -void CreateAlterFunctionNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, +bool CreateAlterFunctionNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { Attachment* const attachment = transaction->getAttachment(); @@ -1790,7 +1790,7 @@ void CreateAlterFunctionNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch if (package.isEmpty()) { if (createIfNotExistsOnly && !DYN_UTIL_check_unique_name_nothrow(tdbb, transaction, name, obj_udf)) - return; + return false; executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_FUNCTION, name, NULL); @@ -1865,6 +1865,8 @@ void CreateAlterFunctionNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch storePrivileges(tdbb, transaction, name, obj_udf, EXEC_PRIVILEGES); executeAlter(tdbb, dsqlScratch, transaction, false, false); + + return true; } bool CreateAlterFunctionNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, @@ -2772,8 +2774,8 @@ void CreateAlterProcedureNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsq status_exception::raise(Arg::Gds(isc_dyn_proc_not_found) << Arg::Str(name)); } } - else - executeCreate(tdbb, dsqlScratch, transaction); + else if (!executeCreate(tdbb, dsqlScratch, transaction)) + return; compile(tdbb, dsqlScratch); @@ -2799,7 +2801,7 @@ void CreateAlterProcedureNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsq } } -void CreateAlterProcedureNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, +bool CreateAlterProcedureNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { Attachment* const attachment = transaction->getAttachment(); @@ -2808,7 +2810,7 @@ void CreateAlterProcedureNode::executeCreate(thread_db* tdbb, DsqlCompilerScratc if (package.isEmpty()) { if (createIfNotExistsOnly && !DYN_UTIL_check_unique_name_nothrow(tdbb, transaction, name, obj_procedure)) - return; + return false; executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_PROCEDURE, name, NULL); @@ -2875,6 +2877,8 @@ void CreateAlterProcedureNode::executeCreate(thread_db* tdbb, DsqlCompilerScratc storePrivileges(tdbb, transaction, name, obj_procedure, EXEC_PRIVILEGES); executeAlter(tdbb, dsqlScratch, transaction, false, false); + + return true; } bool CreateAlterProcedureNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, @@ -6430,20 +6434,6 @@ void RelationNode::defineField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch dsql_fld* field = clause->field; dsql_rel* relation = dsqlScratch->relation; - if (clause->createIfNotExistsOnly) - { - AutoCacheRequest request(tdbb, drq_l_rel_fld_name, DYN_REQUESTS); - - FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) - RFL IN RDB$RELATION_FIELDS - WITH RFL.RDB$RELATION_NAME = relation->rel_name.c_str() AND - RFL.RDB$FIELD_NAME = field->fld_name.c_str() - { - return; - } - END_FOR - } - // Add the field to the relation being defined for parsing purposes. bool permanent = false; @@ -6646,20 +6636,6 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra { MemoryPool& pool = dsqlScratch->getPool(); - if (clause->createIfNotExistsOnly) - { - AutoCacheRequest request(tdbb, drq_l_rel_con, DYN_REQUESTS); - - FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) - RC IN RDB$RELATION_CONSTRAINTS - WITH RC.RDB$CONSTRAINT_NAME EQ clause->name.c_str() AND - RC.RDB$RELATION_NAME EQ name.c_str() - { - return; - } - END_FOR - } - switch (clause->constraintType) { case AddConstraintClause::CTYPE_NOT_NULL: @@ -7754,10 +7730,33 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc switch ((*i)->type) { case Clause::TYPE_ADD_COLUMN: - executeBeforeTrigger(); - defineField(tdbb, dsqlScratch, transaction, - static_cast(i->getObject()), -1, NULL); + { + const auto addColumnClause = static_cast(i->getObject()); + bool createColumn = true; + + if (addColumnClause->createIfNotExistsOnly) + { + AutoCacheRequest request(tdbb, drq_l_rel_fld_name, DYN_REQUESTS); + + FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) + RFL IN RDB$RELATION_FIELDS + WITH RFL.RDB$RELATION_NAME = relation->rel_name.c_str() AND + RFL.RDB$FIELD_NAME = addColumnClause->field->fld_name.c_str() + { + createColumn = false; + break; + } + END_FOR + } + + if (createColumn) + { + executeBeforeTrigger(); + defineField(tdbb, dsqlScratch, transaction, addColumnClause, -1, nullptr); + } + break; + } case Clause::TYPE_ALTER_COL_TYPE: executeBeforeTrigger(); @@ -7937,16 +7936,46 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc } case Clause::TYPE_ADD_CONSTRAINT: - executeBeforeTrigger(); - makeConstraint(tdbb, dsqlScratch, transaction, - static_cast(i->getObject()), constraints); - break; - case Clause::TYPE_DROP_CONSTRAINT: { - CreateDropConstraint& dropConstraint = constraints.add(); - dropConstraint.name = static_cast(i->getObject())->name; - dropConstraint.silent = static_cast(i->getObject())->silent; + const bool silent = (*i)->type == Clause::TYPE_ADD_CONSTRAINT ? + static_cast(i->getObject())->createIfNotExistsOnly : + static_cast(i->getObject())->silent; + bool found = false; + + if (silent) + { + const auto& constraintName = (*i)->type == Clause::TYPE_ADD_CONSTRAINT ? + static_cast(i->getObject())->name : + static_cast(i->getObject())->name; + + AutoCacheRequest request(tdbb, drq_l_rel_con, DYN_REQUESTS); + + FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) + RC IN RDB$RELATION_CONSTRAINTS + WITH RC.RDB$CONSTRAINT_NAME EQ constraintName.c_str() AND + RC.RDB$RELATION_NAME EQ name.c_str() + { + found = true; + break; + } + END_FOR + } + + if ((*i)->type == Clause::TYPE_ADD_CONSTRAINT && !(silent && found)) + { + executeBeforeTrigger(); + makeConstraint(tdbb, dsqlScratch, transaction, + static_cast(i->getObject()), constraints); + } + else if ((*i)->type == Clause::TYPE_DROP_CONSTRAINT && !(silent && !found)) + { + executeBeforeTrigger(); + CreateDropConstraint& dropConstraint = constraints.add(); + dropConstraint.name = static_cast(i->getObject())->name; + dropConstraint.silent = static_cast(i->getObject())->silent; + } + break; } @@ -8038,15 +8067,11 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc RC.RDB$RELATION_NAME EQ name.c_str() { found = true; - executeBeforeTrigger(); ERASE RC; } END_FOR - if (!constraint->silent && !found) - executeBeforeTrigger(); - - if (!found && !constraint->silent) + if (!found) { // msg 130: "CONSTRAINT %s does not exist." status_exception::raise(Arg::PrivateDyn(130) << constraint->name); diff --git a/src/dsql/DdlNodes.h b/src/dsql/DdlNodes.h index b44bb3e3b9..554a39ca35 100644 --- a/src/dsql/DdlNodes.h +++ b/src/dsql/DdlNodes.h @@ -459,7 +459,7 @@ private: return external && external->udfModule.hasData(); } - void executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction); + bool executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction); bool executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction, bool secondPass, bool runTriggers); bool executeAlterIndividualParameters(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction, @@ -599,7 +599,7 @@ protected: } private: - void executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction); + bool executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction); bool executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction, bool secondPass, bool runTriggers); bool executeAlterIndividualParameters(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction,