diff --git a/doc/sql.extensions/README.ddl.txt b/doc/sql.extensions/README.ddl.txt index a898767c35..140e2810c2 100644 --- a/doc/sql.extensions/README.ddl.txt +++ b/doc/sql.extensions/README.ddl.txt @@ -664,5 +664,43 @@ DROP USER [IF EXISTS] [USING PLUGIN ] DROP PACKAGE [IF EXISTS] DROP PACKAGE BODY [IF EXISTS] DROP [GLOBAL] MAPPING [IF EXISTS] -ALTER TABLE DROP [IF EXISTS] -ALTER TABLE
DROP CONSTRAINT [IF EXISTS] +ALTER TABLE
DROP [IF EXISTS] +ALTER TABLE
DROP CONSTRAINT [IF EXISTS] + +2) CREATE [IF NOT EXISTS] + +Using subclause IF NOT EXISTS, it's now possible to try to create objects and do not get errors when they +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. + +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. + +The following statements are supported: + +CREATE EXCEPTION [IF NOT EXISTS] ... +CREATE INDEX [IF NOT EXISTS] ... +CREATE PROCEDURE [IF NOT EXISTS] ... +CREATE TABLE [IF NOT EXISTS] ... +CREATE TRIGGER [IF NOT EXISTS] ... +CREATE VIEW [IF NOT EXISTS] ... +CREATE FILTER [IF NOT EXISTS] ... +CREATE DOMAIN [IF NOT EXISTS] ... +CREATE FUNCTION [IF NOT EXISTS] ... +DECLARE EXTERNAL FUNCTION [IF NOT EXISTS] ... +CREATE SHADOW [IF NOT EXISTS] ... +CREATE ROLE [IF NOT EXISTS] ... +CREATE GENERATOR [IF NOT EXISTS] ... +CREATE SEQUENCE [IF NOT EXISTS] ... +CREATE COLLATION [IF NOT EXISTS] ... +CREATE USER [IF NOT EXISTS] ... +CREATE PACKAGE [IF NOT EXISTS] ... +CREATE PACKAGE BODY [IF NOT EXISTS] ... +CREATE [GLOBAL] MAPPING [IF NOT EXISTS] ... +ALTER TABLE
ADD [IF NOT EXISTS] ... +ALTER TABLE
ADD CONSTRAINT [IF NOT EXISTS] ... diff --git a/src/common/security.h b/src/common/security.h index fc43cb5d3a..8495efcee2 100644 --- a/src/common/security.h +++ b/src/common/security.h @@ -220,6 +220,7 @@ public: unsigned int op; int trustedAuth; bool silent; + bool createIfNotExistsOnly = false; CharField user, pass, first, last, middle, com, attr; IntField adm, act; CharField database, dba, dbaPassword, role; diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp index e508f97ff1..31bf5bf643 100644 --- a/src/dsql/DdlNodes.epp +++ b/src/dsql/DdlNodes.epp @@ -1789,6 +1789,9 @@ void CreateAlterFunctionNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch if (package.isEmpty()) { + if (createIfNotExistsOnly && !DYN_UTIL_check_unique_name_nothrow(tdbb, transaction, name, obj_udf)) + return; + executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_FUNCTION, name, NULL); @@ -2804,6 +2807,9 @@ void CreateAlterProcedureNode::executeCreate(thread_db* tdbb, DsqlCompilerScratc if (package.isEmpty()) { + if (createIfNotExistsOnly && !DYN_UTIL_check_unique_name_nothrow(tdbb, transaction, name, obj_procedure)) + return; + executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_PROCEDURE, name, NULL); @@ -3724,9 +3730,14 @@ void CreateAlterTriggerNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlS void CreateAlterTriggerNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { + if (createIfNotExistsOnly && !DYN_UTIL_check_unique_name_nothrow(tdbb, transaction, name, obj_trigger)) + return; + executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_TRIGGER, name, NULL); + DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_trigger); + store(tdbb, dsqlScratch, transaction); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_TRIGGER, @@ -4010,9 +4021,14 @@ void CreateCollationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScra // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); + if (createIfNotExistsOnly && !DYN_UTIL_check_unique_name_nothrow(tdbb, transaction, name, obj_collation)) + return; + executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_COLLATION, name, NULL); + DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_collation); + AutoCacheRequest request(tdbb, drq_s_colls, DYN_REQUESTS); STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) @@ -4400,9 +4416,14 @@ void CreateDomainNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); + if (createIfNotExistsOnly && !DYN_UTIL_check_unique_name_nothrow(tdbb, transaction, nameType->name, obj_field)) + return; + executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_DOMAIN, nameType->name, NULL); + DYN_UTIL_check_unique_name(tdbb, transaction, nameType->name, obj_field); + storeGlobalField(tdbb, transaction, nameType->name, type); if (nameType->defaultClause || check || notNull) @@ -5539,6 +5560,9 @@ void CreateAlterExceptionNode::executeCreate(thread_db* tdbb, DsqlCompilerScratc Attachment* const attachment = transaction->getAttachment(); const MetaString& ownerName = attachment->getEffectiveUserName(); + if (createIfNotExistsOnly && !DYN_UTIL_check_unique_name_nothrow(tdbb, transaction, name, obj_exception)) + return; + executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_EXCEPTION, name, NULL); @@ -5766,9 +5790,14 @@ void CreateAlterSequenceNode::putErrorPrefix(Firebird::Arg::StatusVector& status void CreateAlterSequenceNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { + if (createIfNotExistsOnly && !DYN_UTIL_check_unique_name_nothrow(tdbb, transaction, name, obj_generator)) + return; + executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_SEQUENCE, name, NULL); + DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_generator); + const SINT64 val = value.value_or(1); SLONG initialStep = 1; if (step.has_value()) @@ -5777,6 +5806,7 @@ void CreateAlterSequenceNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch if (initialStep == 0) status_exception::raise(Arg::Gds(isc_dyn_cant_use_zero_increment) << Arg::Str(name)); } + store(tdbb, transaction, name, fb_sysflag_user, val, initialStep); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_SEQUENCE, @@ -6398,11 +6428,25 @@ void RelationNode::defineField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch const ObjectsArray* pkCols) { 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; - dsql_rel* relation = dsqlScratch->relation; if (relation != NULL) { if (!(relation->rel_flags & REL_new_relation)) @@ -6596,12 +6640,26 @@ bool RelationNode::defineDefault(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlS } // Make a constraint object from a legacy node. -void RelationNode::makeConstraint(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch, +void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction, AddConstraintClause* clause, ObjectsArray& constraints, bool* notNull) { 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: @@ -7474,6 +7532,9 @@ void CreateRelationNode::checkPermission(thread_db* tdbb, jrd_tra* transaction) void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { + if (createIfNotExistsOnly && !DYN_UTIL_check_unique_name_nothrow(tdbb, transaction, name, obj_relation)) + return; + saveRelation(tdbb, dsqlScratch, name, false, true); if (externalFile) @@ -8790,6 +8851,9 @@ void CreateAlterViewNode::checkPermission(thread_db* tdbb, jrd_tra* transaction) void CreateAlterViewNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { + if (createIfNotExistsOnly && !DYN_UTIL_check_unique_name_nothrow(tdbb, transaction, name, obj_relation)) + return; + Attachment* const attachment = transaction->tra_attachment; const MetaString& ownerName = attachment->getEffectiveUserName(); @@ -9962,6 +10026,9 @@ void CreateIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); + if (createIfNotExistsOnly && !DYN_UTIL_check_unique_name_nothrow(tdbb, transaction, name, obj_index)) + return; + executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_INDEX, name, NULL); @@ -10402,6 +10469,9 @@ void CreateShadowNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScrat FIRST 1 X IN RDB$FILES WITH X.RDB$SHADOW_NUMBER EQ number { + if (createIfNotExistsOnly) + return; + // msg 165: "Shadow %ld already exists" status_exception::raise(Arg::PrivateDyn(165) << Arg::Num(number)); } @@ -10522,6 +10592,10 @@ void CreateAlterRoleNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScra // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); + MetaName dummyName; + + if (createIfNotExistsOnly && isItSqlRole(tdbb, transaction, name, dummyName)) + return; executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, createFlag ? DDL_TRIGGER_CREATE_ROLE : DDL_TRIGGER_ALTER_ROLE, name, NULL); @@ -10544,7 +10618,6 @@ void CreateAlterRoleNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScra status_exception::raise(Arg::PrivateDyn(193) << name); } - MetaName dummyName; if (createFlag && isItSqlRole(tdbb, transaction, name, dummyName)) { // msg 194: "SQL role @1 already exists" @@ -10701,6 +10774,8 @@ void MappingNode::runInSecurityDb(SecDbContext* secDbContext) { case MAP_ADD: ddl = "CREATE MAPPING "; + if (createIfNotExistsOnly) + ddl += "IF NOT EXISTS "; break; case MAP_MOD: ddl = "ALTER MAPPING "; @@ -11014,6 +11089,8 @@ void MappingNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd case MAP_ADD: if (found) { + if (createIfNotExistsOnly) + return; (Arg::Gds(isc_map_already_exists) << name).raise(); } // fall through ... @@ -11261,6 +11338,8 @@ void CreateAlterUserNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScra (Arg::Gds(isc_random) << "Missing user name for ALTER CURRENT USER").raise(); } + userData->createIfNotExistsOnly = createIfNotExistsOnly; + Firebird::LocalStatus s; CheckStatusWrapper statusWrapper(&s); diff --git a/src/dsql/DdlNodes.h b/src/dsql/DdlNodes.h index 8d97ee51d9..b44bb3e3b9 100644 --- a/src/dsql/DdlNodes.h +++ b/src/dsql/DdlNodes.h @@ -475,6 +475,7 @@ public: MetaName name; bool create; bool alter; + bool createIfNotExistsOnly = false; NestConst external; Firebird::TriState deterministic; Firebird::Array > parameters; @@ -614,6 +615,7 @@ public: MetaName name; bool create; bool alter; + bool createIfNotExistsOnly = false; NestConst external; Firebird::Array > parameters; Firebird::Array > returns; @@ -789,6 +791,7 @@ private: public: bool create; bool alter; + bool createIfNotExistsOnly = false; NestConst localDeclList; NestConst body; bool compiled; @@ -888,6 +891,7 @@ public: MetaName fromName; Firebird::string fromExternal; Firebird::UCharBuffer specificAttributes; + bool createIfNotExistsOnly = false; private: USHORT attributesOn; @@ -949,6 +953,7 @@ public: NestConst nameType; bool notNull; NestConst check; + bool createIfNotExistsOnly = false; }; @@ -1068,6 +1073,7 @@ public: Firebird::string message; bool create; bool alter; + bool createIfNotExistsOnly = false; }; @@ -1152,6 +1158,7 @@ private: public: bool create; bool alter; + bool createIfNotExistsOnly = false; bool legacy; bool restartSpecified; const MetaName name; @@ -1380,6 +1387,7 @@ public: Firebird::ObjectsArray refColumns; NestConst refAction; NestConst check; + bool createIfNotExistsOnly = false; }; struct IdentityOptions @@ -1422,6 +1430,7 @@ public: NestConst computed; NestConst identityOptions; bool notNullSpecified; + bool createIfNotExistsOnly = false; }; struct AlterColNameClause : public Clause @@ -1596,6 +1605,7 @@ public: std::optional relationType = rel_persistent; bool preserveRowsOpt; bool deleteRowsOpt; + bool createIfNotExistsOnly = false; }; @@ -1699,6 +1709,7 @@ private: public: bool create; bool alter; + bool createIfNotExistsOnly = false; NestConst viewFields; NestConst selectExpr; Firebird::string source; @@ -1783,6 +1794,7 @@ public: NestConst columns; NestConst computed; NestConst partial; + bool createIfNotExistsOnly = false; }; @@ -1990,6 +2002,7 @@ public: bool manual; bool conditional; Firebird::Array > files; + bool createIfNotExistsOnly = false; }; @@ -2067,7 +2080,9 @@ private: public: MetaName name; - bool createFlag, sysPrivDrop; + bool createFlag; + bool sysPrivDrop; + bool createIfNotExistsOnly = false; void addPrivilege(const MetaName* privName) { @@ -2127,6 +2142,7 @@ public: bool global = false; bool role = false; bool silentDrop = false; + bool createIfNotExistsOnly = false; }; @@ -2226,6 +2242,7 @@ public: Firebird::TriState adminRole; Firebird::TriState active; Mode mode; + bool createIfNotExistsOnly = false; void addProperty(MetaName* pr, Firebird::string* val = NULL) { diff --git a/src/dsql/PackageNodes.epp b/src/dsql/PackageNodes.epp index 38397006c9..c13853ce09 100644 --- a/src/dsql/PackageNodes.epp +++ b/src/dsql/PackageNodes.epp @@ -27,6 +27,7 @@ #include "../jrd/jrd.h" #include "../jrd/tra.h" #include "../jrd/dfw_proto.h" +#include "../jrd/dyn_ut_proto.h" #include "../jrd/exe_proto.h" #include "../jrd/met_proto.h" #include "../jrd/vio_proto.h" @@ -363,9 +364,14 @@ void CreateAlterPackageNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* Attachment* const attachment = transaction->getAttachment(); const MetaString& ownerName = attachment->getEffectiveUserName(); + if (createIfNotExistsOnly && !DYN_UTIL_check_unique_name_nothrow(tdbb, transaction, name, obj_package_header)) + return; + executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_PACKAGE, name, NULL); + DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_package_header); + AutoCacheRequest requestHandle(tdbb, drq_s_pkg, DYN_REQUESTS); STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) @@ -789,6 +795,9 @@ void CreatePackageBodyNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlSc { if (!PKG.RDB$VALID_BODY_FLAG.NULL && PKG.RDB$VALID_BODY_FLAG != 0) { + if (createIfNotExistsOnly) + return; + status_exception::raise( Arg::Gds(isc_no_meta_update) << Arg::Gds(isc_dyn_package_body_exists) << Arg::Str(name)); diff --git a/src/dsql/PackageNodes.h b/src/dsql/PackageNodes.h index e97fa32085..23868292dd 100644 --- a/src/dsql/PackageNodes.h +++ b/src/dsql/PackageNodes.h @@ -107,6 +107,7 @@ public: MetaName name; bool create; bool alter; + bool createIfNotExistsOnly = false; Firebird::string source; Firebird::Array* items; Firebird::SortedArray functionNames; @@ -179,6 +180,7 @@ public: Firebird::string source; Firebird::Array* declaredItems; Firebird::Array* items; + bool createIfNotExistsOnly = false; private: Firebird::string owner; diff --git a/src/dsql/parse-conflicts.txt b/src/dsql/parse-conflicts.txt index bf49686d98..58be4c6e99 100644 --- a/src/dsql/parse-conflicts.txt +++ b/src/dsql/parse-conflicts.txt @@ -1 +1 @@ -94 shift/reduce conflicts, 22 reduce/reduce conflicts. +115 shift/reduce conflicts, 22 reduce/reduce conflicts. diff --git a/src/dsql/parse.y b/src/dsql/parse.y index f0e45d8574..74501908c8 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -822,6 +822,7 @@ using namespace Firebird; Jrd::ValueSourceClause* valueSourceClause; Jrd::RelationNode* relationNode; Jrd::RelationNode::AddColumnClause* addColumnClause; + Jrd::RelationNode::AddConstraintClause* addConstraintClause; Jrd::RelationNode::RefActionClause* refActionClause; Jrd::RelationNode::IndexConstraintClause* indexConstraintClause; Jrd::RelationNode::IdentityOptions* identityOptions; @@ -1406,7 +1407,12 @@ declare %type declare_clause declare_clause : FILTER filter_decl_clause { $$ = $2; } - | EXTERNAL FUNCTION udf_decl_clause { $$ = $3; } + | EXTERNAL FUNCTION if_not_exists_opt udf_decl_clause + { + const auto node = $4; + node->createIfNotExistsOnly = $3; + $$ = node; + } ; %type udf_decl_clause @@ -1534,37 +1540,129 @@ create %type create_clause create_clause - : EXCEPTION exception_clause { $$ = $2; } - | unique_opt order_direction INDEX symbol_index_name ON simple_table_name + : EXCEPTION if_not_exists_opt exception_clause + { + const auto node = $3; + node->createIfNotExistsOnly = $2; + $$ = node; + } + | unique_opt order_direction INDEX if_not_exists_opt symbol_index_name ON simple_table_name { - CreateIndexNode* node = newNode(*$4); + const auto node = newNode(*$5); node->unique = $1; node->descending = $2; - node->relation = $6; + node->createIfNotExistsOnly = $4; + node->relation = $7; $$ = node; } - index_definition(static_cast($7)) + index_definition(static_cast($8)) { - $$ = $7; + $$ = $8; } - | FUNCTION function_clause { $$ = $2; } - | PROCEDURE procedure_clause { $$ = $2; } - | TABLE table_clause { $$ = $2; } - | GLOBAL TEMPORARY TABLE gtt_table_clause { $$ = $4; } - | TRIGGER trigger_clause { $$ = $2; } - | VIEW view_clause { $$ = $2; } - | GENERATOR generator_clause { $$ = $2; } - | SEQUENCE generator_clause { $$ = $2; } + | FUNCTION if_not_exists_opt function_clause + { + const auto node = $3; + node->createIfNotExistsOnly = $2; + $$ = node; + } + | PROCEDURE if_not_exists_opt procedure_clause + { + const auto node = $3; + node->createIfNotExistsOnly = $2; + $$ = node; + } + | TABLE if_not_exists_opt table_clause + { + const auto node = $3; + node->createIfNotExistsOnly = $2; + $$ = node; + } + | GLOBAL TEMPORARY TABLE if_not_exists_opt gtt_table_clause + { + const auto node = $5; + node->createIfNotExistsOnly = $4; + $$ = node; + } + | TRIGGER if_not_exists_opt trigger_clause + { + const auto node = $3; + node->createIfNotExistsOnly = $2; + $$ = node; + } + | VIEW if_not_exists_opt view_clause + { + const auto node = $3; + node->createIfNotExistsOnly = $2; + $$ = node; + } + | GENERATOR if_not_exists_opt generator_clause + { + const auto node = $3; + node->createIfNotExistsOnly = $2; + $$ = node; + } + | SEQUENCE if_not_exists_opt generator_clause + { + const auto node = $3; + node->createIfNotExistsOnly = $2; + $$ = node; + } | DATABASE db_clause { $$ = $2; } - | DOMAIN domain_clause { $$ = $2; } - | SHADOW shadow_clause { $$ = $2; } - | ROLE role_clause { $2->createFlag = true; $$ = $2; } - | COLLATION collation_clause { $$ = $2; } - | USER create_user_clause { $$ = $2; } - | PACKAGE package_clause { $$ = $2; } - | PACKAGE BODY package_body_clause { $$ = $3; } - | MAPPING create_map_clause(false) { $$ = $2; } - | GLOBAL MAPPING create_map_clause(true) { $$ = $3; } + | DOMAIN if_not_exists_opt domain_clause + { + const auto node = $3; + node->createIfNotExistsOnly = $2; + $$ = node; + } + | SHADOW if_not_exists_opt shadow_clause + { + const auto node = $3; + node->createIfNotExistsOnly = $2; + $$ = node; + } + | ROLE if_not_exists_opt role_clause + { + const auto node = $3; + node->createIfNotExistsOnly = $2; + node->createFlag = true; + $$ = node; + } + | COLLATION if_not_exists_opt collation_clause + { + const auto node = $3; + node->createIfNotExistsOnly = $2; + $$ = node; + } + | USER if_not_exists_opt create_user_clause + { + const auto node = $3; + node->createIfNotExistsOnly = $2; + $$ = node; + } + | PACKAGE if_not_exists_opt package_clause + { + const auto node = $3; + node->createIfNotExistsOnly = $2; + $$ = node; + } + | PACKAGE BODY if_not_exists_opt package_body_clause + { + const auto node = $4; + node->createIfNotExistsOnly = $3; + $$ = node; + } + | MAPPING if_not_exists_opt create_map_clause(false) + { + const auto node = $3; + node->createIfNotExistsOnly = $2; + $$ = node; + } + | GLOBAL MAPPING if_not_exists_opt create_map_clause(true) + { + const auto node = $4; + node->createIfNotExistsOnly = $3; + $$ = node; + } ; @@ -2345,7 +2443,7 @@ table_element($createRelationNode) // column definition -%type column_def() +%type column_def() column_def($relationNode) : symbol_column_name data_type_or_domain domain_default_opt { @@ -2359,6 +2457,7 @@ column_def($relationNode) column_constraint_clause(NOTRIAL($4)) collate_clause { setCollate($2, $6); + $$ = $4; } | symbol_column_name data_type_or_domain identity_clause { @@ -2372,6 +2471,7 @@ column_def($relationNode) column_constraint_clause(NOTRIAL($4)) collate_clause { setCollate($2, $6); + $$ = $4; } | symbol_column_name non_array_type def_computed { @@ -2381,6 +2481,7 @@ column_def($relationNode) clause->computed = $3; $relationNode->clauses.add(clause); clause->field->flags |= FLD_computed; + $$ = clause; } | symbol_column_name def_computed { @@ -2390,6 +2491,7 @@ column_def($relationNode) clause->computed = $2; $relationNode->clauses.add(clause); clause->field->flags |= FLD_computed; + $$ = clause; } ; @@ -2577,15 +2679,13 @@ column_constraint($addColumnClause) // table constraints -%type table_constraint_definition() +%type table_constraint_definition() table_constraint_definition($relationNode) : constraint_name_opt table_constraint($relationNode) { if ($1) - { - static_cast( - $relationNode->clauses.back().getObject())->name = *$1; - } + $2->name = *$1; + $$ = $2; } ; @@ -2595,7 +2695,7 @@ constraint_name_opt | CONSTRAINT symbol_constraint_name { $$ = $2; } ; -%type table_constraint() +%type table_constraint() table_constraint($relationNode) : UNIQUE column_parens constraint_index_opt { @@ -2611,6 +2711,7 @@ table_constraint($relationNode) constraint.index = $3; $relationNode->clauses.add(&constraint); + $$ = &constraint; } | PRIMARY KEY column_parens constraint_index_opt { @@ -2626,6 +2727,7 @@ table_constraint($relationNode) constraint.index = $4; $relationNode->clauses.add(&constraint); + $$ = &constraint; } | FOREIGN KEY column_parens REFERENCES symbol_table_name column_parens_opt referential_trigger_action constraint_index_opt @@ -2654,6 +2756,7 @@ table_constraint($relationNode) constraint.index = $8; $relationNode->clauses.add(&constraint); + $$ = &constraint; } | check_constraint { @@ -2661,6 +2764,7 @@ table_constraint($relationNode) constraint->constraintType = RelationNode::AddConstraintClause::CTYPE_CHECK; constraint->check = $1; $relationNode->clauses.add(constraint); + $$ = constraint; } ; @@ -4267,8 +4371,18 @@ alter_op($relationNode) clause->name = *$4; $relationNode->clauses.add(clause); } - | ADD column_def($relationNode) - | ADD table_constraint_definition($relationNode) + | ADD if_not_exists_opt column_def($relationNode) + { + const auto node = $3; + node->createIfNotExistsOnly = $2; + } + | ADD table_constraint($relationNode) + | ADD CONSTRAINT if_not_exists_opt symbol_constraint_name table_constraint($relationNode) + { + const auto node = $5; + node->name = *$4; + node->createIfNotExistsOnly = $3; + } | col_opt alter_column_name POSITION pos_short_integer { RelationNode::AlterColPosClause* clause = newNode(); @@ -4917,6 +5031,12 @@ if_exists_opt | IF EXISTS { $$ = true; } ; +%type if_not_exists_opt +if_not_exists_opt + : /* nothing */ { $$ = false; } + | IF NOT EXISTS { $$ = true; } + ; + %type opt_no_file_delete opt_no_file_delete : /* nothing */ { $$ = false; } diff --git a/src/include/firebird/impl/msg/dyn.h b/src/include/firebird/impl/msg/dyn.h index 058b2176a0..5ebc4ad4e5 100644 --- a/src/include/firebird/impl/msg/dyn.h +++ b/src/include/firebird/impl/msg/dyn.h @@ -299,3 +299,7 @@ FB_IMPL_MSG(DYN, 306, dyn_rel_not_exist, -901, "42", "000", "Table @1 does not e FB_IMPL_MSG(DYN, 307, dyn_exc_not_exist, -901, "42", "000", "Exception @1 does not exist") FB_IMPL_MSG(DYN, 308, dyn_gen_not_exist, -901, "42", "000", "Generator/Sequence @1 does not exist") FB_IMPL_MSG(DYN, 309, dyn_fld_not_exist, -901, "42", "000", "Field @1 of table @2 does not exist") +FB_IMPL_MSG_SYMBOL(DYN, 310, dyn_dup_trigger, "Trigger @1 already exists") +FB_IMPL_MSG_SYMBOL(DYN, 311, dyn_dup_domain, "Domain @1 already exists") +FB_IMPL_MSG_SYMBOL(DYN, 312, dyn_dup_collation, "Collation @1 already exists") +FB_IMPL_MSG_SYMBOL(DYN, 313, dyn_dup_package, "Package @1 already exists") diff --git a/src/jrd/UserManagement.cpp b/src/jrd/UserManagement.cpp index 3b013cae1d..d918fde3de 100644 --- a/src/jrd/UserManagement.cpp +++ b/src/jrd/UserManagement.cpp @@ -482,8 +482,13 @@ void UserManagement::execute(USHORT id) } int errcode = manager->execute(&statusWrapper, command, NULL); - if (!command->silent) + + if (!command->silent && + !(command->createIfNotExistsOnly && + fb_utils::containsErrorCode(status.getErrors(), isc_unique_key_violation))) + { checkSecurityResult(errcode, &status, command->userName()->get(), command->operation()); + } delete commands[id]; commands[id] = NULL; diff --git a/src/jrd/drq.h b/src/jrd/drq.h index 741c5e2cc2..d3b3c5a7fb 100644 --- a/src/jrd/drq.h +++ b/src/jrd/drq.h @@ -249,6 +249,12 @@ enum drq_type_t drq_l_pub_rel_name, // lookup relation by name drq_l_pub_all_rels, // iterate through all user relations drq_e_pub_tab_all, // erase relation from all publication + drq_l_trg_name, // lookup trigger name + drq_l_fld_name, // lookup field name + drq_l_coll_name, // lookup collation name + drq_l_pkg_name, // lookup package name + drq_l_rel_con, // lookup relation constraint + drq_l_rel_fld_name, // lookup relation field name drq_MAX }; diff --git a/src/jrd/dyn_ut_proto.h b/src/jrd/dyn_ut_proto.h index 3b1dce3692..16abba7e43 100644 --- a/src/jrd/dyn_ut_proto.h +++ b/src/jrd/dyn_ut_proto.h @@ -37,6 +37,8 @@ void DYN_UTIL_generate_field_position(Jrd::thread_db*, const Jrd::MetaName&, SLO void DYN_UTIL_generate_field_name(Jrd::thread_db*, TEXT*); void DYN_UTIL_generate_field_name(Jrd::thread_db*, Jrd::MetaName&); void DYN_UTIL_generate_constraint_name(Jrd::thread_db*, Jrd::MetaName&); +bool DYN_UTIL_check_unique_name_nothrow(Jrd::thread_db* tdbb, Jrd::jrd_tra* transaction, + const Jrd::MetaName& object_name, int object_type, USHORT* errorCode = nullptr); void DYN_UTIL_check_unique_name(Jrd::thread_db* tdbb, Jrd::jrd_tra* transaction, const Jrd::MetaName& object_name, int object_type); SINT64 DYN_UTIL_gen_unique_id(Jrd::thread_db*, SSHORT, const char*); diff --git a/src/jrd/dyn_util.epp b/src/jrd/dyn_util.epp index c8d4bfedf8..4aa1d2569e 100644 --- a/src/jrd/dyn_util.epp +++ b/src/jrd/dyn_util.epp @@ -78,23 +78,16 @@ static const UCHAR gen_id_blr2[] = blr_parameter, 0, 0, 0, blr_end, blr_end, blr_end, blr_eoc }; -void DYN_UTIL_check_unique_name(thread_db* tdbb, jrd_tra* transaction, - const MetaName& object_name, int object_type) +// Check if an object already exists. If yes, return false. +bool DYN_UTIL_check_unique_name_nothrow(thread_db* tdbb, jrd_tra* transaction, + const MetaName& object_name, int object_type, USHORT* errorCode) { -/************************************** - * - * D Y N _ U T I L _ c h e c k _ u n i q u e _ n a m e - * - ************************************** - * - * Functional description - * Check if an object already exists. - * If yes then return error. - * - **************************************/ SET_TDBB(tdbb); - USHORT error_code = 0; + USHORT tempErrorCode; + errorCode = errorCode ? errorCode : &tempErrorCode; + *errorCode = 0; + AutoCacheRequest request; switch (object_type) @@ -106,11 +99,11 @@ void DYN_UTIL_check_unique_name(thread_db* tdbb, jrd_tra* transaction, FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) EREL IN RDB$RELATIONS WITH EREL.RDB$RELATION_NAME EQ object_name.c_str() { - error_code = 132; + *errorCode = 132; } END_FOR - if (!error_code) + if (!*errorCode) { request.reset(tdbb, drq_l_prc_name, DYN_REQUESTS); @@ -119,7 +112,7 @@ void DYN_UTIL_check_unique_name(thread_db* tdbb, jrd_tra* transaction, WITH EPRC.RDB$PROCEDURE_NAME EQ object_name.c_str() AND EPRC.RDB$PACKAGE_NAME MISSING { - error_code = 135; + *errorCode = 135; } END_FOR } @@ -131,7 +124,7 @@ void DYN_UTIL_check_unique_name(thread_db* tdbb, jrd_tra* transaction, FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) EIDX IN RDB$INDICES WITH EIDX.RDB$INDEX_NAME EQ object_name.c_str() { - error_code = 251; + *errorCode = 251; } END_FOR @@ -143,7 +136,7 @@ void DYN_UTIL_check_unique_name(thread_db* tdbb, jrd_tra* transaction, FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) EXCP IN RDB$EXCEPTIONS WITH EXCP.RDB$EXCEPTION_NAME EQ object_name.c_str() { - error_code = 253; + *errorCode = 253; } END_FOR @@ -155,7 +148,7 @@ void DYN_UTIL_check_unique_name(thread_db* tdbb, jrd_tra* transaction, FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) EGEN IN RDB$GENERATORS WITH EGEN.RDB$GENERATOR_NAME EQ object_name.c_str() { - error_code = 254; + *errorCode = 254; } END_FOR @@ -169,7 +162,59 @@ void DYN_UTIL_check_unique_name(thread_db* tdbb, jrd_tra* transaction, WITH EFUN.RDB$FUNCTION_NAME EQ object_name.c_str() AND EFUN.RDB$PACKAGE_NAME MISSING { - error_code = 268; + *errorCode = 268; + } + END_FOR + + break; + + case obj_trigger: + request.reset(tdbb, drq_l_trg_name, DYN_REQUESTS); + + FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) + TRG IN RDB$TRIGGERS + WITH TRG.RDB$TRIGGER_NAME EQ object_name.c_str() + { + *errorCode = 310; + } + END_FOR + + break; + + case obj_field: + request.reset(tdbb, drq_l_fld_name, DYN_REQUESTS); + + FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) + FLD IN RDB$FIELDS + WITH FLD.RDB$FIELD_NAME EQ object_name.c_str() + { + *errorCode = 311; + } + END_FOR + + break; + + case obj_collation: + request.reset(tdbb, drq_l_coll_name, DYN_REQUESTS); + + FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) + COLL IN RDB$COLLATIONS + WITH COLL.RDB$COLLATION_NAME EQ object_name.c_str() + { + *errorCode = 312; + } + END_FOR + + break; + + case obj_package_header: + request.reset(tdbb, drq_l_pkg_name, DYN_REQUESTS); + + FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) + PKG IN RDB$PACKAGES + WITH PKG.RDB$PACKAGE_NAME EQ object_name.c_str() + { + *errorCode = 313; } END_FOR @@ -179,8 +224,16 @@ void DYN_UTIL_check_unique_name(thread_db* tdbb, jrd_tra* transaction, fb_assert(false); } - if (error_code) - status_exception::raise(Arg::PrivateDyn(error_code) << object_name.c_str()); + return *errorCode == 0; +} + +// Check if an object already exists. If yes, throw error. +void DYN_UTIL_check_unique_name(thread_db* tdbb, jrd_tra* transaction, const MetaName& object_name, int object_type) +{ + USHORT errorCode; + + if (!DYN_UTIL_check_unique_name_nothrow(tdbb, transaction, object_name, object_type, &errorCode)) + status_exception::raise(Arg::PrivateDyn(errorCode) << object_name.c_str()); }