/* * 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 - refactored from pass1.cpp, ddl.cpp, dyn*.epp */ #include "firebird.h" #include "dyn_consts.h" #include "../dsql/DdlNodes.h" #include "../dsql/BoolNodes.h" #include "../dsql/ExprNodes.h" #include "../dsql/StmtNodes.h" #include "../jrd/blr.h" #include "../jrd/btr.h" #include "../jrd/dyn.h" #include "../jrd/flags.h" #include "../jrd/intl.h" #include "../jrd/jrd.h" #include "../jrd/msg_encode.h" #include "../jrd/obj.h" #include "../jrd/ods.h" #include "../jrd/tra.h" #include "../common/os/path_utils.h" #include "../jrd/IntlManager.h" #include "../jrd/PreparedStatement.h" #include "../jrd/ResultSet.h" #include "../jrd/UserManagement.h" #include "../jrd/blb_proto.h" #include "../jrd/cmp_proto.h" #include "../jrd/dfw_proto.h" #include "../jrd/dpm_proto.h" #include "../jrd/dyn_ut_proto.h" #include "../jrd/exe_proto.h" #include "../jrd/intl_proto.h" #include "../common/isc_f_proto.h" #include "../jrd/met_proto.h" #include "../jrd/scl_proto.h" #include "../jrd/vio_proto.h" #include "../dsql/ddl_proto.h" #include "../dsql/errd_proto.h" #include "../dsql/gen_proto.h" #include "../dsql/make_proto.h" #include "../dsql/metd_proto.h" #include "../dsql/pass1_proto.h" #include "../utilities/gsec/gsec.h" #include "../common/dsc_proto.h" #include "../common/StatusArg.h" #include "../auth/SecureRemotePassword/Message.h" namespace Jrd { using namespace Firebird; static void checkForeignKeyTempScope(thread_db* tdbb, jrd_tra* transaction, const MetaName& childRelName, const MetaName& masterIndexName); static void checkSpTrigDependency(thread_db* tdbb, jrd_tra* transaction, const MetaName& relationName, const MetaName& fieldName); static void checkViewDependency(thread_db* tdbb, jrd_tra* transaction, const MetaName& relationName, const MetaName& fieldName); static void clearPermanentField(dsql_rel* relation, bool permanent); static void defineComputed(DsqlCompilerScratch* dsqlScratch, RelationSourceNode* relation, dsql_fld* field, ValueSourceClause* clause, string& source, BlrDebugWriter::BlrData& value); static void deleteKeyConstraint(thread_db* tdbb, jrd_tra* transaction, const MetaName& relationName, const MetaName& constraintName, const MetaName& indexName); static void defineFile(thread_db* tdbb, jrd_tra* transaction, SLONG shadowNumber, bool manualShadow, bool conditionalShadow, SLONG& dbAlloc, const PathName& name, SLONG start, SLONG length); static bool fieldExists(thread_db* tdbb, jrd_tra* transaction, const MetaName& relationName, const MetaName& fieldName); static bool isItSqlRole(thread_db* tdbb, jrd_tra* transaction, const MetaName& inputName, MetaName& outputName); static void makeRelationScopeName(const MetaName& name, const rel_t type, string& message); static void modifyLocalFieldPosition(thread_db* tdbb, jrd_tra* transaction, const MetaName& relationName, const MetaName& fieldName, USHORT newPosition, USHORT existingPosition); static rel_t relationType(SSHORT relationTypeNull, SSHORT relationType); static void saveField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, const MetaName& fieldName); static void saveRelation(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, const MetaName& relationName, bool view, bool creating); static void updateRdbFields(const TypeClause* type, SSHORT& fieldType, SSHORT& fieldLength, SSHORT& fieldSubTypeNull, SSHORT& fieldSubType, SSHORT& fieldScaleNull, SSHORT& fieldScale, SSHORT& characterSetIdNull, SSHORT& characterSetId, SSHORT& characterLengthNull, SSHORT& characterLength, SSHORT& fieldPrecisionNull, SSHORT& fieldPrecision, SSHORT& collationIdNull, SSHORT& collationId, SSHORT& segmentLengthNull, SSHORT& segmentLength); static const char* const CHECK_CONSTRAINT_EXCEPTION = "check_constraint"; DATABASE DB = STATIC "ODS.RDB"; //---------------------- // 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, const MetaName& childRelName, const MetaName& masterIndexName) { AutoCacheRequest request(tdbb, drq_l_rel_info, DYN_REQUESTS); bool error = false; string master, child; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) RLC_M IN RDB$RELATION_CONSTRAINTS CROSS REL_C IN RDB$RELATIONS CROSS REL_M IN RDB$RELATIONS WITH (RLC_M.RDB$CONSTRAINT_TYPE EQ UNIQUE_CNSTRT OR RLC_M.RDB$CONSTRAINT_TYPE EQ PRIMARY_KEY) AND RLC_M.RDB$INDEX_NAME EQ masterIndexName.c_str() AND REL_C.RDB$RELATION_NAME EQ childRelName.c_str() AND REL_M.RDB$RELATION_NAME EQ RLC_M.RDB$RELATION_NAME { const rel_t masterType = relationType(REL_M.RDB$RELATION_TYPE.NULL, REL_M.RDB$RELATION_TYPE); fb_assert(masterType == rel_persistent || masterType == rel_global_temp_preserve || masterType == rel_global_temp_delete); const rel_t childType = relationType(REL_C.RDB$RELATION_TYPE.NULL, REL_C.RDB$RELATION_TYPE); fb_assert(childType == rel_persistent || childType == rel_global_temp_preserve || childType == rel_global_temp_delete); error = masterType != childType && !(masterType == rel_global_temp_preserve && childType == rel_global_temp_delete); if (error) { makeRelationScopeName(masterIndexName, masterType, master); makeRelationScopeName(childRelName, childType, child); break; } } END_FOR if (error) { // Msg 232 : "%s can't reference %s" status_exception::raise(Arg::PrivateDyn(232) << child << master); } } // Check temporary table reference rules between just created child relation and all // its master relations. static void checkRelationTempScope(thread_db* tdbb, jrd_tra* transaction, const MetaName& childRelName, const rel_t childType) { if (childType != rel_persistent && childType != rel_global_temp_preserve && childType != rel_global_temp_delete) { return; } AutoCacheRequest request(tdbb, drq_l_rel_info2, DYN_REQUESTS); bool error = false; string master, child; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) RLC_C IN RDB$RELATION_CONSTRAINTS CROSS IND_C IN RDB$INDICES CROSS IND_M IN RDB$INDICES CROSS REL_M IN RDB$RELATIONS WITH RLC_C.RDB$CONSTRAINT_TYPE EQ FOREIGN_KEY AND RLC_C.RDB$RELATION_NAME EQ childRelName.c_str() AND IND_C.RDB$INDEX_NAME EQ RLC_C.RDB$INDEX_NAME AND IND_M.RDB$INDEX_NAME EQ IND_C.RDB$FOREIGN_KEY AND IND_M.RDB$RELATION_NAME EQ REL_M.RDB$RELATION_NAME { const rel_t masterType = relationType(REL_M.RDB$RELATION_TYPE.NULL, REL_M.RDB$RELATION_TYPE); fb_assert(masterType == rel_persistent || masterType == rel_global_temp_preserve || masterType == rel_global_temp_delete); error = masterType != childType && !(masterType == rel_global_temp_preserve && childType == rel_global_temp_delete); if (error) { makeRelationScopeName(REL_M.RDB$RELATION_NAME, masterType, master); makeRelationScopeName(childRelName, childType, child); break; } } END_FOR if (error) { // Msg 232 : "%s can't reference %s" status_exception::raise(Arg::PrivateDyn(232) << child << master); } } // Checks to see if the given field is referenced in a stored procedure or trigger. // If the field is referenced, throw. static void checkSpTrigDependency(thread_db* tdbb, jrd_tra* transaction, const MetaName& relationName, const MetaName& fieldName) { AutoRequest request; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FIRST 1 DEP IN RDB$DEPENDENCIES WITH DEP.RDB$DEPENDED_ON_NAME EQ relationName.c_str() AND DEP.RDB$FIELD_NAME EQ fieldName.c_str() { MetaName depName(DEP.RDB$DEPENDENT_NAME); // msg 206: Column %s from table %s is referenced in %s. status_exception::raise( Arg::PrivateDyn(206) << fieldName << relationName << depName); } END_FOR } // Checks to see if the given field is referenced in a view. If it is, throw. static void checkViewDependency(thread_db* tdbb, jrd_tra* transaction, const MetaName& relationName, const MetaName& fieldName) { AutoRequest request; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FIRST 1 X IN RDB$RELATION_FIELDS CROSS Y IN RDB$RELATION_FIELDS CROSS Z IN RDB$VIEW_RELATIONS WITH X.RDB$RELATION_NAME EQ relationName.c_str() AND X.RDB$FIELD_NAME EQ fieldName.c_str() AND X.RDB$FIELD_NAME EQ Y.RDB$BASE_FIELD AND X.RDB$FIELD_SOURCE EQ Y.RDB$FIELD_SOURCE AND Y.RDB$RELATION_NAME EQ Z.RDB$VIEW_NAME AND X.RDB$RELATION_NAME EQ Z.RDB$RELATION_NAME AND Y.RDB$VIEW_CONTEXT EQ Z.RDB$VIEW_CONTEXT { MetaName viewName(Z.RDB$VIEW_NAME); // msg 206: Column %s from table %s is referenced in %s. status_exception::raise( Arg::PrivateDyn(206) << fieldName << relationName << viewName); } END_FOR } // Removes temporary pool pointers from field, stored in permanent cache. static void clearPermanentField(dsql_rel* relation, bool permanent) { if (relation && relation->rel_fields && permanent) { relation->rel_fields->fld_procedure = NULL; relation->rel_fields->ranges = NULL; relation->rel_fields->charSet = NULL; relation->rel_fields->subTypeName = NULL; relation->rel_fields->fld_relation = relation; } } // Define a COMPUTED BY clause, for a field or an index. void defineComputed(DsqlCompilerScratch* dsqlScratch, RelationSourceNode* relation, dsql_fld* field, ValueSourceClause* clause, string& source, BlrDebugWriter::BlrData& value) { // Get the table node and set up correct context. dsqlScratch->resetContextStack(); // Save the size of the field if it is specified. dsc saveDesc; saveDesc.dsc_dtype = 0; if (field && field->dtype) { fb_assert(field->dtype <= MAX_UCHAR); saveDesc.dsc_dtype = (UCHAR) field->dtype; saveDesc.dsc_length = field->length; fb_assert(field->scale <= MAX_SCHAR); saveDesc.dsc_scale = (SCHAR) field->scale; saveDesc.dsc_sub_type = field->subType; field->dtype = 0; field->length = 0; field->scale = 0; field->subType = 0; } PASS1_make_context(dsqlScratch, relation); ValueExprNode* input = Node::doDsqlPass(dsqlScratch, clause->value); // Try to calculate size of the computed field. The calculated size // may be ignored, but it will catch self references. dsc desc; MAKE_desc(dsqlScratch, &desc, input); // Generate the blr expression. dsqlScratch->getBlrData().clear(); dsqlScratch->getDebugData().clear(); dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5); GEN_expr(dsqlScratch, input); dsqlScratch->appendUChar(blr_eoc); if (saveDesc.dsc_dtype) { // Restore the field size/type overrides. field->dtype = saveDesc.dsc_dtype; field->length = saveDesc.dsc_length; field->scale = saveDesc.dsc_scale; if (field->dtype <= dtype_any_text) { field->charSetId = DSC_GET_CHARSET(&saveDesc); field->collationId = DSC_GET_COLLATE(&saveDesc); } else field->subType = saveDesc.dsc_sub_type; } else if (field) { // Use size calculated. field->dtype = desc.dsc_dtype; field->length = desc.dsc_length; field->scale = desc.dsc_scale; if (field->dtype <= dtype_any_text) { field->charSetId = DSC_GET_CHARSET(&desc); field->collationId = DSC_GET_COLLATE(&desc); } else field->subType = desc.dsc_sub_type; } dsqlScratch->resetContextStack(); // Generate the source text. source = clause->source; value.assign(dsqlScratch->getBlrData()); } // Delete a record from RDB$RELATION_CONSTRAINTS based on a constraint name. // // On deleting from RDB$RELATION_CONSTRAINTS, 2 system triggers fire: // // (A) pre delete trigger: pre_delete_constraint, will: // // 1. delete a record first from RDB$REF_CONSTRAINTS where // RDB$REF_CONSTRAINTS.RDB$CONSTRAINT_NAME = // RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME // // (B) post delete trigger: post_delete_constraint will: // // 1. also delete a record from RDB$INDICES where // RDB$INDICES.RDB$INDEX_NAME = // RDB$RELATION_CONSTRAINTS.RDB$INDEX_NAME // // 2. also delete a record from RDB$INDEX_SEGMENTS where // RDB$INDEX_SEGMENTS.RDB$INDEX_NAME = // RDB$RELATION_CONSTRAINTS.RDB$INDEX_NAME static void deleteKeyConstraint(thread_db* tdbb, jrd_tra* transaction, const MetaName& relationName, const MetaName& constraintName, const MetaName& indexName) { SET_TDBB(tdbb); AutoCacheRequest request(tdbb, drq_e_rel_const, DYN_REQUESTS); bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) RC IN RDB$RELATION_CONSTRAINTS WITH RC.RDB$CONSTRAINT_NAME EQ constraintName.c_str() AND RC.RDB$CONSTRAINT_TYPE EQ FOREIGN_KEY AND RC.RDB$RELATION_NAME EQ relationName.c_str() AND RC.RDB$INDEX_NAME EQ indexName.c_str() { found = true; ERASE RC; } END_FOR if (!found) { // msg 130: "CONSTRAINT %s does not exist." status_exception::raise( Arg::PrivateDyn(130) << constraintName); } } // Define a database or shadow file. static void defineFile(thread_db* tdbb, jrd_tra* transaction, SLONG shadowNumber, bool manualShadow, bool conditionalShadow, SLONG& dbAlloc, const PathName& name, SLONG start, SLONG length) { PathName expandedName = name; if (!ISC_expand_filename(expandedName, false)) status_exception::raise(Arg::PrivateDyn(231)); // File name is invalid. if (tdbb->getDatabase()->dbb_filename == expandedName) status_exception::raise(Arg::PrivateDyn(166)); AutoCacheRequest request(tdbb, drq_l_files, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FIRST 1 X IN RDB$FILES WITH X.RDB$FILE_NAME EQ expandedName.c_str() { status_exception::raise(Arg::PrivateDyn(166)); } END_FOR request.reset(tdbb, drq_s_files, DYN_REQUESTS); STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$FILES { expandedName.copyTo(X.RDB$FILE_NAME, sizeof(X.RDB$FILE_NAME)); X.RDB$SHADOW_NUMBER = shadowNumber; X.RDB$FILE_FLAGS = (manualShadow ? FILE_manual : 0) | (conditionalShadow ? FILE_conditional : 0); dbAlloc = MAX(dbAlloc, start); X.RDB$FILE_START = dbAlloc; X.RDB$FILE_LENGTH = length; dbAlloc += length; } END_STORE } // Checks to see if the given field already exists in a relation. static bool fieldExists(thread_db* tdbb, jrd_tra* transaction, const MetaName& relationName, const MetaName& fieldName) { AutoRequest request; bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FLD IN RDB$RELATION_FIELDS WITH FLD.RDB$RELATION_NAME EQ relationName.c_str() AND FLD.RDB$FIELD_NAME EQ fieldName.c_str() { found = true; } END_FOR return found; } // If inputName is found in RDB$ROLES, then returns true. Otherwise returns false. static bool isItSqlRole(thread_db* tdbb, jrd_tra* transaction, const MetaName& inputName, MetaName& outputName) { AutoCacheRequest request(tdbb, drq_get_role_nm, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$ROLES WITH X.RDB$ROLE_NAME EQ inputName.c_str() { outputName = X.RDB$OWNER_NAME; return true; } END_FOR return false; } // Make string with relation name and type of its temporary scope. static void makeRelationScopeName(const MetaName& name, const rel_t type, string& message) { const char* scope = NULL; if (type == rel_global_temp_preserve) scope = REL_SCOPE_GTT_PRESERVE; else if (type == rel_global_temp_delete) scope = REL_SCOPE_GTT_DELETE; else scope = REL_SCOPE_PERSISTENT; message.printf(scope, name.c_str()); } // Alters the position of a field with respect to the // other fields in the relation. This will only affect // the order in which the fields will be returned when either // viewing the relation or performing select * from the relation. // // The rules of engagement are as follows: // if new_position > original position // increase RDB$FIELD_POSITION for all fields with RDB$FIELD_POSITION // between the new_position and existing position of the field // then update the position of the field being altered. // just update the position // // if new_position < original position // decrease RDB$FIELD_POSITION for all fields with RDB$FIELD_POSITION // between the new_position and existing position of the field // then update the position of the field being altered. // // if new_position == original_position -- no_op static void modifyLocalFieldPosition(thread_db* tdbb, jrd_tra* transaction, const MetaName& relationName, const MetaName& fieldName, USHORT newPosition, USHORT existingPosition) { // Make sure that there are no duplicate field positions and no gaps in the position sequence. // (gaps are introduced when fields are removed) AutoRequest request; USHORT newPos = 0; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FLD IN RDB$RELATION_FIELDS WITH FLD.RDB$RELATION_NAME EQ relationName.c_str() SORTED BY ASCENDING FLD.RDB$FIELD_POSITION { if (FLD.RDB$FIELD_POSITION != newPos) { MODIFY FLD USING FLD.RDB$FIELD_POSITION = newPos; END_MODIFY } ++newPos; } END_FOR // Find the position of the last field in the relation. SLONG maxPosition = -1; DYN_UTIL_generate_field_position(tdbb, relationName, &maxPosition); // If the existing position of the field is less than the new position of // the field, subtract 1 to move the fields to their new positions otherwise, // increase the value in RDB$FIELD_POSITION by one. const bool moveDown = (existingPosition < newPosition); // Retrieve the records for the fields which have a position between the // existing field position and the new field position. request.reset(); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FLD IN RDB$RELATION_FIELDS WITH FLD.RDB$RELATION_NAME EQ relationName.c_str() AND FLD.RDB$FIELD_POSITION >= MIN(newPosition, existingPosition) AND FLD.RDB$FIELD_POSITION <= MAX(newPosition, existingPosition) { MODIFY FLD USING // If the field is the one we want, change the position, otherwise // increase the value of RDB$FIELD_POSITION. if (fieldName == FLD.RDB$FIELD_NAME) { if (newPosition > maxPosition) { // This prevents gaps in the position sequence of the fields. FLD.RDB$FIELD_POSITION = maxPosition; } else FLD.RDB$FIELD_POSITION = newPosition; } else { if (moveDown) FLD.RDB$FIELD_POSITION = FLD.RDB$FIELD_POSITION - 1; else FLD.RDB$FIELD_POSITION = FLD.RDB$FIELD_POSITION + 1; } FLD.RDB$FIELD_POSITION.NULL = FALSE; END_MODIFY } END_FOR } // Convert RDB$RELATION_TYPE to rel_t type. static rel_t relationType(SSHORT relationTypeNull, SSHORT relationType) { return relationTypeNull ? rel_persistent : rel_t(relationType); } // Save the name of a field in the relation or view currently being defined. This is done to support // definition of triggers which will depend on the metadata created in this statement. static void saveField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, const MetaName& fieldName) { dsql_rel* relation = dsqlScratch->relation; if (!relation) return; MemoryPool& p = relation->rel_flags & REL_new_relation ? *tdbb->getDefaultPool() : dsqlScratch->getAttachment()->dbb_pool; dsql_fld* field = FB_NEW(p) dsql_fld(p); field->fld_name = fieldName.c_str(); field->fld_next = relation->rel_fields; relation->rel_fields = field; } // Save the name of the relation or view currently being defined. This is done to support definition // of triggers which will depend on the metadata created in this statement. static void saveRelation(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, const MetaName& relationName, bool view, bool creating) { if (dsqlScratch->flags & DsqlCompilerScratch::FLAG_METADATA_SAVED) return; dsqlScratch->flags |= DsqlCompilerScratch::FLAG_METADATA_SAVED; dsql_rel* relation; if (!view && !creating) relation = METD_get_relation(dsqlScratch->getTransaction(), dsqlScratch, relationName); else { MemoryPool& pool = *tdbb->getDefaultPool(); relation = FB_NEW(pool) dsql_rel(pool); relation->rel_name = relationName; if (!view) relation->rel_flags = REL_creating; } dsqlScratch->relation = relation; } // Update RDB$FIELDS received by reference. static void updateRdbFields(const TypeClause* type, SSHORT& fieldType, SSHORT& fieldLength, SSHORT& fieldSubTypeNull, SSHORT& fieldSubType, SSHORT& fieldScaleNull, SSHORT& fieldScale, SSHORT& characterSetIdNull, SSHORT& characterSetId, SSHORT& characterLengthNull, SSHORT& characterLength, SSHORT& fieldPrecisionNull, SSHORT& fieldPrecision, SSHORT& collationIdNull, SSHORT& collationId, SSHORT& segmentLengthNull, SSHORT& segmentLength) { // Initialize all nullable fields. fieldSubTypeNull = fieldScaleNull = characterSetIdNull = characterLengthNull = fieldPrecisionNull = collationIdNull = segmentLengthNull = TRUE; if (type->dtype == dtype_blob) { fieldSubTypeNull = FALSE; fieldSubType = type->subType; fieldScaleNull = FALSE; fieldScale = 0; if (type->subType == isc_blob_text) { characterSetIdNull = FALSE; characterSetId = type->charSetId; collationIdNull = FALSE; collationId = type->collationId; } if (type->segLength != 0) { segmentLengthNull = FALSE; segmentLength = type->segLength; } } else if (type->dtype <= dtype_any_text) { fieldSubTypeNull = FALSE; fieldSubType = type->subType; fieldScaleNull = FALSE; fieldScale = 0; if (type->charLength != 0) { characterLengthNull = FALSE; characterLength = type->charLength; } characterSetIdNull = FALSE; characterSetId = type->charSetId; collationIdNull = FALSE; collationId = type->collationId; } else { fieldScaleNull = FALSE; fieldScale = type->scale; if (DTYPE_IS_EXACT(type->dtype)) { fieldPrecisionNull = FALSE; fieldPrecision = type->precision; fieldSubTypeNull = FALSE; fieldSubType = type->subType; } } if (type->dtype == dtype_varying) { fb_assert(type->length <= MAX_SSHORT); fieldLength = (SSHORT) (type->length - sizeof(USHORT)); } else fieldLength = type->length; fieldType = blr_dtypes[type->dtype]; } //---------------------- // Delete a security class. bool DdlNode::deleteSecurityClass(thread_db* tdbb, jrd_tra* transaction, const MetaName& secClass) { AutoCacheRequest request(tdbb, drq_e_class, DYN_REQUESTS); bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) SC IN RDB$SECURITY_CLASSES WITH SC.RDB$SECURITY_CLASS EQ secClass.c_str() { found = true; ERASE SC; } END_FOR return found; } void DdlNode::storePrivileges(thread_db* tdbb, jrd_tra* transaction, const MetaName& name, int type, const char* privileges) { Attachment* const attachment = transaction->tra_attachment; const string& userName = attachment->att_user->usr_user_name; AutoCacheRequest request(tdbb, drq_s_usr_prvs, DYN_REQUESTS); for (const char* p = privileges; *p; ++p) { STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$USER_PRIVILEGES { strcpy(X.RDB$RELATION_NAME, name.c_str()); strcpy(X.RDB$USER, userName.c_str()); X.RDB$USER_TYPE = obj_user; X.RDB$OBJECT_TYPE = type; X.RDB$PRIVILEGE[0] = *p; X.RDB$PRIVILEGE[1] = 0; X.RDB$GRANT_OPTION = 1; } END_STORE } } void DdlNode::executeDdlTrigger(thread_db* tdbb, jrd_tra* transaction, DdlTriggerWhen when, int action, const MetaName& objectName, const string& sqlText) { Attachment* const attachment = transaction->tra_attachment; // do nothing if user doesn't want database triggers if (attachment->att_flags & ATT_no_db_triggers) return; fb_assert(action > 0); // first element is NULL DdlTriggerContext context; context.eventType = DDL_TRIGGER_ACTION_NAMES[action][0]; context.objectType = DDL_TRIGGER_ACTION_NAMES[action][1]; context.objectName = objectName; context.sqlText = sqlText; Stack::AutoPushPop autoContext(attachment->ddlTriggersContext, context); AutoSavePoint savePoint(tdbb, transaction); EXE_execute_ddl_triggers(tdbb, transaction, when == DTW_BEFORE, action); savePoint.release(); // everything is ok } void DdlNode::executeDdlTrigger(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction, DdlTriggerWhen when, int action, const MetaName& objectName) { executeDdlTrigger(tdbb, transaction, when, action, objectName, *dsqlScratch->getStatement()->getSqlText()); } void DdlNode::storeGlobalField(thread_db* tdbb, jrd_tra* transaction, MetaName& name, const TypeClause* field, const string& computedSource, const BlrDebugWriter::BlrData& computedValue) { Attachment* const attachment = transaction->tra_attachment; const string& userName = attachment->att_user->usr_user_name; const ValueListNode* elements = field->ranges; const USHORT dims = elements ? elements->items.getCount() / 2 : 0; if (dims > MAX_ARRAY_DIMENSIONS) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-604) << Arg::Gds(isc_dsql_max_arr_dim_exceeded)); } if (name.isEmpty()) DYN_UTIL_generate_field_name(tdbb, name); AutoCacheRequest requestHandle(tdbb, drq_s_fld_src, DYN_REQUESTS); STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) FLD IN RDB$FIELDS { FLD.RDB$SYSTEM_FLAG = 0; strcpy(FLD.RDB$FIELD_NAME, name.c_str()); FLD.RDB$OWNER_NAME.NULL = FALSE; strcpy(FLD.RDB$OWNER_NAME, userName.c_str()); FLD.RDB$COMPUTED_SOURCE.NULL = TRUE; FLD.RDB$COMPUTED_BLR.NULL = TRUE; FLD.RDB$DIMENSIONS.NULL = TRUE; updateRdbFields(field, FLD.RDB$FIELD_TYPE, FLD.RDB$FIELD_LENGTH, FLD.RDB$FIELD_SUB_TYPE.NULL, FLD.RDB$FIELD_SUB_TYPE, FLD.RDB$FIELD_SCALE.NULL, FLD.RDB$FIELD_SCALE, FLD.RDB$CHARACTER_SET_ID.NULL, FLD.RDB$CHARACTER_SET_ID, FLD.RDB$CHARACTER_LENGTH.NULL, FLD.RDB$CHARACTER_LENGTH, FLD.RDB$FIELD_PRECISION.NULL, FLD.RDB$FIELD_PRECISION, FLD.RDB$COLLATION_ID.NULL, FLD.RDB$COLLATION_ID, FLD.RDB$SEGMENT_LENGTH.NULL, FLD.RDB$SEGMENT_LENGTH); if (dims != 0) { FLD.RDB$DIMENSIONS.NULL = FALSE; FLD.RDB$DIMENSIONS = dims; } if (computedSource.hasData()) { FLD.RDB$COMPUTED_SOURCE.NULL = FALSE; attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$COMPUTED_SOURCE, computedSource); } if (computedValue.hasData()) { FLD.RDB$COMPUTED_BLR.NULL = FALSE; attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$COMPUTED_BLR, computedValue); } } END_STORE if (elements) // Is the type an array? { requestHandle.reset(tdbb, drq_s_fld_dym, DYN_REQUESTS); SSHORT position = 0; const NestConst* ptr = elements->items.begin(); for (const NestConst* const end = elements->items.end(); ptr != end; ++ptr, ++position) { const ValueExprNode* element = *ptr++; const SLONG lrange = element->as()->getSlong(); element = *ptr; const SLONG hrange = element->as()->getSlong(); if (lrange >= hrange) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-604) << Arg::Gds(isc_dsql_arr_range_error)); } STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) DIM IN RDB$FIELD_DIMENSIONS { strcpy(DIM.RDB$FIELD_NAME, name.c_str()); DIM.RDB$DIMENSION = position; DIM.RDB$UPPER_BOUND = hrange; DIM.RDB$LOWER_BOUND = lrange; } END_STORE } } storePrivileges(tdbb, transaction, name, obj_field, USAGE_PRIVILEGES); } //---------------------- ParameterClause::ParameterClause(MemoryPool& pool, dsql_fld* field, const MetaName& aCollate, ValueSourceClause* aDefaultClause, ValueExprNode* aParameterExpr) : name(pool, field ? field->fld_name : ""), type(field), defaultClause(aDefaultClause), parameterExpr(aParameterExpr) { type->collate = aCollate; } void ParameterClause::print(string& text) const { string s; type->print(s); text.printf("name: '%s' %s", name.c_str(), s.c_str()); } //---------------------- void AlterCharSetNode::print(string& text) const { text.printf( "AlterCharSetNode\n" " charSet: %s\n" " defaultCollation: %s\n", charSet.c_str(), defaultCollation.c_str()); } void AlterCharSetNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { METD_drop_charset(transaction, charSet); MET_dsql_cache_release(tdbb, SYM_intlsym_charset, charSet); bool charSetFound = false; bool collationFound = false; AutoCacheRequest requestHandle1(tdbb, drq_m_charset, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle1 TRANSACTION_HANDLE transaction) CS IN RDB$CHARACTER_SETS WITH CS.RDB$CHARACTER_SET_NAME EQ charSet.c_str() { charSetFound = true; executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_CHARACTER_SET, charSet); AutoCacheRequest requestHandle2(tdbb, drq_l_collation, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle2 TRANSACTION_HANDLE transaction) COLL IN RDB$COLLATIONS WITH COLL.RDB$CHARACTER_SET_ID EQ CS.RDB$CHARACTER_SET_ID AND COLL.RDB$COLLATION_NAME EQ defaultCollation.c_str() { collationFound = true; } END_FOR if (collationFound) { MODIFY CS CS.RDB$DEFAULT_COLLATE_NAME.NULL = FALSE; strcpy(CS.RDB$DEFAULT_COLLATE_NAME, defaultCollation.c_str()); END_MODIFY } } END_FOR if (!charSetFound) status_exception::raise(Arg::Gds(isc_charset_not_found) << Arg::Str(charSet)); if (!collationFound) { status_exception::raise( Arg::Gds(isc_collation_not_found) << Arg::Str(defaultCollation) << Arg::Str(charSet)); } executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_CHARACTER_SET, charSet); } //---------------------- void CommentOnNode::print(string& text) const { text.printf( "CommentOnNode\n" " objType: %s\n" " objName: %s\n" " text: %s\n", objType, objName.c_str(), this->text.c_str()); } // select rdb$relation_name from rdb$relation_fields where rdb$field_name = 'RDB$DESCRIPTION'; // gives the list of objects that accept descriptions. At FB2 time, the only // subobjects with descriptions are relation's fields and procedure's parameters. // In FB3 we added function's arguments. void CommentOnNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { Attachment* const attachment = transaction->tra_attachment; const char* tableClause = NULL; const char* columnClause = NULL; const char* subColumnClause = NULL; const char* addWhereClause = NULL; Arg::StatusVector status; if (objType == obj_parameter) { fb_assert(subName.hasData()); PreparedStatement::Builder sql; sql << "select 1 from rdb$procedures p join rdb$procedure_parameters pp using (rdb$procedure_name)" << "where p.rdb$procedure_name =" << objName << "and p.rdb$package_name is null and pp.rdb$package_name is null" << "and pp.rdb$parameter_name =" << subName << "union all" << "select 2 from rdb$functions f join rdb$function_arguments fa using (rdb$function_name)" << "where f.rdb$function_name =" << objName << "and f.rdb$package_name is null and fa.rdb$package_name is null" << "and fa.rdb$argument_name =" << subName; AutoPreparedStatement ps(attachment->prepareStatement(tdbb, transaction, sql)); AutoResultSet rs(ps->executeQuery(tdbb, transaction)); if (!rs->fetch(tdbb)) { status_exception::raise(Arg::Gds(isc_dyn_routine_param_not_found) << Arg::Str(subName) << Arg::Str(objName)); } fb_assert(!rs->isNull(1)); dsc& desc = rs->getDesc(1); fb_assert(desc.dsc_dtype == dtype_long); const SLONG result = *reinterpret_cast(desc.dsc_address); switch (result) { case 1: objType = obj_procedure; break; case 2: objType = obj_udf; break; default: fb_assert(false); } if (rs->fetch(tdbb)) { status_exception::raise(Arg::Gds(isc_dyn_routine_param_ambiguous) << Arg::Str(subName) << Arg::Str(objName)); } } switch (objType) { case obj_database: tableClause = "rdb$database"; break; case obj_field: tableClause = "rdb$fields"; columnClause = "rdb$field_name"; status << Arg::Gds(isc_dyn_domain_not_found); break; case obj_relation: if (subName.hasData()) { tableClause = "rdb$relation_fields"; subColumnClause = "rdb$field_name"; status << Arg::Gds(isc_dyn_column_does_not_exist) << Arg::Str(subName) << Arg::Str(objName); } else { tableClause = "rdb$relations"; addWhereClause = "rdb$view_blr is null"; status << Arg::Gds(isc_dyn_table_not_found) << Arg::Str(objName); } columnClause = "rdb$relation_name"; break; case obj_view: tableClause = "rdb$relations"; columnClause = "rdb$relation_name"; status << Arg::Gds(isc_dyn_view_not_found) << Arg::Str(objName); addWhereClause = "rdb$view_blr is not null"; break; case obj_procedure: if (subName.hasData()) { tableClause = "rdb$procedure_parameters"; subColumnClause = "rdb$parameter_name"; status << Arg::Gds(isc_dyn_proc_param_not_found) << Arg::Str(subName) << Arg::Str(objName); } else { tableClause = "rdb$procedures"; status << Arg::Gds(isc_dyn_proc_not_found) << Arg::Str(objName); } addWhereClause = "rdb$package_name is null"; columnClause = "rdb$procedure_name"; break; case obj_trigger: tableClause = "rdb$triggers"; columnClause = "rdb$trigger_name"; status << Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(objName); break; case obj_udf: if (subName.hasData()) { tableClause = "rdb$function_arguments"; subColumnClause = "rdb$argument_name"; status << Arg::Gds(isc_dyn_func_param_not_found) << Arg::Str(subName) << Arg::Str(objName); } else { tableClause = "rdb$functions"; status << Arg::Gds(isc_dyn_func_not_found) << Arg::Str(objName); } addWhereClause = "rdb$package_name is null"; columnClause = "rdb$function_name"; break; case obj_blob_filter: tableClause = "rdb$filters"; columnClause = "rdb$function_name"; status << Arg::Gds(isc_dyn_filter_not_found) << Arg::Str(objName); break; case obj_exception: tableClause = "rdb$exceptions"; columnClause = "rdb$exception_name"; status << Arg::Gds(isc_dyn_exception_not_found) << Arg::Str(objName); break; case obj_generator: tableClause = "rdb$generators"; columnClause = "rdb$generator_name"; status << Arg::Gds(isc_dyn_gen_not_found) << Arg::Str(objName); break; case obj_index: tableClause = "rdb$indices"; columnClause = "rdb$index_name"; status << Arg::Gds(isc_dyn_index_not_found) << Arg::Str(objName); break; case obj_sql_role: tableClause = "rdb$roles"; columnClause = "rdb$role_name"; status << Arg::Gds(isc_dyn_role_not_found) << Arg::Str(objName); break; case obj_charset: tableClause = "rdb$character_sets"; columnClause = "rdb$character_set_name"; status << Arg::Gds(isc_dyn_charset_not_found) << Arg::Str(objName); break; case obj_collation: tableClause = "rdb$collations"; columnClause = "rdb$collation_name"; status << Arg::Gds(isc_dyn_collation_not_found) << Arg::Str(objName); break; case obj_package_header: tableClause = "rdb$packages"; columnClause = "rdb$package_name"; status << Arg::Gds(isc_dyn_package_not_found) << Arg::Str(objName); break; case obj_schema: tableClause = "rdb$schemas"; columnClause = "rdb$schema_name"; status << Arg::Gds(isc_dyn_schema_not_found) << Arg::Str(objName); break; default: fb_assert(false); return; } Nullable description; if (text.hasData()) description = text; PreparedStatement::Builder sql; sql << "update" << tableClause << "set rdb$description =" << description; if (columnClause) { sql << "where" << columnClause << "=" << objName; if (subColumnClause) sql << "and" << subColumnClause << "=" << subName; } if (addWhereClause) sql << "and" << addWhereClause; AutoPreparedStatement ps(attachment->prepareStatement(tdbb, transaction, sql)); if (ps->executeUpdate(tdbb, transaction) == 0) status_exception::raise(status); } //---------------------- void CreateAlterFunctionNode::print(string& text) const { text.printf( "CreateAlterFunctionNode\n" " name: '%s' create: %d alter: %d\n", name.c_str(), create, alter); if (external) { string s; s.printf(" external -> name: '%s' engine: '%s'\n", external->name.c_str(), external->engine.c_str()); text += s; } text += " Parameters:\n"; for (size_t i = 0; i < parameters.getCount(); ++i) { const ParameterClause* parameter = parameters[i]; string s; parameter->print(s); text += " " + s + "\n"; } } DdlNode* CreateAlterFunctionNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) { dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_FUNCTION); const CompoundStmtNode* variables = localDeclList; if (variables) { // insure that variable names do not duplicate parameter names SortedArray names; for (size_t i = 0; i < parameters.getCount(); ++i) { ParameterClause* parameter = parameters[i]; names.add(parameter->name); } const NestConst* ptr = variables->statements.begin(); for (const NestConst* const end = variables->statements.end(); ptr != end; ++ptr) { const DeclareVariableNode* varNode = (*ptr)->as(); if (varNode) { const dsql_fld* field = varNode->dsqlDef->type; DEV_BLKCHK(field, dsql_type_fld); if (names.exist(field->fld_name)) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-901) << Arg::Gds(isc_dsql_var_conflict) << Arg::Str(field->fld_name)); } } } } source.ltrim("\n\r\t "); bool hasDefaultParams = false; // compile default expressions for (unsigned i = 0; i < parameters.getCount(); ++i) { ParameterClause* parameter = parameters[i]; if (parameter->defaultClause) { hasDefaultParams = true; parameter->defaultClause->value = doDsqlPass(dsqlScratch, parameter->defaultClause->value); } else if (hasDefaultParams) { // parameter without default value after parameters with default ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_bad_default_value) << Arg::Gds(isc_invalid_clause) << Arg::Str("defaults must be last")); } } for (unsigned i = 0; i < parameters.getCount(); ++i) parameters[i]->type->resolve(dsqlScratch); if (returnType && returnType->type) returnType->type->resolve(dsqlScratch); return DdlNode::dsqlPass(dsqlScratch); } void CreateAlterFunctionNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { fb_assert(create || alter); // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); bool altered = false; // first pass if (alter) { if (executeAlter(tdbb, dsqlScratch, transaction, false, true)) { altered = true; } else { if (create) // create or alter executeCreate(tdbb, dsqlScratch, transaction); else status_exception::raise(Arg::Gds(isc_dyn_func_not_found) << Arg::Str(name)); } } else executeCreate(tdbb, dsqlScratch, transaction); compile(tdbb, dsqlScratch); executeAlter(tdbb, dsqlScratch, transaction, true, false); // second pass if (package.isEmpty()) { executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, (altered ? DDL_TRIGGER_ALTER_FUNCTION : DDL_TRIGGER_CREATE_FUNCTION), name); } savePoint.release(); // everything is ok if (alter) { // Update DSQL cache METD_drop_function(transaction, QualifiedName(name, package)); MET_dsql_cache_release(tdbb, SYM_udf, name, package); } } void CreateAlterFunctionNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { Attachment* const attachment = transaction->getAttachment(); const string& userName = attachment->att_user->usr_user_name; if (package.isEmpty()) { executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_FUNCTION, name); DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_udf); } AutoCacheRequest requestHandle(tdbb, drq_s_funcs2, DYN_REQUESTS); int faults = 0; while (true) { try { SINT64 id = DYN_UTIL_gen_unique_id(tdbb, drq_g_nxt_fun_id, "RDB$FUNCTIONS"); id %= (MAX_SSHORT + 1); if (!id) continue; STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) FUN IN RDB$FUNCTIONS { FUN.RDB$FUNCTION_ID = id; FUN.RDB$SYSTEM_FLAG = 0; strcpy(FUN.RDB$FUNCTION_NAME, name.c_str()); if (package.hasData()) { FUN.RDB$PACKAGE_NAME.NULL = FALSE; strcpy(FUN.RDB$PACKAGE_NAME, package.c_str()); FUN.RDB$PRIVATE_FLAG.NULL = FALSE; FUN.RDB$PRIVATE_FLAG = privateScope; FUN.RDB$OWNER_NAME.NULL = FALSE; strcpy(FUN.RDB$OWNER_NAME, packageOwner.c_str()); } else { FUN.RDB$PACKAGE_NAME.NULL = TRUE; FUN.RDB$PRIVATE_FLAG.NULL = TRUE; FUN.RDB$OWNER_NAME.NULL = FALSE; strcpy(FUN.RDB$OWNER_NAME, userName.c_str()); } FUN.RDB$LEGACY_FLAG.NULL = FALSE; FUN.RDB$LEGACY_FLAG = isUdf() ? TRUE : FALSE; FUN.RDB$RETURN_ARGUMENT.NULL = FALSE; FUN.RDB$RETURN_ARGUMENT = 0; } END_STORE break; } catch (const status_exception& ex) { if (ex.value()[1] != isc_unique_key_violation) throw; if (++faults > MAX_SSHORT) throw; fb_utils::init_status(tdbb->tdbb_status_vector); } } if (package.isEmpty()) storePrivileges(tdbb, transaction, name, obj_udf, EXEC_PRIVILEGES); executeAlter(tdbb, dsqlScratch, transaction, false, false); } bool CreateAlterFunctionNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction, bool secondPass, bool runTriggers) { Attachment* const attachment = transaction->getAttachment(); bool modified = false; unsigned returnPos = 0; AutoCacheRequest requestHandle(tdbb, drq_m_funcs2, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) FUN IN RDB$FUNCTIONS WITH FUN.RDB$FUNCTION_NAME EQ name.c_str() AND FUN.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '') { if (!FUN.RDB$SYSTEM_FLAG.NULL && FUN.RDB$SYSTEM_FLAG) { status_exception::raise( Arg::Gds(isc_dyn_cannot_mod_sysfunc) << FUN.RDB$FUNCTION_NAME); } if (!secondPass && runTriggers && package.isEmpty()) { executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_FUNCTION, name); } MODIFY FUN if (secondPass) { FUN.RDB$FUNCTION_BLR.NULL = TRUE; FUN.RDB$DEBUG_INFO.NULL = TRUE; } else { FUN.RDB$ENGINE_NAME.NULL = TRUE; FUN.RDB$MODULE_NAME.NULL = TRUE; FUN.RDB$ENTRYPOINT.NULL = TRUE; FUN.RDB$VALID_BLR.NULL = TRUE; FUN.RDB$FUNCTION_SOURCE.NULL = !(source.hasData() && (external || package.isEmpty())); if (!FUN.RDB$FUNCTION_SOURCE.NULL) attachment->storeMetaDataBlob(tdbb, transaction, &FUN.RDB$FUNCTION_SOURCE, source); FUN.RDB$DETERMINISTIC_FLAG.NULL = FALSE; FUN.RDB$DETERMINISTIC_FLAG = deterministic ? TRUE : FALSE; if (isUdf()) { dsql_fld* field = returnType ? returnType->type : NULL; if (field) { // CVC: This is case of "returns [by value|reference]". // Some data types can not be returned as value. if (returnType->udfMechanism.value == FUN_value && (field->dtype == dtype_text || field->dtype == dtype_varying || field->dtype == dtype_cstring || field->dtype == dtype_blob || field->dtype == dtype_timestamp)) { // Return mode by value not allowed for this data type. status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_return_mode_err)); } // For functions returning a blob, coerce return argument position to // be the last parameter. if (field->dtype == dtype_blob) { returnPos = unsigned(parameters.getCount()) + 1; if (returnPos > MAX_UDF_ARGUMENTS) { // External functions can not have more than 10 parameters // Or 9 if the function returns a BLOB. status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_extern_func_err)); } FUN.RDB$RETURN_ARGUMENT = (SSHORT) returnPos; } else FUN.RDB$RETURN_ARGUMENT = 0; } else // CVC: This is case of "returns parameter " { // Function modifies an argument whose value is the function return value. if (udfReturnPos < 1 || ULONG(udfReturnPos) > parameters.getCount()) { // CVC: We should devise new msg "position should be between 1 and #params"; // here it is: dsql_udf_return_pos_err // External functions can not have more than 10 parameters // Not strictly correct -- return position error status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << // gds__extern_func_err Arg::Gds(isc_dsql_udf_return_pos_err) << Arg::Num(parameters.getCount())); } // We'll verify that SCALAR_ARRAY can't be used as a return type. // The support for SCALAR_ARRAY is only for input parameters. if (parameters[udfReturnPos - 1]->udfMechanism.specified && parameters[udfReturnPos - 1]->udfMechanism.value == FUN_scalar_array) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_random) << "BY SCALAR_ARRAY can't be used as a return parameter"); } FUN.RDB$RETURN_ARGUMENT = (SSHORT) udfReturnPos; } } } if (external) { if (!secondPass) { FUN.RDB$ENGINE_NAME.NULL = (SSHORT) external->engine.isEmpty(); external->engine.copyTo(FUN.RDB$ENGINE_NAME, sizeof(FUN.RDB$ENGINE_NAME)); if (external->udfModule.length() >= sizeof(FUN.RDB$MODULE_NAME)) status_exception::raise(Arg::Gds(isc_dyn_name_longer)); FUN.RDB$MODULE_NAME.NULL = (SSHORT) external->udfModule.isEmpty(); external->udfModule.copyTo(FUN.RDB$MODULE_NAME, sizeof(FUN.RDB$MODULE_NAME)); if (external->name.length() >= sizeof(FUN.RDB$ENTRYPOINT)) status_exception::raise(Arg::Gds(isc_dyn_name_longer)); FUN.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty(); external->name.copyTo(FUN.RDB$ENTRYPOINT, sizeof(FUN.RDB$ENTRYPOINT)); } } else if (body) { if (secondPass) { FUN.RDB$VALID_BLR.NULL = FALSE; FUN.RDB$VALID_BLR = TRUE; FUN.RDB$FUNCTION_BLR.NULL = FALSE; attachment->storeBinaryBlob(tdbb, transaction, &FUN.RDB$FUNCTION_BLR, dsqlScratch->getBlrData()); FUN.RDB$DEBUG_INFO.NULL = FALSE; attachment->storeBinaryBlob(tdbb, transaction, &FUN.RDB$DEBUG_INFO, dsqlScratch->getDebugData()); } } if (package.hasData()) { FUN.RDB$PRIVATE_FLAG.NULL = FALSE; FUN.RDB$PRIVATE_FLAG = privateScope; } else FUN.RDB$PRIVATE_FLAG.NULL = TRUE; modified = true; END_MODIFY } END_FOR if (!secondPass && modified) { // Get all comments from the old parameter list. MetaNameBidMap comments; collectParamComments(tdbb, transaction, comments); // delete all old arguments DropFunctionNode::dropArguments(tdbb, transaction, name, package); // and insert the new ones if (returnType && returnType->type) storeArgument(tdbb, dsqlScratch, transaction, returnPos, true, returnType, NULL); for (size_t i = 0; i < parameters.getCount(); ++i) { ParameterClause* parameter = parameters[i]; bid comment; // Find the original comment to recreate in the new parameter. if (!comments.get(parameter->name, comment)) comment.clear(); storeArgument(tdbb, dsqlScratch, transaction, i + 1, false, parameter, &comment); } } return modified; } void CreateAlterFunctionNode::storeArgument(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction, unsigned pos, bool returnArg, ParameterClause* parameter, const bid* comment) { Attachment* const attachment = transaction->getAttachment(); TypeClause* type = parameter->type; AutoCacheRequest requestHandle(tdbb, drq_s_func_args2, DYN_REQUESTS); STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) ARG IN RDB$FUNCTION_ARGUMENTS { ARG.RDB$FUNCTION_NAME.NULL = FALSE; strcpy(ARG.RDB$FUNCTION_NAME, name.c_str()); ARG.RDB$ARGUMENT_NAME.NULL = (SSHORT) parameter->name.isEmpty(); strcpy(ARG.RDB$ARGUMENT_NAME, parameter->name.c_str()); ARG.RDB$PACKAGE_NAME.NULL = (SSHORT) package.isEmpty(); strcpy(ARG.RDB$PACKAGE_NAME, package.c_str()); ARG.RDB$SYSTEM_FLAG = 0; ARG.RDB$SYSTEM_FLAG.NULL = FALSE; ARG.RDB$ARGUMENT_POSITION.NULL = FALSE; ARG.RDB$ARGUMENT_POSITION = pos; ARG.RDB$NULL_FLAG.NULL = TRUE; ARG.RDB$RELATION_NAME.NULL = TRUE; ARG.RDB$FIELD_NAME.NULL = TRUE; ARG.RDB$FIELD_SOURCE.NULL = TRUE; ARG.RDB$DEFAULT_VALUE.NULL = TRUE; ARG.RDB$DEFAULT_SOURCE.NULL = TRUE; ARG.RDB$MECHANISM.NULL = TRUE; ARG.RDB$FIELD_TYPE.NULL = TRUE; ARG.RDB$FIELD_LENGTH.NULL = TRUE; ARG.RDB$FIELD_PRECISION.NULL = TRUE; ARG.RDB$FIELD_SCALE.NULL = TRUE; ARG.RDB$CHARACTER_SET_ID.NULL = TRUE; ARG.RDB$COLLATION_ID.NULL = TRUE; ARG.RDB$ARGUMENT_MECHANISM.NULL = TRUE; if (!isUdf()) { ARG.RDB$ARGUMENT_MECHANISM.NULL = FALSE; ARG.RDB$ARGUMENT_MECHANISM = (USHORT) (type->fullDomain || type->typeOfName.isEmpty() ? prm_mech_normal : prm_mech_type_of); } if (type->notNull) { ARG.RDB$NULL_FLAG.NULL = FALSE; ARG.RDB$NULL_FLAG = TRUE; } if (type->typeOfTable.isEmpty()) { if (type->typeOfName.hasData()) { ARG.RDB$FIELD_SOURCE.NULL = FALSE; strcpy(ARG.RDB$FIELD_SOURCE, type->typeOfName.c_str()); } else { if (isUdf()) { SSHORT segmentLength, segmentLengthNull; ARG.RDB$FIELD_TYPE.NULL = FALSE; ARG.RDB$FIELD_LENGTH.NULL = FALSE; updateRdbFields(type, ARG.RDB$FIELD_TYPE, ARG.RDB$FIELD_LENGTH, ARG.RDB$FIELD_SUB_TYPE.NULL, ARG.RDB$FIELD_SUB_TYPE, ARG.RDB$FIELD_SCALE.NULL, ARG.RDB$FIELD_SCALE, ARG.RDB$CHARACTER_SET_ID.NULL, ARG.RDB$CHARACTER_SET_ID, ARG.RDB$CHARACTER_LENGTH.NULL, ARG.RDB$CHARACTER_LENGTH, ARG.RDB$FIELD_PRECISION.NULL, ARG.RDB$FIELD_PRECISION, ARG.RDB$COLLATION_ID.NULL, ARG.RDB$COLLATION_ID, segmentLengthNull, segmentLength); } else { MetaName fieldName; storeGlobalField(tdbb, transaction, fieldName, type); ARG.RDB$FIELD_SOURCE.NULL = FALSE; strcpy(ARG.RDB$FIELD_SOURCE, fieldName.c_str()); } } } else { ARG.RDB$RELATION_NAME.NULL = FALSE; strcpy(ARG.RDB$RELATION_NAME, type->typeOfTable.c_str()); ARG.RDB$FIELD_NAME.NULL = FALSE; strcpy(ARG.RDB$FIELD_NAME, type->typeOfName.c_str()); ARG.RDB$FIELD_SOURCE.NULL = FALSE; strcpy(ARG.RDB$FIELD_SOURCE, type->fieldSource.c_str()); } // ASF: If we used a collate with a domain or table.column type, write it // into RDB$FUNCTION_ARGUMENTS. if (type->collate.hasData() && type->typeOfName.hasData()) { ARG.RDB$COLLATION_ID.NULL = FALSE; ARG.RDB$COLLATION_ID = type->collationId; } // ASF: I moved this block to write defaults on RDB$FUNCTION_ARGUMENTS. // It was writing in RDB$FIELDS, but that would require special support // for packaged functions signature verification. if (parameter->defaultClause) { ARG.RDB$DEFAULT_VALUE.NULL = FALSE; ARG.RDB$DEFAULT_SOURCE.NULL = FALSE; attachment->storeMetaDataBlob(tdbb, transaction, &ARG.RDB$DEFAULT_SOURCE, parameter->defaultClause->source); dsqlScratch->getBlrData().clear(); if (dsqlScratch->isVersion4()) dsqlScratch->appendUChar(blr_version4); else dsqlScratch->appendUChar(blr_version5); GEN_expr(dsqlScratch, parameter->defaultClause->value); dsqlScratch->appendUChar(blr_eoc); attachment->storeBinaryBlob(tdbb, transaction, &ARG.RDB$DEFAULT_VALUE, dsqlScratch->getBlrData()); } if (isUdf()) { ARG.RDB$MECHANISM.NULL = FALSE; if (returnArg && // CVC: This is case of "returns [by value|reference]" udfReturnPos == 0 && type->dtype == dtype_blob) { // CVC: I need to test returning blobs by descriptor before allowing the // change there. For now, I ignore the return type specification. const bool freeIt = parameter->udfMechanism.value < 0; ARG.RDB$MECHANISM = (SSHORT) ((freeIt ? -1 : 1) * FUN_blob_struct); // If we have the FREE_IT set then the blob has to be freed on return. } else if (parameter->udfMechanism.specified) ARG.RDB$MECHANISM = (SSHORT) parameter->udfMechanism.value; else if (type->dtype == dtype_blob) ARG.RDB$MECHANISM = (SSHORT) FUN_blob_struct; else ARG.RDB$MECHANISM = (SSHORT) FUN_reference; } ARG.RDB$DESCRIPTION.NULL = !comment || comment->isEmpty(); if (!ARG.RDB$DESCRIPTION.NULL) ARG.RDB$DESCRIPTION = *comment; } END_STORE } void CreateAlterFunctionNode::compile(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch) { if (invalid) status_exception::raise(Arg::Gds(isc_dyn_invalid_ddl_func) << name); if (compiled) return; compiled = true; invalid = true; if (body) { dsqlScratch->beginDebug(); dsqlScratch->getBlrData().clear(); if (dsqlScratch->isVersion4()) dsqlScratch->appendUChar(blr_version4); else dsqlScratch->appendUChar(blr_version5); dsqlScratch->appendUChar(blr_begin); Array > returns; returns.add(returnType); dsqlScratch->genParameters(parameters, returns); if (parameters.getCount() != 0) { dsqlScratch->appendUChar(blr_receive); dsqlScratch->appendUChar(0); } dsqlScratch->appendUChar(blr_begin); for (unsigned i = 0; i < parameters.getCount(); ++i) { ParameterClause* parameter = parameters[i]; if (parameter->type->fullDomain || parameter->type->notNull) { // ASF: To validate input parameters we need only to read its value. // Assigning it to null is an easy way to do this. dsqlScratch->appendUChar(blr_assignment); dsqlScratch->appendUChar(blr_parameter2); dsqlScratch->appendUChar(0); // input dsqlScratch->appendUShort(i * 2); dsqlScratch->appendUShort(i * 2 + 1); dsqlScratch->appendUChar(blr_null); } } dsql_var* const variable = dsqlScratch->outputVariables[0]; dsqlScratch->putLocalVariable(variable, 0, NULL); // ASF: This is here to not change the old logic (proc_flag) // of previous calls to PASS1_node and PASS1_statement. dsqlScratch->setPsql(true); dsqlScratch->putLocalVariables(localDeclList, 1); dsqlScratch->loopLevel = 0; dsqlScratch->cursorNumber = 0; StmtNode* stmtNode = body->dsqlPass(dsqlScratch); GEN_hidden_variables(dsqlScratch); dsqlScratch->appendUChar(blr_stall); // put a label before body of procedure, // so that any EXIT statement can get out dsqlScratch->appendUChar(blr_label); dsqlScratch->appendUChar(0); stmtNode->genBlr(dsqlScratch); dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_DDL); dsqlScratch->appendUChar(blr_end); dsqlScratch->genReturn(false); dsqlScratch->appendUChar(blr_end); dsqlScratch->appendUChar(blr_eoc); dsqlScratch->endDebug(); } invalid = false; } void CreateAlterFunctionNode::collectParamComments(thread_db* tdbb, jrd_tra* transaction, MetaNameBidMap& items) { AutoRequest requestHandle; FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) ARG IN RDB$FUNCTION_ARGUMENTS WITH ARG.RDB$FUNCTION_NAME EQ name.c_str() AND ARG.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '') AND ARG.RDB$DESCRIPTION NOT MISSING { items.put(ARG.RDB$ARGUMENT_NAME, ARG.RDB$DESCRIPTION); } END_FOR } //---------------------- void AlterExternalFunctionNode::print(string& text) const { text.printf( "AlterExternalFunctionNode\n" " name: '%s'\n" " entrypoint: '%s'\n" " module: '%s'\n", name.c_str(), clauses.name.c_str(), clauses.udfModule.c_str()); } // Allow changing the entry point and/or the module name of a UDF. void AlterExternalFunctionNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { if (clauses.name.isEmpty() && clauses.udfModule.isEmpty()) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-104) /*** FIXME: << // Unexpected end of command Arg::Gds(isc_command_end_err2) << Arg::Num(node->nod_line) << Arg::Num(node->nod_column + obj_name->str_length)***/); // + strlen("FUNCTION") } // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); AutoCacheRequest request(tdbb, drq_m_fun, DYN_REQUESTS); bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FUN IN RDB$FUNCTIONS WITH FUN.RDB$FUNCTION_NAME EQ name.c_str() AND FUN.RDB$PACKAGE_NAME MISSING { found = true; executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_FUNCTION, name); if (!FUN.RDB$ENGINE_NAME.NULL || !FUN.RDB$FUNCTION_BLR.NULL) status_exception::raise(Arg::Gds(isc_dyn_newfc_oldsyntax) << name); MODIFY FUN if (clauses.name.hasData()) { if (clauses.name.length() >= sizeof(FUN.RDB$ENTRYPOINT)) status_exception::raise(Arg::Gds(isc_dyn_name_longer)); FUN.RDB$ENTRYPOINT.NULL = FALSE; strcpy(FUN.RDB$ENTRYPOINT, clauses.name.c_str()); } if (clauses.udfModule.hasData()) { if (clauses.udfModule.length() >= sizeof(FUN.RDB$MODULE_NAME)) status_exception::raise(Arg::Gds(isc_dyn_name_longer)); FUN.RDB$MODULE_NAME.NULL = FALSE; strcpy(FUN.RDB$MODULE_NAME, clauses.udfModule.c_str()); } END_MODIFY } END_FOR if (found) executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_FUNCTION, name); else { // msg 41: "Function %s not found" status_exception::raise(Arg::PrivateDyn(41) << name); } savePoint.release(); // everything is ok // Update DSQL cache METD_drop_function(transaction, QualifiedName(name, "")); MET_dsql_cache_release(tdbb, SYM_udf, name, ""); } //---------------------- void DropFunctionNode::dropArguments(thread_db* tdbb, jrd_tra* transaction, const MetaName& functionName, const MetaName& packageName) { AutoCacheRequest requestHandle(tdbb, drq_e_func_args, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) ARG IN RDB$FUNCTION_ARGUMENTS WITH ARG.RDB$FUNCTION_NAME EQ functionName.c_str() AND ARG.RDB$PACKAGE_NAME EQUIV NULLIF(packageName.c_str(), '') { // get rid of arguments in rdb$fields if (!ARG.RDB$FIELD_SOURCE.NULL && ARG.RDB$RELATION_NAME.NULL && ARG.RDB$FIELD_NAME.NULL) { AutoCacheRequest requestHandle2(tdbb, drq_e_arg_gfld, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle2 TRANSACTION_HANDLE transaction) FLD IN RDB$FIELDS WITH FLD.RDB$FIELD_NAME EQ ARG.RDB$FIELD_SOURCE AND FLD.RDB$FIELD_NAME STARTING WITH IMPLICIT_DOMAIN_PREFIX AND (FLD.RDB$SYSTEM_FLAG EQ 0 OR FLD.RDB$SYSTEM_FLAG MISSING) { ERASE FLD; } END_FOR } ERASE ARG; } END_FOR } void DropFunctionNode::print(string& text) const { text.printf( "DropFunctionNode\n" " name: '%s'\n", name.c_str()); } DdlNode* DropFunctionNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) { dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_FUNCTION); return DdlNode::dsqlPass(dsqlScratch); } void DropFunctionNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); bool found = false; dropArguments(tdbb, transaction, name, package); AutoCacheRequest requestHandle(tdbb, drq_e_funcs, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) FUN IN RDB$FUNCTIONS WITH FUN.RDB$FUNCTION_NAME EQ name.c_str() AND FUN.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '') { if (!FUN.RDB$SYSTEM_FLAG.NULL && FUN.RDB$SYSTEM_FLAG) status_exception::raise(Arg::Gds(isc_dyn_cannot_mod_sysfunc) << FUN.RDB$FUNCTION_NAME); if (package.isEmpty()) executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_FUNCTION, name); ERASE FUN; if (!FUN.RDB$SECURITY_CLASS.NULL) deleteSecurityClass(tdbb, transaction, FUN.RDB$SECURITY_CLASS); found = true; } END_FOR if (!found && !silent) status_exception::raise(Arg::Gds(isc_dyn_func_not_found) << Arg::Str(name)); if (package.isEmpty()) { requestHandle.reset(tdbb, drq_e_fun_prvs, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND PRIV.RDB$OBJECT_TYPE = obj_udf { ERASE PRIV; } END_FOR requestHandle.reset(tdbb, drq_e_fun_prv, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$USER EQ name.c_str() AND PRIV.RDB$USER_TYPE = obj_udf { ERASE PRIV; } END_FOR } if (found && package.isEmpty()) executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_FUNCTION, name); savePoint.release(); // everything is ok // Update DSQL cache METD_drop_function(transaction, QualifiedName(name, package)); MET_dsql_cache_release(tdbb, SYM_udf, name, package); } //---------------------- void CreateAlterProcedureNode::print(string& text) const { text.printf( "CreateAlterProcedureNode\n" " name: '%s' create: %d alter: %d\n", name.c_str(), create, alter); if (external) { string s; s.printf(" external -> name: '%s' engine: '%s'\n", external->name.c_str(), external->engine.c_str()); text += s; } text += " Parameters:\n"; for (size_t i = 0; i < parameters.getCount(); ++i) { const ParameterClause* parameter = parameters[i]; string s; parameter->print(s); text += " " + s + "\n"; } text += " Returns:\n"; for (size_t i = 0; i < returns.getCount(); ++i) { const ParameterClause* parameter = returns[i]; string s; parameter->print(s); text += " " + s + "\n"; } } DdlNode* CreateAlterProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) { dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_PROCEDURE); const CompoundStmtNode* variables = localDeclList; if (variables) { // insure that variable names do not duplicate parameter names SortedArray names; for (size_t i = 0; i < parameters.getCount(); ++i) { ParameterClause* parameter = parameters[i]; names.add(parameter->name); } for (size_t i = 0; i < returns.getCount(); ++i) { ParameterClause* parameter = returns[i]; names.add(parameter->name); } const NestConst* ptr = variables->statements.begin(); for (const NestConst* const end = variables->statements.end(); ptr != end; ++ptr) { const DeclareVariableNode* varNode = (*ptr)->as(); if (varNode) { const dsql_fld* field = varNode->dsqlDef->type; DEV_BLKCHK(field, dsql_type_fld); if (names.exist(field->fld_name)) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-901) << Arg::Gds(isc_dsql_var_conflict) << Arg::Str(field->fld_name)); } } } } source.ltrim("\n\r\t "); bool hasDefaultParams = false; // compile default expressions for (unsigned i = 0; i < parameters.getCount(); ++i) { ParameterClause* parameter = parameters[i]; if (parameter->defaultClause) { hasDefaultParams = true; parameter->defaultClause->value = doDsqlPass(dsqlScratch, parameter->defaultClause->value); } else if (hasDefaultParams) { // parameter without default value after parameters with default ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_bad_default_value) << Arg::Gds(isc_invalid_clause) << Arg::Str("defaults must be last")); } } for (unsigned i = 0; i < parameters.getCount(); ++i) parameters[i]->type->resolve(dsqlScratch); for (unsigned i = 0; i < returns.getCount(); ++i) returns[i]->type->resolve(dsqlScratch); return DdlNode::dsqlPass(dsqlScratch); } void CreateAlterProcedureNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { fb_assert(create || alter); // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); bool altered = false; // first pass if (alter) { if (executeAlter(tdbb, dsqlScratch, transaction, false, true)) altered = true; else { if (create) // create or alter executeCreate(tdbb, dsqlScratch, transaction); else status_exception::raise(Arg::Gds(isc_dyn_proc_not_found) << Arg::Str(name)); } } else executeCreate(tdbb, dsqlScratch, transaction); compile(tdbb, dsqlScratch); executeAlter(tdbb, dsqlScratch, transaction, true, false); // second pass if (package.isEmpty()) { executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, (altered ? DDL_TRIGGER_ALTER_PROCEDURE : DDL_TRIGGER_CREATE_PROCEDURE), name); } savePoint.release(); // everything is ok if (alter) { // Update DSQL cache METD_drop_procedure(transaction, QualifiedName(name, package)); MET_dsql_cache_release(tdbb, SYM_procedure, name, package); } } void CreateAlterProcedureNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { Attachment* const attachment = transaction->getAttachment(); const string& userName = attachment->att_user->usr_user_name; if (package.isEmpty()) { executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_PROCEDURE, name); DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_procedure); } AutoCacheRequest requestHandle(tdbb, drq_s_prcs2, DYN_REQUESTS); int faults = 0; while (true) { try { SINT64 id = DYN_UTIL_gen_unique_id(tdbb, drq_g_nxt_prc_id, "RDB$PROCEDURES"); id %= (MAX_SSHORT + 1); if (!id) continue; STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) P IN RDB$PROCEDURES { P.RDB$PROCEDURE_ID = id; P.RDB$SYSTEM_FLAG = 0; strcpy(P.RDB$PROCEDURE_NAME, name.c_str()); if (package.hasData()) { P.RDB$PACKAGE_NAME.NULL = FALSE; strcpy(P.RDB$PACKAGE_NAME, package.c_str()); P.RDB$PRIVATE_FLAG.NULL = FALSE; P.RDB$PRIVATE_FLAG = privateScope; strcpy(P.RDB$OWNER_NAME, packageOwner.c_str()); } else { P.RDB$PACKAGE_NAME.NULL = TRUE; P.RDB$PRIVATE_FLAG.NULL = TRUE; strcpy(P.RDB$OWNER_NAME, userName.c_str()); } } END_STORE break; } catch (const status_exception& ex) { if (ex.value()[1] != isc_unique_key_violation) throw; if (++faults > MAX_SSHORT) throw; fb_utils::init_status(tdbb->tdbb_status_vector); } } if (package.isEmpty()) storePrivileges(tdbb, transaction, name, obj_procedure, EXEC_PRIVILEGES); executeAlter(tdbb, dsqlScratch, transaction, false, false); } bool CreateAlterProcedureNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction, bool secondPass, bool runTriggers) { Attachment* const attachment = transaction->getAttachment(); AutoCacheRequest requestHandle(tdbb, drq_m_prcs2, DYN_REQUESTS); bool modified = false; DsqlCompiledStatement* statement = dsqlScratch->getStatement(); FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) P IN RDB$PROCEDURES WITH P.RDB$PROCEDURE_NAME EQ name.c_str() AND P.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '') { if (!P.RDB$SYSTEM_FLAG.NULL && P.RDB$SYSTEM_FLAG) status_exception::raise(Arg::Gds(isc_dyn_cannot_mod_sysproc) << P.RDB$PROCEDURE_NAME); if (!secondPass && runTriggers && package.isEmpty()) { executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_PROCEDURE, name); } MODIFY P if (secondPass) { P.RDB$PROCEDURE_BLR.NULL = TRUE; P.RDB$DEBUG_INFO.NULL = TRUE; P.RDB$PROCEDURE_TYPE.NULL = TRUE; P.RDB$PROCEDURE_INPUTS = (USHORT) parameters.getCount(); P.RDB$PROCEDURE_OUTPUTS = (USHORT) returns.getCount(); } else { P.RDB$ENGINE_NAME.NULL = TRUE; P.RDB$ENTRYPOINT.NULL = TRUE; P.RDB$PROCEDURE_SOURCE.NULL = TRUE; P.RDB$VALID_BLR.NULL = TRUE; P.RDB$PROCEDURE_SOURCE.NULL = !(source.hasData() && (external || package.isEmpty())); if (!P.RDB$PROCEDURE_SOURCE.NULL) attachment->storeMetaDataBlob(tdbb, transaction, &P.RDB$PROCEDURE_SOURCE, source); if (package.hasData()) { P.RDB$PRIVATE_FLAG.NULL = FALSE; P.RDB$PRIVATE_FLAG = privateScope; } else P.RDB$PRIVATE_FLAG.NULL = TRUE; } if (external) { if (secondPass) { P.RDB$PROCEDURE_TYPE.NULL = FALSE; P.RDB$PROCEDURE_TYPE = (USHORT) prc_selectable; } else { P.RDB$ENGINE_NAME.NULL = FALSE; strcpy(P.RDB$ENGINE_NAME, external->engine.c_str()); if (external->name.length() >= sizeof(P.RDB$ENTRYPOINT)) status_exception::raise(Arg::Gds(isc_dyn_name_longer)); P.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty(); strcpy(P.RDB$ENTRYPOINT, external->name.c_str()); } } else if (body) { if (secondPass) { P.RDB$VALID_BLR.NULL = FALSE; P.RDB$VALID_BLR = TRUE; P.RDB$PROCEDURE_BLR.NULL = FALSE; attachment->storeBinaryBlob(tdbb, transaction, &P.RDB$PROCEDURE_BLR, dsqlScratch->getBlrData()); P.RDB$DEBUG_INFO.NULL = FALSE; attachment->storeBinaryBlob(tdbb, transaction, &P.RDB$DEBUG_INFO, dsqlScratch->getDebugData()); P.RDB$PROCEDURE_TYPE.NULL = FALSE; P.RDB$PROCEDURE_TYPE = (USHORT) (statement->getFlags() & DsqlCompiledStatement::FLAG_SELECTABLE ? prc_selectable : prc_executable); } } modified = true; END_MODIFY } END_FOR if (!secondPass && modified) { // Get all comments from the old parameter list. MetaNameBidMap comments; collectParamComments(tdbb, transaction, comments); // Delete all old input and output parameters. DropProcedureNode::dropParameters(tdbb, transaction, name, package); // And insert the new ones. for (size_t i = 0; i < parameters.getCount(); ++i) { ParameterClause* parameter = parameters[i]; bid comment; // Find the original comment to recreate in the new parameter. if (!comments.get(parameter->name, comment)) comment.clear(); storeParameter(tdbb, dsqlScratch, transaction, 0, i, parameter, &comment); } for (size_t i = 0; i < returns.getCount(); ++i) { ParameterClause* parameter = returns[i]; bid comment; // Find the original comment to recreate in the new parameter. if (!comments.get(parameter->name, comment)) comment.clear(); storeParameter(tdbb, dsqlScratch, transaction, 1, i, parameter, &comment); } AutoCacheRequest requestHandle2(tdbb, drq_m_prm_view, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle2 TRANSACTION_HANDLE transaction) PRM IN RDB$PROCEDURE_PARAMETERS CROSS RFR IN RDB$RELATION_FIELDS CROSS VRL IN RDB$VIEW_RELATIONS WITH PRM.RDB$PROCEDURE_NAME EQ name.c_str() AND PRM.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '') AND VRL.RDB$RELATION_NAME EQ PRM.RDB$PROCEDURE_NAME AND VRL.RDB$PACKAGE_NAME EQUIV PRM.RDB$PACKAGE_NAME AND VRL.RDB$CONTEXT_TYPE EQ VCT_PROCEDURE AND RFR.RDB$RELATION_NAME EQ VRL.RDB$VIEW_NAME AND RFR.RDB$VIEW_CONTEXT EQ VRL.RDB$VIEW_CONTEXT AND RFR.RDB$BASE_FIELD = PRM.RDB$PARAMETER_NAME { MODIFY RFR { strcpy(RFR.RDB$FIELD_SOURCE, PRM.RDB$FIELD_SOURCE); } END_MODIFY } END_FOR } return modified; } void CreateAlterProcedureNode::storeParameter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction, USHORT parameterType, unsigned pos, ParameterClause* parameter, const bid* comment) { Attachment* const attachment = transaction->getAttachment(); TypeClause* type = parameter->type; AutoCacheRequest requestHandle(tdbb, drq_s_prms4, DYN_REQUESTS); STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) PRM IN RDB$PROCEDURE_PARAMETERS { PRM.RDB$PARAMETER_NAME.NULL = FALSE; strcpy(PRM.RDB$PARAMETER_NAME, parameter->name.c_str()); PRM.RDB$PROCEDURE_NAME.NULL = FALSE; strcpy(PRM.RDB$PROCEDURE_NAME, name.c_str()); if (package.hasData()) { PRM.RDB$PACKAGE_NAME.NULL = FALSE; strcpy(PRM.RDB$PACKAGE_NAME, package.c_str()); } else PRM.RDB$PACKAGE_NAME.NULL = TRUE; PRM.RDB$SYSTEM_FLAG = 0; PRM.RDB$SYSTEM_FLAG.NULL = FALSE; PRM.RDB$PARAMETER_NUMBER.NULL = FALSE; PRM.RDB$PARAMETER_NUMBER = pos; PRM.RDB$PARAMETER_TYPE.NULL = FALSE; PRM.RDB$PARAMETER_TYPE = parameterType; PRM.RDB$PARAMETER_MECHANISM.NULL = FALSE; PRM.RDB$PARAMETER_MECHANISM = (USHORT) (type->fullDomain || type->typeOfName.isEmpty() ? prm_mech_normal : prm_mech_type_of); PRM.RDB$NULL_FLAG.NULL = !type->notNull; PRM.RDB$NULL_FLAG = type->notNull; PRM.RDB$RELATION_NAME.NULL = type->typeOfTable.isEmpty(); PRM.RDB$FIELD_NAME.NULL = PRM.RDB$RELATION_NAME.NULL || type->typeOfName.isEmpty(); PRM.RDB$FIELD_SOURCE.NULL = FALSE; if (PRM.RDB$RELATION_NAME.NULL) { if (type->typeOfName.hasData()) strcpy(PRM.RDB$FIELD_SOURCE, type->typeOfName.c_str()); else { MetaName fieldName; storeGlobalField(tdbb, transaction, fieldName, type); strcpy(PRM.RDB$FIELD_SOURCE, fieldName.c_str()); } } else { strcpy(PRM.RDB$RELATION_NAME, type->typeOfTable.c_str()); strcpy(PRM.RDB$FIELD_NAME, type->typeOfName.c_str()); strcpy(PRM.RDB$FIELD_SOURCE, type->fieldSource.c_str()); } // ASF: If we used a collate with a domain or table.column type, write it // in RDB$PROCEDURE_PARAMETERS. PRM.RDB$COLLATION_ID.NULL = !(type->collate.hasData() && type->typeOfName.hasData()); if (!PRM.RDB$COLLATION_ID.NULL) PRM.RDB$COLLATION_ID = type->collationId; // ASF: I moved this block to write defaults on RDB$PROCEDURE_PARAMETERS. // It was writing in RDB$FIELDS, but that would require special support // for packaged procedures signature verification. PRM.RDB$DEFAULT_VALUE.NULL = !parameter->defaultClause; PRM.RDB$DEFAULT_SOURCE.NULL = !parameter->defaultClause; if (parameter->defaultClause) { attachment->storeMetaDataBlob(tdbb, transaction, &PRM.RDB$DEFAULT_SOURCE, parameter->defaultClause->source); dsqlScratch->getBlrData().clear(); if (dsqlScratch->isVersion4()) dsqlScratch->appendUChar(blr_version4); else dsqlScratch->appendUChar(blr_version5); GEN_expr(dsqlScratch, parameter->defaultClause->value); dsqlScratch->appendUChar(blr_eoc); attachment->storeBinaryBlob(tdbb, transaction, &PRM.RDB$DEFAULT_VALUE, dsqlScratch->getBlrData()); } PRM.RDB$DESCRIPTION.NULL = !comment || comment->isEmpty(); if (!PRM.RDB$DESCRIPTION.NULL) PRM.RDB$DESCRIPTION = *comment; } END_STORE } void CreateAlterProcedureNode::compile(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch) { if (invalid) status_exception::raise(Arg::Gds(isc_dyn_invalid_ddl_proc) << name); if (compiled) return; compiled = true; if (!body) return; invalid = true; dsqlScratch->beginDebug(); dsqlScratch->getBlrData().clear(); if (dsqlScratch->isVersion4()) dsqlScratch->appendUChar(blr_version4); else dsqlScratch->appendUChar(blr_version5); dsqlScratch->appendUChar(blr_begin); dsqlScratch->genParameters(parameters, returns); if (parameters.getCount() != 0) { dsqlScratch->appendUChar(blr_receive); dsqlScratch->appendUChar(0); } dsqlScratch->appendUChar(blr_begin); for (unsigned i = 0; i < parameters.getCount(); ++i) { ParameterClause* parameter = parameters[i]; if (parameter->type->fullDomain || parameter->type->notNull) { // ASF: To validate an input parameter we need only to read its value. // Assigning it to null is an easy way to do this. dsqlScratch->appendUChar(blr_assignment); dsqlScratch->appendUChar(blr_parameter2); dsqlScratch->appendUChar(0); // input dsqlScratch->appendUShort(i * 2); dsqlScratch->appendUShort(i * 2 + 1); dsqlScratch->appendUChar(blr_null); } } for (Array::const_iterator i = dsqlScratch->outputVariables.begin(); i != dsqlScratch->outputVariables.end(); ++i) { dsqlScratch->putLocalVariable(*i, 0, NULL); } // ASF: This is here to not change the old logic (proc_flag) // of previous calls to PASS1_node and PASS1_statement. dsqlScratch->setPsql(true); dsqlScratch->putLocalVariables(localDeclList, returns.getCount()); dsqlScratch->loopLevel = 0; dsqlScratch->cursorNumber = 0; StmtNode* stmtNode = body->dsqlPass(dsqlScratch); GEN_hidden_variables(dsqlScratch); dsqlScratch->appendUChar(blr_stall); // put a label before body of procedure, // so that any EXIT statement can get out dsqlScratch->appendUChar(blr_label); dsqlScratch->appendUChar(0); stmtNode->genBlr(dsqlScratch); dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_DDL); dsqlScratch->appendUChar(blr_end); dsqlScratch->genReturn(true); dsqlScratch->appendUChar(blr_end); dsqlScratch->appendUChar(blr_eoc); dsqlScratch->endDebug(); invalid = false; } void CreateAlterProcedureNode::collectParamComments(thread_db* tdbb, jrd_tra* transaction, MetaNameBidMap& items) { AutoRequest requestHandle; FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) PRM IN RDB$PROCEDURE_PARAMETERS WITH PRM.RDB$PROCEDURE_NAME EQ name.c_str() AND PRM.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '') AND PRM.RDB$DESCRIPTION NOT MISSING { items.put(PRM.RDB$PARAMETER_NAME, PRM.RDB$DESCRIPTION); } END_FOR } //---------------------- void DropProcedureNode::dropParameters(thread_db* tdbb, jrd_tra* transaction, const MetaName& procedureName, const MetaName& packageName) { AutoCacheRequest requestHandle(tdbb, drq_e_prms2, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) PRM IN RDB$PROCEDURE_PARAMETERS WITH PRM.RDB$PROCEDURE_NAME EQ procedureName.c_str() AND PRM.RDB$PACKAGE_NAME EQUIV NULLIF(packageName.c_str(), '') { // get rid of parameters in rdb$fields if (!PRM.RDB$FIELD_SOURCE.NULL && PRM.RDB$RELATION_NAME.NULL && PRM.RDB$FIELD_NAME.NULL) { AutoCacheRequest requestHandle2(tdbb, drq_e_prm_gfld, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle2 TRANSACTION_HANDLE transaction) FLD IN RDB$FIELDS WITH FLD.RDB$FIELD_NAME EQ PRM.RDB$FIELD_SOURCE AND FLD.RDB$FIELD_NAME STARTING WITH IMPLICIT_DOMAIN_PREFIX AND (FLD.RDB$SYSTEM_FLAG EQ 0 OR FLD.RDB$SYSTEM_FLAG MISSING) { ERASE FLD; } END_FOR } ERASE PRM; } END_FOR } void DropProcedureNode::print(string& text) const { text.printf( "DropProcedureNode\n" " name: '%s'\n", name.c_str()); } DdlNode* DropProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) { dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_PROCEDURE); return DdlNode::dsqlPass(dsqlScratch); } void DropProcedureNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); bool found = false; dropParameters(tdbb, transaction, name, package); AutoCacheRequest requestHandle(tdbb, drq_e_prcs2, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) PRC IN RDB$PROCEDURES WITH PRC.RDB$PROCEDURE_NAME EQ name.c_str() AND PRC.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '') { if (!PRC.RDB$SYSTEM_FLAG.NULL && PRC.RDB$SYSTEM_FLAG) status_exception::raise(Arg::Gds(isc_dyn_cannot_mod_sysproc) << PRC.RDB$PROCEDURE_NAME); if (package.isEmpty()) { executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_PROCEDURE, name); } ERASE PRC; if (!PRC.RDB$SECURITY_CLASS.NULL) deleteSecurityClass(tdbb, transaction, PRC.RDB$SECURITY_CLASS); found = true; } END_FOR if (!found && !silent) status_exception::raise(Arg::Gds(isc_dyn_proc_not_found) << Arg::Str(name)); if (package.isEmpty()) { requestHandle.reset(tdbb, drq_e_prc_prvs, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND PRIV.RDB$OBJECT_TYPE = obj_procedure { ERASE PRIV; } END_FOR requestHandle.reset(tdbb, drq_e_prc_prv, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$USER EQ name.c_str() AND PRIV.RDB$USER_TYPE = obj_procedure { ERASE PRIV; } END_FOR } if (found && package.isEmpty()) executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_PROCEDURE, name); savePoint.release(); // everything is ok // Update DSQL cache METD_drop_procedure(transaction, QualifiedName(name, package)); MET_dsql_cache_release(tdbb, SYM_procedure, name, package); } //---------------------- void TriggerDefinition::store(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { if (name.isEmpty()) DYN_UTIL_generate_trigger_name(tdbb, transaction, name); AutoCacheRequest requestHandle(tdbb, drq_s_triggers, DYN_REQUESTS); STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) TRG IN RDB$TRIGGERS { TRG.RDB$SYSTEM_FLAG = SSHORT(systemFlag); TRG.RDB$FLAGS = TRG_sql | (fkTrigger ? TRG_ignore_perm : 0); strcpy(TRG.RDB$TRIGGER_NAME, name.c_str()); TRG.RDB$RELATION_NAME.NULL = relationName.isEmpty(); strcpy(TRG.RDB$RELATION_NAME, relationName.c_str()); fb_assert(type.specified); TRG.RDB$TRIGGER_TYPE = type.value; TRG.RDB$TRIGGER_SEQUENCE = (!position.specified ? 0 : position.value); TRG.RDB$TRIGGER_INACTIVE = (!active.specified ? 0 : (USHORT) !active.value); } END_STORE modify(tdbb, dsqlScratch, transaction); } bool TriggerDefinition::modify(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { Attachment* const attachment = transaction->getAttachment(); bool modified = false; // ASF: Unregistered bug (2.0, 2.1, 2.5, 3.0): CREATE OR ALTER TRIGGER accepts different table // than one used in already created trigger. AutoCacheRequest requestHandle(tdbb, drq_m_trigger2, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) TRG IN RDB$TRIGGERS WITH TRG.RDB$TRIGGER_NAME EQ name.c_str() { if (type.specified && type.value != (FB_UINT64) TRG.RDB$TRIGGER_TYPE && TRG.RDB$RELATION_NAME.NULL) { status_exception::raise( Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_db_trigger_type_cant_change)); } if (systemFlag == fb_sysflag_user && !TRG.RDB$SYSTEM_FLAG.NULL) { switch (TRG.RDB$SYSTEM_FLAG) { case fb_sysflag_check_constraint: case fb_sysflag_referential_constraint: case fb_sysflag_view_check: status_exception::raise(Arg::Gds(isc_dyn_cant_modify_auto_trig)); break; case fb_sysflag_system: status_exception::raise( Arg::Gds(isc_dyn_cannot_mod_systrig) << TRG.RDB$TRIGGER_NAME); break; default: break; } } preModify(tdbb, dsqlScratch, transaction); MODIFY TRG if (blrData.length > 0 || external) { fb_assert(!(blrData.length > 0 && external)); TRG.RDB$ENGINE_NAME.NULL = TRUE; TRG.RDB$ENTRYPOINT.NULL = TRUE; TRG.RDB$TRIGGER_SOURCE.NULL = TRUE; TRG.RDB$TRIGGER_BLR.NULL = TRUE; TRG.RDB$DEBUG_INFO.NULL = TRUE; TRG.RDB$VALID_BLR.NULL = TRUE; } if (type.specified) TRG.RDB$TRIGGER_TYPE = type.value; if (position.specified) TRG.RDB$TRIGGER_SEQUENCE = position.value; if (active.specified) TRG.RDB$TRIGGER_INACTIVE = (USHORT) !active.value; if (external) { TRG.RDB$ENGINE_NAME.NULL = FALSE; strcpy(TRG.RDB$ENGINE_NAME, external->engine.c_str()); if (external->name.length() >= sizeof(TRG.RDB$ENTRYPOINT)) status_exception::raise(Arg::Gds(isc_dyn_name_longer)); TRG.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty(); strcpy(TRG.RDB$ENTRYPOINT, external->name.c_str()); } else if (blrData.length > 0) { TRG.RDB$VALID_BLR.NULL = FALSE; TRG.RDB$VALID_BLR = TRUE; TRG.RDB$TRIGGER_BLR.NULL = FALSE; attachment->storeBinaryBlob(tdbb, transaction, &TRG.RDB$TRIGGER_BLR, blrData); } if (debugData.length > 0) { TRG.RDB$DEBUG_INFO.NULL = FALSE; attachment->storeBinaryBlob(tdbb, transaction, &TRG.RDB$DEBUG_INFO, debugData); } if (source.hasData()) { TRG.RDB$TRIGGER_SOURCE.NULL = FALSE; attachment->storeMetaDataBlob(tdbb, transaction, &TRG.RDB$TRIGGER_SOURCE, source); } modified = true; END_MODIFY } END_FOR if (modified) postModify(tdbb, dsqlScratch, transaction); return modified; } //---------------------- void CreateAlterTriggerNode::print(string& text) const { text.printf( "CreateAlterTriggerNode\n" " name: '%s' create: %d alter: %d relationName: '%s'\n" " type: %d, %d active: %d, %d position: %d, %d\n", name.c_str(), create, alter, relationName.c_str(), type.specified, type.value, active.specified, active.value, position.specified, position.value); if (external) { string s; s.printf(" external -> name: '%s' engine: '%s'\n", external->name.c_str(), external->engine.c_str()); text += s; } } DdlNode* CreateAlterTriggerNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) { dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_TRIGGER); if (type.specified) { if (create && // ALTER TRIGGER doesn't accept table name ((relationName.hasData() && (type.value & (unsigned) TRIGGER_TYPE_MASK) != (unsigned) TRIGGER_TYPE_DML) || (relationName.isEmpty() && (type.value & (unsigned) TRIGGER_TYPE_MASK) != (unsigned) TRIGGER_TYPE_DB && (type.value & (unsigned) TRIGGER_TYPE_MASK) != (unsigned) TRIGGER_TYPE_DDL))) { status_exception::raise( Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_incompatible_trigger_type)); } } return DdlNode::dsqlPass(dsqlScratch); } void CreateAlterTriggerNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { fb_assert(create || alter); Attachment* const attachment = transaction->getAttachment(); if (relationName.isEmpty() && !attachment->locksmith()) status_exception::raise(Arg::Gds(isc_adm_task_denied)); source.ltrim("\n\r\t "); // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); if (!create) { AutoRequest requestHandle; FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) TRG IN RDB$TRIGGERS WITH TRG.RDB$TRIGGER_NAME EQ name.c_str() { if (!type.specified && !TRG.RDB$TRIGGER_TYPE.NULL) type = TRG.RDB$TRIGGER_TYPE; if (relationName.isEmpty() && !TRG.RDB$RELATION_NAME.NULL) relationName = TRG.RDB$RELATION_NAME; } END_FOR if (!type.specified) status_exception::raise(Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(name)); } compile(tdbb, dsqlScratch); blrData = dsqlScratch->getBlrData(); debugData = dsqlScratch->getDebugData(); if (alter) { if (!modify(tdbb, dsqlScratch, transaction)) { if (create) // create or alter executeCreate(tdbb, dsqlScratch, transaction); else status_exception::raise(Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(name)); } } else executeCreate(tdbb, dsqlScratch, transaction); savePoint.release(); // everything is ok } void CreateAlterTriggerNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_TRIGGER, name); store(tdbb, dsqlScratch, transaction); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_TRIGGER, name); } void CreateAlterTriggerNode::compile(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch) { if (invalid) status_exception::raise(Arg::Gds(isc_dyn_invalid_ddl_trig) << name); if (compiled) return; compiled = true; invalid = true; if (body) { dsqlScratch->beginDebug(); dsqlScratch->getBlrData().clear(); // Create the "OLD" and "NEW" contexts for the trigger -- // the new one could be a dummy place holder to avoid resolving // fields to that context but prevent relations referenced in // the trigger actions from referencing the predefined "1" context. if (dsqlScratch->contextNumber) dsqlScratch->resetTriggerContextStack(); if (relationName.hasData()) { RelationSourceNode* relationNode = FB_NEW(getPool()) RelationSourceNode(getPool(), relationName); const string temp = relationNode->alias; // always empty? if (hasOldContext(type.value)) { relationNode->alias = OLD_CONTEXT_NAME; dsql_ctx* oldContext = PASS1_make_context(dsqlScratch, relationNode); oldContext->ctx_flags |= CTX_system; } else dsqlScratch->contextNumber++; if (hasNewContext(type.value)) { relationNode->alias = NEW_CONTEXT_NAME; dsql_ctx* newContext = PASS1_make_context(dsqlScratch, relationNode); newContext->ctx_flags |= CTX_system; } else dsqlScratch->contextNumber++; relationNode->alias = temp; } // generate the trigger blr if (dsqlScratch->isVersion4()) dsqlScratch->appendUChar(blr_version4); else dsqlScratch->appendUChar(blr_version5); dsqlScratch->appendUChar(blr_begin); dsqlScratch->setPsql(true); dsqlScratch->putLocalVariables(localDeclList, 0); dsqlScratch->scopeLevel++; // dimitr: I see no reason to deny EXIT command in triggers, // hence I've added zero label at the beginning. // My first suspicion regarding an obvious conflict // with trigger messages (nod_abort) is wrong, // although the fact that they use the same BLR code // is still a potential danger and must be fixed. // Hopefully, system triggers are never recompiled. dsqlScratch->appendUChar(blr_label); dsqlScratch->appendUChar(0); dsqlScratch->loopLevel = 0; dsqlScratch->cursorNumber = 0; body->dsqlPass(dsqlScratch)->genBlr(dsqlScratch); dsqlScratch->scopeLevel--; dsqlScratch->appendUChar(blr_end); dsqlScratch->appendUChar(blr_eoc); dsqlScratch->endDebug(); // The statement type may have been set incorrectly when parsing // the trigger actions, so reset it to reflect the fact that this // is a data definition statement; also reset the ddl node. dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_DDL); } invalid = false; } //---------------------- void DropTriggerNode::print(string& text) const { text.printf( "DropTriggerNode\n" " name: '%s'\n", name.c_str()); } DdlNode* DropTriggerNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) { dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_TRIGGER); return DdlNode::dsqlPass(dsqlScratch); } void DropTriggerNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); bool found = false; MetaName relationName; AutoCacheRequest requestHandle(tdbb, drq_e_trigger3, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) X IN RDB$TRIGGERS WITH X.RDB$TRIGGER_NAME EQ name.c_str() { if (!X.RDB$SYSTEM_FLAG.NULL) { switch (X.RDB$SYSTEM_FLAG) { case fb_sysflag_check_constraint: case fb_sysflag_referential_constraint: case fb_sysflag_view_check: status_exception::raise(Arg::Gds(isc_dyn_cant_modify_auto_trig)); break; case fb_sysflag_system: status_exception::raise( Arg::Gds(isc_dyn_cannot_mod_systrig) << X.RDB$TRIGGER_NAME); break; default: break; } } if (X.RDB$RELATION_NAME.NULL && !transaction->getAttachment()->locksmith()) status_exception::raise(Arg::Gds(isc_adm_task_denied)); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_TRIGGER, name); relationName = X.RDB$RELATION_NAME; ERASE X; found = true; } END_FOR if (!found && !silent) status_exception::raise(Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(name)); requestHandle.reset(tdbb, drq_e_trg_msgs3, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) TM IN RDB$TRIGGER_MESSAGES WITH TM.RDB$TRIGGER_NAME EQ name.c_str() { ERASE TM; } END_FOR requestHandle.reset(tdbb, drq_e_trg_prv2, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$USER EQ name.c_str() AND PRIV.RDB$USER_TYPE = obj_trigger { ERASE PRIV; } END_FOR // Clear the update flags on the fields if this is the last remaining // trigger that changes a view. bool viewFound = false; requestHandle.reset(tdbb, drq_e_trg_prv3, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) FIRST 1 V IN RDB$VIEW_RELATIONS CROSS F IN RDB$RELATION_FIELDS CROSS T IN RDB$TRIGGERS WITH V.RDB$VIEW_NAME EQ relationName.c_str() AND F.RDB$RELATION_NAME EQ V.RDB$VIEW_NAME AND F.RDB$RELATION_NAME EQ T.RDB$RELATION_NAME { viewFound = true; } END_FOR if (!viewFound) { requestHandle.reset(tdbb, drq_m_rel_flds2, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) F IN RDB$RELATION_FIELDS WITH F.RDB$RELATION_NAME EQ relationName.c_str() { MODIFY F USING F.RDB$UPDATE_FLAG = FALSE; END_MODIFY } END_FOR } if (found) executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_TRIGGER, name); savePoint.release(); // everything is ok } //---------------------- void CreateCollationNode::print(string& text) const { text.printf( "CreateCollationNode\n" " name: '%s'\n" " forCharSet: '%s'\n" " fromName: '%s'\n" " fromExternal: '%s'\n" " attributesOn: %x\n" " attributesOff: %x\n", name.c_str(), forCharSet.c_str(), fromName.c_str(), fromExternal.c_str(), attributesOn, attributesOff); } void CreateCollationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { Attachment* const attachment = transaction->tra_attachment; const string& userName = attachment->att_user->usr_user_name; // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_COLLATION, name); AutoCacheRequest request(tdbb, drq_s_colls, DYN_REQUESTS); STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$COLLATIONS { X.RDB$CHARACTER_SET_ID = forCharSetId; strcpy(X.RDB$COLLATION_NAME, name.c_str()); X.RDB$SYSTEM_FLAG = 0; X.RDB$OWNER_NAME.NULL = FALSE; strcpy(X.RDB$OWNER_NAME, userName.c_str()); X.RDB$SPECIFIC_ATTRIBUTES.NULL = TRUE; X.RDB$BASE_COLLATION_NAME.NULL = TRUE; CharSet* cs = INTL_charset_lookup(tdbb, forCharSetId); SubtypeInfo info; if (fromName.hasData()) { if (MET_get_char_coll_subtype_info(tdbb, INTL_CS_COLL_TO_TTYPE(forCharSetId, fromCollationId), &info) && info.specificAttributes.hasData()) { UCharBuffer temp; ULONG size = info.specificAttributes.getCount() * cs->maxBytesPerChar(); size = INTL_convert_bytes(tdbb, forCharSetId, temp.getBuffer(size), size, CS_METADATA, info.specificAttributes.begin(), info.specificAttributes.getCount(), status_exception::raise); temp.shrink(size); info.specificAttributes = temp; } strcpy(X.RDB$BASE_COLLATION_NAME, info.baseCollationName.c_str()); X.RDB$BASE_COLLATION_NAME.NULL = FALSE; } else if (fromExternal.hasData()) { strcpy(X.RDB$BASE_COLLATION_NAME, fromExternal.c_str()); X.RDB$BASE_COLLATION_NAME.NULL = FALSE; } if (specificAttributes.hasData() && forCharSetId != attachment->att_charset) { UCharBuffer temp; ULONG size = specificAttributes.getCount() * cs->maxBytesPerChar(); size = INTL_convert_bytes(tdbb, forCharSetId, temp.getBuffer(size), size, attachment->att_charset, specificAttributes.begin(), specificAttributes.getCount(), status_exception::raise); temp.shrink(size); specificAttributes = temp; } info.charsetName = forCharSet.c_str(); info.collationName = name; if (X.RDB$BASE_COLLATION_NAME.NULL) info.baseCollationName = info.collationName; else info.baseCollationName = X.RDB$BASE_COLLATION_NAME; info.ignoreAttributes = false; if (!IntlManager::collationInstalled(info.baseCollationName.c_str(), info.charsetName.c_str())) { // msg: 223: "Collation @1 not installed for character set @2" status_exception::raise( Arg::PrivateDyn(223) << info.baseCollationName << info.charsetName); } IntlUtil::SpecificAttributesMap map; if (!IntlUtil::parseSpecificAttributes( cs, info.specificAttributes.getCount(), info.specificAttributes.begin(), &map) || !IntlUtil::parseSpecificAttributes( cs, specificAttributes.getCount(), specificAttributes.begin(), &map)) { // msg: 222: "Invalid collation attributes" status_exception::raise(Arg::PrivateDyn(222)); } const string s = IntlUtil::generateSpecificAttributes(cs, map); string newSpecificAttributes; if (!IntlManager::setupCollationAttributes( info.baseCollationName.c_str(), info.charsetName.c_str(), s, newSpecificAttributes)) { // msg: 222: "Invalid collation attributes" status_exception::raise(Arg::PrivateDyn(222)); } memcpy(info.specificAttributes.getBuffer(newSpecificAttributes.length()), newSpecificAttributes.begin(), newSpecificAttributes.length()); if (info.specificAttributes.hasData()) { X.RDB$SPECIFIC_ATTRIBUTES.NULL = FALSE; attachment->storeMetaDataBlob(tdbb, transaction, &X.RDB$SPECIFIC_ATTRIBUTES, string(info.specificAttributes.begin(), info.specificAttributes.getCount()), forCharSetId); } info.attributes = (info.attributes | attributesOn) & (~attributesOff); X.RDB$COLLATION_ATTRIBUTES = info.attributes; // Do not allow invalid attributes here. if (!INTL_texttype_validate(tdbb, &info)) { // msg: 222: "Invalid collation attributes" status_exception::raise(Arg::PrivateDyn(222)); } // ASF: User collations are created with the last number available, // to minimize the possibility of conflicts with future system collations. // The greater available number is 126 to avoid signed/unsigned problems. X.RDB$COLLATION_ID.NULL = TRUE; X.RDB$COLLATION_ID = 126; AutoCacheRequest request2(tdbb, drq_l_max_coll_id, DYN_REQUESTS); FOR(REQUEST_HANDLE request2) Y IN RDB$COLLATIONS WITH Y.RDB$CHARACTER_SET_ID = forCharSetId AND Y.RDB$COLLATION_ID NOT MISSING SORTED BY DESCENDING Y.RDB$COLLATION_ID { if (Y.RDB$COLLATION_ID + 1 <= X.RDB$COLLATION_ID) { X.RDB$COLLATION_ID.NULL = FALSE; break; } else X.RDB$COLLATION_ID = Y.RDB$COLLATION_ID - 1; } END_FOR if (X.RDB$COLLATION_ID.NULL) status_exception::raise(Arg::Gds(isc_max_coll_per_charset)); } END_STORE storePrivileges(tdbb, transaction, name, obj_collation, USAGE_PRIVILEGES); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_COLLATION, name); savePoint.release(); // everything is ok // Update DSQL cache METD_drop_collation(transaction, name); MET_dsql_cache_release(tdbb, SYM_intlsym_collation, name); } DdlNode* CreateCollationNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) { const dsql_intlsym* resolvedCharSet = METD_get_charset( dsqlScratch->getTransaction(), forCharSet.length(), forCharSet.c_str()); if (!resolvedCharSet) { // specified character set not found ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-504) << Arg::Gds(isc_charset_not_found) << forCharSet); } forCharSetId = resolvedCharSet->intlsym_charset_id; if (fromName.hasData()) { const dsql_intlsym* resolvedCollation = METD_get_collation( dsqlScratch->getTransaction(), fromName, forCharSetId); if (!resolvedCollation) { // Specified collation not found ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_collation_not_found) << fromName << forCharSet); } fromCollationId = resolvedCollation->intlsym_collate_id; } return DdlNode::dsqlPass(dsqlScratch); } //---------------------- void DropCollationNode::print(string& text) const { text.printf( "DropCollationNode\n" " name: '%s'\n", name.c_str()); } void DropCollationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); bool found = false; AutoCacheRequest request(tdbb, drq_e_colls, DYN_REQUESTS); FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) COLL IN RDB$COLLATIONS CROSS CS IN RDB$CHARACTER_SETS WITH COLL.RDB$COLLATION_NAME EQ name.c_str() AND CS.RDB$CHARACTER_SET_ID EQ COLL.RDB$CHARACTER_SET_ID { executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_COLLATION, name); if (!COLL.RDB$SYSTEM_FLAG.NULL && COLL.RDB$SYSTEM_FLAG) status_exception::raise(Arg::Gds(isc_dyn_cannot_del_syscoll)); if (COLL.RDB$COLLATION_ID == 0 || (!CS.RDB$DEFAULT_COLLATE_NAME.NULL && MetaName(COLL.RDB$COLLATION_NAME) == MetaName(CS.RDB$DEFAULT_COLLATE_NAME))) { fb_utils::exact_name_limit(CS.RDB$CHARACTER_SET_NAME, sizeof(CS.RDB$CHARACTER_SET_NAME)); status_exception::raise( Arg::Gds(isc_dyn_cannot_del_def_coll) << CS.RDB$CHARACTER_SET_NAME); } found = true; fb_utils::exact_name_limit(COLL.RDB$COLLATION_NAME, sizeof(COLL.RDB$COLLATION_NAME)); AutoCacheRequest request2(tdbb, drq_l_rfld_coll, DYN_REQUESTS); FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) RF IN RDB$RELATION_FIELDS CROSS F IN RDB$FIELDS WITH RF.RDB$FIELD_SOURCE EQ F.RDB$FIELD_NAME AND F.RDB$CHARACTER_SET_ID EQ COLL.RDB$CHARACTER_SET_ID AND RF.RDB$COLLATION_ID EQ COLL.RDB$COLLATION_ID { fb_utils::exact_name_limit(RF.RDB$RELATION_NAME, sizeof(RF.RDB$RELATION_NAME)); fb_utils::exact_name_limit(RF.RDB$FIELD_NAME, sizeof(RF.RDB$FIELD_NAME)); status_exception::raise( Arg::Gds(isc_dyn_coll_used_table) << COLL.RDB$COLLATION_NAME << RF.RDB$RELATION_NAME << RF.RDB$FIELD_NAME); } END_FOR request2.reset(tdbb, drq_l_prm_coll, DYN_REQUESTS); FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) PRM IN RDB$PROCEDURE_PARAMETERS CROSS F IN RDB$FIELDS WITH PRM.RDB$FIELD_SOURCE EQ F.RDB$FIELD_NAME AND F.RDB$CHARACTER_SET_ID EQ COLL.RDB$CHARACTER_SET_ID AND PRM.RDB$COLLATION_ID EQ COLL.RDB$COLLATION_ID { fb_utils::exact_name_limit(PRM.RDB$PARAMETER_NAME, sizeof(PRM.RDB$PARAMETER_NAME)); status_exception::raise( Arg::Gds(isc_dyn_coll_used_procedure) << COLL.RDB$COLLATION_NAME << QualifiedName(PRM.RDB$PROCEDURE_NAME, (PRM.RDB$PACKAGE_NAME.NULL ? NULL : PRM.RDB$PACKAGE_NAME)).toString().c_str() << PRM.RDB$PARAMETER_NAME); } END_FOR request2.reset(tdbb, drq_l_arg_coll, DYN_REQUESTS); FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) ARG IN RDB$FUNCTION_ARGUMENTS CROSS F IN RDB$FIELDS WITH ARG.RDB$FIELD_SOURCE EQ F.RDB$FIELD_NAME AND F.RDB$CHARACTER_SET_ID EQ COLL.RDB$CHARACTER_SET_ID AND ARG.RDB$COLLATION_ID EQ COLL.RDB$COLLATION_ID { fb_utils::exact_name_limit(ARG.RDB$ARGUMENT_NAME, sizeof(ARG.RDB$ARGUMENT_NAME)); status_exception::raise( Arg::Gds(isc_dyn_coll_used_function) << COLL.RDB$COLLATION_NAME << QualifiedName(ARG.RDB$FUNCTION_NAME, (ARG.RDB$PACKAGE_NAME.NULL ? NULL : ARG.RDB$PACKAGE_NAME)).toString().c_str() << ARG.RDB$ARGUMENT_NAME); } END_FOR request2.reset(tdbb, drq_l_fld_coll, DYN_REQUESTS); FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) F IN RDB$FIELDS WITH F.RDB$CHARACTER_SET_ID EQ COLL.RDB$CHARACTER_SET_ID AND F.RDB$COLLATION_ID EQ COLL.RDB$COLLATION_ID { fb_utils::exact_name_limit(F.RDB$FIELD_NAME, sizeof(F.RDB$FIELD_NAME)); status_exception::raise( Arg::Gds(isc_dyn_coll_used_domain) << COLL.RDB$COLLATION_NAME << F.RDB$FIELD_NAME); } END_FOR ERASE COLL; if (!COLL.RDB$SECURITY_CLASS.NULL) deleteSecurityClass(tdbb, transaction, COLL.RDB$SECURITY_CLASS); } END_FOR request.reset(tdbb, drq_e_coll_prvs, DYN_REQUESTS); FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND PRIV.RDB$OBJECT_TYPE = obj_collation { ERASE PRIV; } END_FOR if (found) executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_COLLATION, name); else status_exception::raise(Arg::Gds(isc_dyn_collation_not_found) << Arg::Str(name)); savePoint.release(); // everything is ok // Update DSQL cache METD_drop_collation(transaction, name); MET_dsql_cache_release(tdbb, SYM_intlsym_collation, name); } //---------------------- void CreateDomainNode::print(string& text) const { string nameTypeStr; nameType->print(nameTypeStr); text = "CreateDomainNode\n" " " + nameTypeStr + "\n"; } void CreateDomainNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { Attachment* const attachment = transaction->tra_attachment; dsql_fld* type = nameType->type; // The commented line should be restored when implicit domains get their own sys flag. //if (fb_utils::implicit_domain(nameType->name.c_str())) if (strncmp(nameType->name.c_str(), IMPLICIT_DOMAIN_PREFIX, IMPLICIT_DOMAIN_PREFIX_LEN) == 0) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-637) << Arg::Gds(isc_dsql_implicit_domain_name) << nameType->name); } const ValueListNode* elements = type->ranges; const USHORT dims = elements ? elements->items.getCount() / 2 : 0; if (nameType->defaultClause && dims != 0) { // Default value is not allowed for array type in domain %s status_exception::raise(Arg::PrivateDyn(226) << nameType->name); } type->resolve(dsqlScratch); dsqlScratch->domainValue.dsc_dtype = type->dtype; dsqlScratch->domainValue.dsc_length = type->length; dsqlScratch->domainValue.dsc_scale = type->scale; // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_DOMAIN, nameType->name); storeGlobalField(tdbb, transaction, nameType->name, type); if (nameType->defaultClause || check || notNull) { AutoCacheRequest request(tdbb, drq_m_fld, DYN_REQUESTS); FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FLD IN RDB$FIELDS WITH FLD.RDB$FIELD_NAME EQ nameType->name.c_str() { MODIFY FLD if (nameType->defaultClause) { FLD.RDB$DEFAULT_SOURCE.NULL = FALSE; attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$DEFAULT_SOURCE, nameType->defaultClause->source); dsqlScratch->getBlrData().clear(); dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5); ValueExprNode* node = doDsqlPass(dsqlScratch, nameType->defaultClause->value); GEN_expr(dsqlScratch, node); dsqlScratch->appendUChar(blr_eoc); FLD.RDB$DEFAULT_VALUE.NULL = FALSE; attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$DEFAULT_VALUE, dsqlScratch->getBlrData()); } if (check) { FLD.RDB$VALIDATION_SOURCE.NULL = FALSE; attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$VALIDATION_SOURCE, check->source); dsqlScratch->getBlrData().clear(); dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5); // Increment the context level for this statement, so that the context number for // any RSE generated for a SELECT within the CHECK clause will be greater than 0. // In the environment of a domain check constraint, context number 0 is reserved // for the "blr_fid, 0, 0, 0," which is emitted for a nod_dom_value, corresponding // to an occurance of the VALUE keyword in the body of the check constraint. // -- chrisj 1999-08-20 ++dsqlScratch->contextNumber; BoolExprNode* node = doDsqlPass(dsqlScratch, check->value); GEN_expr(dsqlScratch, node); dsqlScratch->appendUChar(blr_eoc); FLD.RDB$VALIDATION_BLR.NULL = FALSE; attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$VALIDATION_BLR, dsqlScratch->getBlrData()); } if (notNull) { FLD.RDB$NULL_FLAG.NULL = FALSE; FLD.RDB$NULL_FLAG = 1; } END_MODIFY } END_FOR } executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_DOMAIN, nameType->name); savePoint.release(); // everything is ok } //---------------------- // Compare the original field type with the new field type to determine if the original type can be // changed to the new type. // // The following conversions are not allowed: // Blob to anything // Array to anything // Date to anything // Char to any numeric // Varchar to any numeric // Anything to Blob // Anything to Array // // This function throws an exception if the conversion can not be made. // // ASF: We should stop using dyn_fld here as soon DYN stops to be a caller of this function. void AlterDomainNode::checkUpdate(const dyn_fld& origFld, const dyn_fld& newFld) { ULONG errorCode = FB_SUCCESS; const USHORT origLen = DTYPE_IS_TEXT(origFld.dyn_dsc.dsc_dtype) ? origFld.dyn_charlen : DSC_string_length(&origFld.dyn_dsc); // Check to make sure that the old and new types are compatible switch (origFld.dyn_dtype) { // CHARACTER types case blr_text: case blr_varying: case blr_cstring: switch (newFld.dyn_dtype) { case blr_blob: case blr_blob_id: // Cannot change datatype for column %s. // The operation cannot be performed on BLOB, or ARRAY columns. errorCode = isc_dyn_dtype_invalid; break; case blr_sql_date: case blr_sql_time: case blr_timestamp: case blr_int64: case blr_long: case blr_short: case blr_d_float: case blr_double: case blr_float: // Cannot convert column %s from character to non-character data. errorCode = isc_dyn_dtype_conv_invalid; break; // If the original field is a character field and the new field is a character field, // is there enough space in the new field? case blr_text: case blr_varying: case blr_cstring: if (newFld.dyn_charlen < origLen) { // msg 208: New size specified for column %s must be at least %d characters. errorCode = isc_dyn_char_fld_too_small; } break; default: fb_assert(FALSE); errorCode = ENCODE_ISC_MSG(87, DYN_MSG_FAC); // MODIFY RDB$FIELDS FAILED break; } break; // BLOB and ARRAY types case blr_blob: case blr_blob_id: // Cannot change datatype for column %s. // The operation cannot be performed on BLOB, or ARRAY columns. errorCode = isc_dyn_dtype_invalid; break; // DATE types case blr_sql_date: case blr_sql_time: case blr_timestamp: switch (newFld.dyn_dtype) { case blr_sql_date: if (origFld.dyn_dtype == blr_sql_time) { // Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported. errorCode = isc_dyn_invalid_dtype_conversion; } break; case blr_sql_time: if (origFld.dyn_dtype == blr_sql_date) { // Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported. errorCode = isc_dyn_invalid_dtype_conversion; } break; case blr_timestamp: if (origFld.dyn_dtype == blr_sql_time) { // Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported. errorCode = isc_dyn_invalid_dtype_conversion; } break; // If the original field is a date field and the new field is a character field, // is there enough space in the new field? case blr_text: case blr_text2: case blr_varying: case blr_varying2: case blr_cstring: case blr_cstring2: if (newFld.dyn_charlen < origLen) { // msg 208: New size specified for column %s must be at least %d characters. errorCode = isc_dyn_char_fld_too_small; } break; default: // Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported. errorCode = isc_dyn_invalid_dtype_conversion; break; } break; // NUMERIC types case blr_int64: case blr_long: case blr_short: case blr_d_float: case blr_double: case blr_float: switch (newFld.dyn_dtype) { case blr_blob: case blr_blob_id: // Cannot change datatype for column %s. // The operation cannot be performed on BLOB, or ARRAY columns. errorCode = isc_dyn_dtype_invalid; break; case blr_sql_date: case blr_sql_time: case blr_timestamp: // Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported. errorCode = isc_dyn_invalid_dtype_conversion; break; // If the original field is a numeric field and the new field is a numeric field, // is there enough space in the new field (do not allow the base type to decrease) case blr_short: switch (origFld.dyn_dtype) { case blr_short: errorCode = checkUpdateNumericType(origFld, newFld); break; default: // Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported. errorCode = isc_dyn_invalid_dtype_conversion; break; } break; case blr_long: switch (origFld.dyn_dtype) { case blr_long: case blr_short: errorCode = checkUpdateNumericType(origFld, newFld); break; default: // Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported. errorCode = isc_dyn_invalid_dtype_conversion; break; } break; case blr_float: switch (origFld.dyn_dtype) { case blr_float: case blr_short: break; default: // Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported. errorCode = isc_dyn_invalid_dtype_conversion; break; } break; case blr_int64: switch (origFld.dyn_dtype) { case blr_int64: case blr_long: case blr_short: errorCode = checkUpdateNumericType(origFld, newFld); break; default: // Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported. errorCode = isc_dyn_invalid_dtype_conversion; break; } break; case blr_d_float: case blr_double: switch (origFld.dyn_dtype) { case blr_double: case blr_d_float: case blr_float: case blr_short: case blr_long: break; default: // Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported. errorCode = isc_dyn_invalid_dtype_conversion; break; } break; // If the original field is a numeric field and the new field is a character field, // is there enough space in the new field? case blr_text: case blr_varying: case blr_cstring: if (newFld.dyn_charlen < origLen) { // msg 208: New size specified for column %s must be at least %d characters. errorCode = isc_dyn_char_fld_too_small; } break; default: fb_assert(FALSE); errorCode = ENCODE_ISC_MSG(87, DYN_MSG_FAC); // MODIFY RDB$FIELDS FAILED break; } break; default: fb_assert(FALSE); errorCode = ENCODE_ISC_MSG(87, DYN_MSG_FAC); // MODIFY RDB$FIELDS FAILED break; } if (errorCode == FB_SUCCESS) return; switch (errorCode) { case isc_dyn_dtype_invalid: // Cannot change datatype for column %s.The operation cannot be performed on DATE, BLOB, or ARRAY columns. status_exception::raise(Arg::Gds(errorCode) << origFld.dyn_fld_name.c_str()); break; case isc_dyn_dtype_conv_invalid: // Cannot convert column %s from character to non-character data. status_exception::raise(Arg::Gds(errorCode) << origFld.dyn_fld_name.c_str()); break; case isc_dyn_char_fld_too_small: // msg 208: New size specified for column %s must be at least %d characters. status_exception::raise( Arg::Gds(errorCode) << origFld.dyn_fld_name.c_str() << Arg::Num(origLen)); break; case isc_dyn_scale_too_big: { int code = errorCode; int diff = newFld.dyn_precision - (origFld.dyn_precision + origFld.dyn_dsc.dsc_scale); if (diff < 0) { // If new scale becomes negative externally, the message is useless for the user. // (The scale is always zero or negative for us but externally is non-negative.) // Let's ask the user to widen the precision, then. Example: numeric(4, 0) -> numeric(1, 1). code = isc_dyn_precision_too_small; diff = newFld.dyn_precision - newFld.dyn_dsc.dsc_scale - diff; } // scale_too_big: New scale specified for column @1 must be at most @2. // precision_too_small: New precision specified for column @1 must be at least @2. status_exception::raise( Arg::Gds(code) << origFld.dyn_fld_name.c_str() << Arg::Num(diff)); } break; case isc_dyn_invalid_dtype_conversion: { TEXT orig_type[25], new_type[25]; DSC_get_dtype_name(&origFld.dyn_dsc, orig_type, sizeof(orig_type)); DSC_get_dtype_name(&newFld.dyn_dsc, new_type, sizeof(new_type)); // Cannot change datatype for @1. Conversion from base type @2 to @3 is not supported. status_exception::raise( Arg::Gds(errorCode) << origFld.dyn_fld_name.c_str() << orig_type << new_type); } break; default: // msg 95: "MODIFY RDB$RELATION_FIELDS failed" status_exception::raise(Arg::PrivateDyn(95)); } } // Compare the original field type with the new field type to determine if the original type can be // changed to the new type. // The types should be integral, since it tests only numeric/decimal subtypes to ensure the scale is // not being widened at the expense of the precision, because the old stored values should fit in // the new definition. // // This function returns an error code if the conversion can not be made. If the conversion can be // made, FB_SUCCESS is returned. ULONG AlterDomainNode::checkUpdateNumericType(const dyn_fld& origFld, const dyn_fld& newFld) { // Since dsc_scale is negative, the sum of precision and scale produces // the width of the integral part. if (origFld.dyn_sub_type && newFld.dyn_sub_type && origFld.dyn_precision + origFld.dyn_dsc.dsc_scale > newFld.dyn_precision + newFld.dyn_dsc.dsc_scale) { return isc_dyn_scale_too_big; } return FB_SUCCESS; } // Retrieves the type information for a domain so that it can be compared to a local field before // modifying the datatype of a field. void AlterDomainNode::getDomainType(thread_db* tdbb, jrd_tra* transaction, dyn_fld& dynFld) { AutoRequest request; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FLD IN RDB$FIELDS WITH FLD.RDB$FIELD_NAME EQ dynFld.dyn_fld_source.c_str(); { DSC_make_descriptor(&dynFld.dyn_dsc, FLD.RDB$FIELD_TYPE, FLD.RDB$FIELD_SCALE, FLD.RDB$FIELD_LENGTH, FLD.RDB$FIELD_SUB_TYPE, FLD.RDB$CHARACTER_SET_ID, FLD.RDB$COLLATION_ID); dynFld.dyn_charbytelen = FLD.RDB$FIELD_LENGTH; dynFld.dyn_dtype = FLD.RDB$FIELD_TYPE; dynFld.dyn_precision = FLD.RDB$FIELD_PRECISION; dynFld.dyn_sub_type = FLD.RDB$FIELD_SUB_TYPE; dynFld.dyn_charlen = FLD.RDB$CHARACTER_LENGTH; dynFld.dyn_collation = FLD.RDB$COLLATION_ID; dynFld.dyn_null_flag = FLD.RDB$NULL_FLAG != 0; if (!FLD.RDB$DIMENSIONS.NULL && FLD.RDB$DIMENSIONS > 0) dynFld.dyn_dtype = blr_blob; } END_FOR } // Updates the field names in an index and forces the index to be rebuilt with the new field names. void AlterDomainNode::modifyLocalFieldIndex(thread_db* tdbb, jrd_tra* transaction, const MetaName& relationName, const MetaName& fieldName, const MetaName& newFieldName) { AutoRequest request; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) IDX IN RDB$INDICES CROSS IDXS IN RDB$INDEX_SEGMENTS WITH IDX.RDB$INDEX_NAME EQ IDXS.RDB$INDEX_NAME AND IDX.RDB$RELATION_NAME EQ relationName.c_str() AND IDXS.RDB$FIELD_NAME EQ fieldName.c_str() { // Change the name of the field in the index MODIFY IDXS USING memcpy(IDXS.RDB$FIELD_NAME, newFieldName.c_str(), sizeof(IDXS.RDB$FIELD_NAME)); END_MODIFY // Set the index name to itself to tell the index to rebuild MODIFY IDX USING // This is to fool both gpre and gcc. char* p = IDX.RDB$INDEX_NAME; p[MAX_SQL_IDENTIFIER_LEN] = 0; END_MODIFY } END_FOR } void AlterDomainNode::print(string& text) const { text.printf( "AlterDomainNode\n" " %s\n", name.c_str()); } void AlterDomainNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { Attachment* const attachment = transaction->tra_attachment; // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); AutoCacheRequest request(tdbb, drq_m_fld2, DYN_REQUESTS); bool found = false; FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FLD IN RDB$FIELDS WITH FLD.RDB$FIELD_NAME EQ name.c_str() { found = true; executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_DOMAIN, name); if (!FLD.RDB$SYSTEM_FLAG.NULL && FLD.RDB$SYSTEM_FLAG == fb_sysflag_system) { status_exception::raise(Arg::Gds(isc_dyn_cant_modify_sysobj) << "domain" << Arg::Str(name)); } MODIFY FLD if (dropConstraint) { FLD.RDB$VALIDATION_BLR.NULL = TRUE; FLD.RDB$VALIDATION_SOURCE.NULL = TRUE; } if (dropDefault) { FLD.RDB$DEFAULT_VALUE.NULL = TRUE; FLD.RDB$DEFAULT_SOURCE.NULL = TRUE; } if (setConstraint) { if (!FLD.RDB$VALIDATION_BLR.NULL) { // msg 160: "Only one constraint allowed for a domain" status_exception::raise(Arg::PrivateDyn(160)); } dsql_fld localField(dsqlScratch->getStatement()->getPool()); // Get the attributes of the domain, and set any occurrences of // keyword VALUE to the correct type, length, scale, etc. if (!METD_get_domain(dsqlScratch->getTransaction(), &localField, name)) { // Specified domain or source field does not exist status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_domain_not_found) << name); } dsqlScratch->domainValue.dsc_dtype = localField.dtype; dsqlScratch->domainValue.dsc_length = localField.length; dsqlScratch->domainValue.dsc_scale = localField.scale; FLD.RDB$VALIDATION_SOURCE.NULL = FALSE; attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$VALIDATION_SOURCE, setConstraint->source); dsqlScratch->getBlrData().clear(); dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5); // Increment the context level for this statement, so that the context number for // any RSE generated for a SELECT within the CHECK clause will be greater than 0. // In the environment of a domain check constraint, context number 0 is reserved // for the "blr_fid, 0, 0, 0," which is emitted for a nod_dom_value, corresponding // to an occurance of the VALUE keyword in the body of the check constraint. // -- chrisj 1999-08-20 ++dsqlScratch->contextNumber; BoolExprNode* node = doDsqlPass(dsqlScratch, setConstraint->value); GEN_expr(dsqlScratch, node); dsqlScratch->appendUChar(blr_eoc); FLD.RDB$VALIDATION_BLR.NULL = FALSE; attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$VALIDATION_BLR, dsqlScratch->getBlrData()); } if (setDefault) { if (FLD.RDB$DIMENSIONS) { // msg 226: "Default value is not allowed for array type in domain %s" status_exception::raise(Arg::PrivateDyn(226) << name); } FLD.RDB$DEFAULT_SOURCE.NULL = FALSE; attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$DEFAULT_SOURCE, setDefault->source); dsqlScratch->getBlrData().clear(); dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5); ValueExprNode* node = doDsqlPass(dsqlScratch, setDefault->value); GEN_expr(dsqlScratch, node); dsqlScratch->appendUChar(blr_eoc); FLD.RDB$DEFAULT_VALUE.NULL = FALSE; attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$DEFAULT_VALUE, dsqlScratch->getBlrData()); } if (notNullFlag.specified) { FLD.RDB$NULL_FLAG.NULL = FALSE; FLD.RDB$NULL_FLAG = notNullFlag.value; } if (type) { type->resolve(dsqlScratch); dyn_fld origDom, newDom; DSC_make_descriptor(&origDom.dyn_dsc, FLD.RDB$FIELD_TYPE, FLD.RDB$FIELD_SCALE, FLD.RDB$FIELD_LENGTH, FLD.RDB$FIELD_SUB_TYPE, FLD.RDB$CHARACTER_SET_ID, FLD.RDB$COLLATION_ID); origDom.dyn_fld_name = name; origDom.dyn_charbytelen = FLD.RDB$FIELD_LENGTH; origDom.dyn_dtype = FLD.RDB$FIELD_TYPE; origDom.dyn_precision = FLD.RDB$FIELD_PRECISION; origDom.dyn_sub_type = FLD.RDB$FIELD_SUB_TYPE; origDom.dyn_charlen = FLD.RDB$CHARACTER_LENGTH; origDom.dyn_collation = FLD.RDB$COLLATION_ID; origDom.dyn_null_flag = !FLD.RDB$NULL_FLAG.NULL && FLD.RDB$NULL_FLAG != 0; // If the original field type is an array, force its blr type to blr_blob if (FLD.RDB$DIMENSIONS != 0) origDom.dyn_dtype = blr_blob; USHORT typeLength = type->length; switch (type->dtype) { case dtype_varying: typeLength -= sizeof(USHORT); break; // Not valid for domains, but may be important for a future refactor. case dtype_cstring: --typeLength; break; default: break; } DSC_make_descriptor(&newDom.dyn_dsc, blr_dtypes[type->dtype], type->scale, typeLength, type->subType, type->charSetId, type->collationId); newDom.dyn_fld_name = name; newDom.dyn_charbytelen = typeLength; newDom.dyn_dtype = blr_dtypes[type->dtype]; newDom.dyn_precision = type->precision; newDom.dyn_sub_type = type->subType; newDom.dyn_charlen = type->charLength; newDom.dyn_collation = type->collationId; newDom.dyn_null_flag = type->notNull; // Now that we have all of the information needed, let's check to see if the field // type can be modifed. checkUpdate(origDom, newDom); if (!newDom.dyn_dsc.isExact() || newDom.dyn_dsc.dsc_scale != 0) { AutoCacheRequest request(tdbb, drq_l_ident_gens, DYN_REQUESTS); FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) RFR IN RDB$RELATION_FIELDS WITH RFR.RDB$FIELD_SOURCE = FLD.RDB$FIELD_NAME AND RFR.RDB$GENERATOR_NAME NOT MISSING { // Domain @1 must be of exact number type with zero scale because it's used // in an identity column. status_exception::raise(Arg::PrivateDyn(276) << name); } END_FOR } // If the datatype was changed, update any indexes that involved the domain AutoRequest request2; FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) DOM IN RDB$RELATION_FIELDS WITH DOM.RDB$FIELD_SOURCE EQ name.c_str() { modifyLocalFieldIndex(tdbb, transaction, DOM.RDB$RELATION_NAME, DOM.RDB$FIELD_NAME, DOM.RDB$FIELD_NAME); } END_FOR // Update RDB$FIELDS updateRdbFields(type, FLD.RDB$FIELD_TYPE, FLD.RDB$FIELD_LENGTH, FLD.RDB$FIELD_SUB_TYPE.NULL, FLD.RDB$FIELD_SUB_TYPE, FLD.RDB$FIELD_SCALE.NULL, FLD.RDB$FIELD_SCALE, FLD.RDB$CHARACTER_SET_ID.NULL, FLD.RDB$CHARACTER_SET_ID, FLD.RDB$CHARACTER_LENGTH.NULL, FLD.RDB$CHARACTER_LENGTH, FLD.RDB$FIELD_PRECISION.NULL, FLD.RDB$FIELD_PRECISION, FLD.RDB$COLLATION_ID.NULL, FLD.RDB$COLLATION_ID, FLD.RDB$SEGMENT_LENGTH.NULL, FLD.RDB$SEGMENT_LENGTH); } if (renameTo.hasData()) { rename(tdbb, transaction, (FLD.RDB$DIMENSIONS.NULL ? 0 : FLD.RDB$DIMENSIONS)); strcpy(FLD.RDB$FIELD_NAME, renameTo.c_str()); } END_MODIFY } END_FOR if (!found) { // msg 89: "Global field not found" status_exception::raise(Arg::PrivateDyn(89)); } executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_DOMAIN, name); savePoint.release(); // everything is ok } void AlterDomainNode::rename(thread_db* tdbb, jrd_tra* transaction, SSHORT dimensions) { // Checks to see if the given domain already exists. AutoRequest request; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FLD IN RDB$FIELDS WITH FLD.RDB$FIELD_NAME EQ renameTo.c_str() { // msg 204: Cannot rename domain %s to %s. A domain with that name already exists. status_exception::raise(Arg::PrivateDyn(204) << name << renameTo); } END_FOR // CVC: Let's update the dimensions, too. if (dimensions != 0) { request.reset(); FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FDIM IN RDB$FIELD_DIMENSIONS WITH FDIM.RDB$FIELD_NAME EQ name.c_str() { MODIFY FDIM USING strcpy(FDIM.RDB$FIELD_NAME, renameTo.c_str()); END_MODIFY } END_FOR } request.reset(); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) RFLD IN RDB$RELATION_FIELDS WITH RFLD.RDB$FIELD_SOURCE EQ name.c_str() { MODIFY RFLD USING strcpy(RFLD.RDB$FIELD_SOURCE, renameTo.c_str()); END_MODIFY modifyLocalFieldIndex(tdbb, transaction, RFLD.RDB$RELATION_NAME, RFLD.RDB$FIELD_NAME, RFLD.RDB$FIELD_NAME); } END_FOR } //---------------------- // Delete the records in RDB$FIELD_DIMENSIONS pertaining to a field. bool DropDomainNode::deleteDimensionRecords(thread_db* tdbb, jrd_tra* transaction, const MetaName& name) { AutoCacheRequest request(tdbb, drq_e_dims, DYN_REQUESTS); bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$FIELD_DIMENSIONS WITH X.RDB$FIELD_NAME EQ name.c_str() { found = true; ERASE X; } END_FOR return found; } void DropDomainNode::print(string& text) const { text.printf( "DropDomainNode\n" " name: '%s'\n", name.c_str()); } void DropDomainNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); AutoCacheRequest request(tdbb, drq_e_gfields, DYN_REQUESTS); bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$FIELDS WITH X.RDB$FIELD_NAME EQ name.c_str() { executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_DOMAIN, name); check(tdbb, transaction); deleteDimensionRecords(tdbb, transaction, name); ERASE X; if (!X.RDB$SECURITY_CLASS.NULL) deleteSecurityClass(tdbb, transaction, X.RDB$SECURITY_CLASS); found = true; } END_FOR request.reset(tdbb, drq_e_gfld_prvs, DYN_REQUESTS); FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND PRIV.RDB$OBJECT_TYPE = obj_field { ERASE PRIV; } END_FOR if (found) executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_DOMAIN, name); else { // msg 89: "Domain not found" status_exception::raise(Arg::PrivateDyn(89)); } savePoint.release(); // everything is ok } void DropDomainNode::check(thread_db* tdbb, jrd_tra* transaction) { AutoCacheRequest request(tdbb, drq_l_fld_src, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) Y IN RDB$RELATION_FIELDS WITH Y.RDB$FIELD_SOURCE EQ name.c_str() { fb_utils::exact_name_limit(Y.RDB$FIELD_SOURCE, sizeof(Y.RDB$FIELD_SOURCE)); fb_utils::exact_name_limit(Y.RDB$RELATION_NAME, sizeof(Y.RDB$RELATION_NAME)); fb_utils::exact_name_limit(Y.RDB$FIELD_NAME, sizeof(Y.RDB$FIELD_NAME)); // msg 43: "Domain %s is used in table %s (local name %s) and can not be dropped" status_exception::raise( Arg::PrivateDyn(43) << Y.RDB$FIELD_SOURCE << Y.RDB$RELATION_NAME << Y.RDB$FIELD_NAME); } END_FOR request.reset(tdbb, drq_l_prp_src, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$PROCEDURE_PARAMETERS WITH X.RDB$FIELD_SOURCE EQ name.c_str() { fb_utils::exact_name_limit(X.RDB$FIELD_SOURCE, sizeof(X.RDB$FIELD_SOURCE)); fb_utils::exact_name_limit(X.RDB$PROCEDURE_NAME, sizeof(X.RDB$PROCEDURE_NAME)); fb_utils::exact_name_limit(X.RDB$PARAMETER_NAME, sizeof(X.RDB$PARAMETER_NAME)); // msg 239: "Domain %s is used in procedure %s (parameter name %s) and cannot be dropped" status_exception::raise( Arg::PrivateDyn(239) << X.RDB$FIELD_SOURCE << QualifiedName(X.RDB$PROCEDURE_NAME, (X.RDB$PACKAGE_NAME.NULL ? NULL : X.RDB$PACKAGE_NAME)).toString().c_str() << X.RDB$PARAMETER_NAME); } END_FOR request.reset(tdbb, drq_l_arg_src, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$FUNCTION_ARGUMENTS WITH X.RDB$FIELD_SOURCE EQ name.c_str() { fb_utils::exact_name_limit(X.RDB$FIELD_SOURCE, sizeof(X.RDB$FIELD_SOURCE)); fb_utils::exact_name_limit(X.RDB$FUNCTION_NAME, sizeof(X.RDB$FUNCTION_NAME)); fb_utils::exact_name_limit(X.RDB$ARGUMENT_NAME, sizeof(X.RDB$ARGUMENT_NAME)); // msg 239: "Domain %s is used in function %s (parameter name %s) and cannot be dropped" status_exception::raise( Arg::Gds(isc_dyn_domain_used_function) << X.RDB$FIELD_SOURCE << QualifiedName(X.RDB$FUNCTION_NAME, (X.RDB$PACKAGE_NAME.NULL ? NULL : X.RDB$PACKAGE_NAME)).toString().c_str() << X.RDB$ARGUMENT_NAME); } END_FOR } //---------------------- void CreateAlterExceptionNode::print(string& text) const { text.printf( "CreateAlterExceptionNode\n" " name: '%s' create: %d alter: %d\n" " message: '%s'\n", name.c_str(), create, alter, message.c_str()); } void CreateAlterExceptionNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { fb_assert(create || alter); if (message.length() > XCP_MESSAGE_LENGTH) status_exception::raise(Arg::Gds(isc_dyn_name_longer)); // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); if (alter) { if (!executeAlter(tdbb, dsqlScratch, transaction)) { if (create) // create or alter executeCreate(tdbb, dsqlScratch, transaction); else { // msg 144: "Exception not found" status_exception::raise(Arg::PrivateDyn(144)); } } } else executeCreate(tdbb, dsqlScratch, transaction); savePoint.release(); // everything is ok } void CreateAlterExceptionNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { Attachment* const attachment = transaction->getAttachment(); const string& userName = attachment->att_user->usr_user_name; executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_EXCEPTION, name); DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_exception); AutoCacheRequest request(tdbb, drq_s_xcp, DYN_REQUESTS); int faults = 0; while (true) { try { SINT64 id = DYN_UTIL_gen_unique_id(tdbb, drq_g_nxt_xcp_id, "RDB$EXCEPTIONS"); id %= (MAX_SSHORT + 1); if (!id) continue; STORE (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$EXCEPTIONS { X.RDB$EXCEPTION_NUMBER = id; X.RDB$SYSTEM_FLAG = 0; strcpy(X.RDB$EXCEPTION_NAME, name.c_str()); X.RDB$OWNER_NAME.NULL = FALSE; strcpy(X.RDB$OWNER_NAME, userName.c_str()); strcpy(X.RDB$MESSAGE, message.c_str()); } END_STORE break; } catch (const status_exception& ex) { if (ex.value()[1] != isc_unique_key_violation) throw; if (++faults > MAX_SSHORT) throw; fb_utils::init_status(tdbb->tdbb_status_vector); } } storePrivileges(tdbb, transaction, name, obj_exception, USAGE_PRIVILEGES); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_EXCEPTION, name); } bool CreateAlterExceptionNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { AutoCacheRequest request(tdbb, drq_m_xcp, DYN_REQUESTS); bool modified = false; FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$EXCEPTIONS WITH X.RDB$EXCEPTION_NAME EQ name.c_str() { executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_EXCEPTION, name); MODIFY X strcpy(X.RDB$MESSAGE, message.c_str()); modified = true; END_MODIFY } END_FOR if (modified) executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_EXCEPTION, name); return modified; } //---------------------- void DropExceptionNode::print(string& text) const { text.printf( "DropExceptionNode\n" " name: '%s'\n", name.c_str()); } void DropExceptionNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); AutoCacheRequest request(tdbb, drq_e_xcp, DYN_REQUESTS); bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$EXCEPTIONS WITH X.RDB$EXCEPTION_NAME EQ name.c_str() { executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_EXCEPTION, name); ERASE X; if (!X.RDB$SECURITY_CLASS.NULL) deleteSecurityClass(tdbb, transaction, X.RDB$SECURITY_CLASS); found = true; } END_FOR request.reset(tdbb, drq_e_xcp_prvs, DYN_REQUESTS); FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND PRIV.RDB$OBJECT_TYPE = obj_exception { ERASE PRIV; } END_FOR if (found) executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_EXCEPTION, name); else if (!silent) { // msg 144: "Exception not found" status_exception::raise(Arg::PrivateDyn(144)); } savePoint.release(); // everything is ok } //---------------------- void CreateAlterSequenceNode::print(string& text) const { text.printf( "CreateAlterSequenceNode\n" " name: %s\n", name.c_str()); } void CreateAlterSequenceNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { fb_assert(create || alter); // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); if (alter) { if (!executeAlter(tdbb, dsqlScratch, transaction)) { if (create) // create or alter executeCreate(tdbb, dsqlScratch, transaction); else { // msg 214: "Sequence not found" status_exception::raise(Arg::PrivateDyn(214) << name); } } } else executeCreate(tdbb, dsqlScratch, transaction); savePoint.release(); // everything is ok } void CreateAlterSequenceNode::putErrorPrefix(Firebird::Arg::StatusVector& statusVector) { // Possibilities I see in parse.y //CREATE SEQ -> !legacy, create, !alter //REPL SEQ (create or alter) -> !legacy, create, alter //ALTER SEQ -> !legacy, !create, alter //SET GENERATOR -> legacy, !create, alter ISC_STATUS rc = 0; if (legacy) { if (alter) rc = isc_dsql_set_generator_failed; else rc = isc_dsql_create_sequence_failed; // no way to distinguish } else { if (alter) rc = isc_dsql_alter_sequence_failed; else rc = isc_dsql_create_sequence_failed; } statusVector << Firebird::Arg::Gds(rc) << name; } void CreateAlterSequenceNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_SEQUENCE, name); const SINT64 val = value.specified ? value.value : 0; SLONG initialStep = 1; if (step.specified) { initialStep = step.value; 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, name); } bool CreateAlterSequenceNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { bool forbidden = false; if (legacy) { // The only need for this code is that for the sake of backward compatibility // SET GENERATOR is still described as isc_info_sql_stmt_set_generator and ISQL // treats it as DML thus executing it in a separate transaction. So we need to ensure // that the generator created in another transaction can be found here. This is done // using MET_lookup_generator() which works in the system transaction. SLONG oldStep = 0; const SLONG id = MET_lookup_generator(tdbb, name, &forbidden, &oldStep); if (id < 0) return false; if (forbidden && !tdbb->getAttachment()->isRWGbak()) status_exception::raise(Arg::Gds(isc_dyn_cant_modify_sysobj) << "generator" << Arg::Str(name)); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_SEQUENCE, name); fb_assert(restartSpecified && value.specified); const SINT64 val = value.specified ? value.value : 0; if (step.specified) { const SLONG newStep = step.value; if (newStep == 0) status_exception::raise(Arg::Gds(isc_dyn_cant_use_zero_increment) << Arg::Str(name)); // Perhaps it's better to move this to DFW? if (newStep != oldStep) MET_update_generator_increment(tdbb, id, newStep); } transaction->getGenIdCache()->put(id, val); dsc desc; desc.makeText((USHORT) name.length(), ttype_metadata, (UCHAR*) name.c_str()); DFW_post_work(transaction, dfw_set_generator, &desc, id); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_SEQUENCE, name); return true; } AutoCacheRequest request(tdbb, drq_l_gens, DYN_REQUESTS); bool found = false; FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$GENERATORS WITH X.RDB$GENERATOR_NAME EQ name.c_str() { executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_SEQUENCE, name); if (!X.RDB$SYSTEM_FLAG.NULL && X.RDB$SYSTEM_FLAG == fb_sysflag_system) { forbidden = true; break; } const SLONG id = X.RDB$GENERATOR_ID; if (step.specified) { const SLONG newStep = step.value; if (newStep == 0) status_exception::raise(Arg::Gds(isc_dyn_cant_use_zero_increment) << Arg::Str(name)); if (newStep != X.RDB$GENERATOR_INCREMENT) { MODIFY X X.RDB$GENERATOR_INCREMENT = newStep; END_MODIFY } } if (restartSpecified) { const SINT64 val = value.specified ? value.value : (!X.RDB$INITIAL_VALUE.NULL ? X.RDB$INITIAL_VALUE : 0); transaction->getGenIdCache()->put(id, val); } dsc desc; desc.makeText((USHORT) name.length(), ttype_metadata, (UCHAR*) name.c_str()); DFW_post_work(transaction, dfw_set_generator, &desc, id); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_SEQUENCE, name); found = true; } END_FOR if (forbidden) status_exception::raise(Arg::Gds(isc_dyn_cant_modify_sysobj) << "generator" << Arg::Str(name)); return found; } SSHORT CreateAlterSequenceNode::store(thread_db* tdbb, jrd_tra* transaction, const MetaName& name, fb_sysflag sysFlag, SINT64 val, SLONG step) { Attachment* const attachment = transaction->tra_attachment; const string& userName = attachment->att_user->usr_user_name; DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_generator); AutoCacheRequest request(tdbb, drq_s_gens, DYN_REQUESTS); int faults = 0; SSHORT storedId = -1; while (true) { try { SINT64 id = DYN_UTIL_gen_unique_id(tdbb, drq_g_nxt_gen_id, MASTER_GENERATOR); id %= MAX_SSHORT + 1; if (id == 0) continue; STORE (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$GENERATORS { X.RDB$GENERATOR_ID = id; X.RDB$SYSTEM_FLAG = (SSHORT) sysFlag; strcpy(X.RDB$GENERATOR_NAME, name.c_str()); X.RDB$OWNER_NAME.NULL = FALSE; strcpy(X.RDB$OWNER_NAME, userName.c_str()); X.RDB$INITIAL_VALUE.NULL = FALSE; X.RDB$INITIAL_VALUE = val; X.RDB$GENERATOR_INCREMENT = step; } END_STORE storedId = id; break; } catch (const status_exception& ex) { if (ex.value()[1] != isc_unique_key_violation) throw; if (++faults > MAX_SSHORT) throw; fb_utils::init_status(tdbb->tdbb_status_vector); } } storePrivileges(tdbb, transaction, name, obj_generator, USAGE_PRIVILEGES); // The STORE above has caused the DFW item to be posted, so we just adjust the cached // generator value. transaction->getGenIdCache()->put(storedId, val); return storedId; } //---------------------- void DropSequenceNode::print(string& text) const { text.printf( "DropSequenceNode\n" " name: %s\n", name.c_str()); } void DropSequenceNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); AutoCacheRequest request(tdbb, drq_e_gens, DYN_REQUESTS); bool found = false; FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) GEN IN RDB$GENERATORS WITH GEN.RDB$GENERATOR_NAME EQ name.c_str() { if (!GEN.RDB$SYSTEM_FLAG.NULL && GEN.RDB$SYSTEM_FLAG != 0) { // msg 272: "Cannot delete system generator @1" status_exception::raise(Arg::PrivateDyn(272) << name); } executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_SEQUENCE, name); ERASE GEN; if (!GEN.RDB$SECURITY_CLASS.NULL) deleteSecurityClass(tdbb, transaction, GEN.RDB$SECURITY_CLASS); found = true; } END_FOR request.reset(tdbb, drq_e_gen_prvs, DYN_REQUESTS); FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND PRIV.RDB$OBJECT_TYPE = obj_generator { ERASE PRIV; } END_FOR if (found) executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_SEQUENCE, name); else if (!silent) status_exception::raise(Arg::Gds(isc_gennotdef) << Arg::Str(name)); savePoint.release(); // everything is ok } // Delete a record from RDB$GENERATORS, without verifying RDB$SYSTEM_FLAG. void DropSequenceNode::deleteIdentity(thread_db* tdbb, jrd_tra* transaction, const MetaName& name) { AutoCacheRequest request(tdbb, drq_e_ident_gens, DYN_REQUESTS); FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) GEN IN RDB$GENERATORS WITH GEN.RDB$GENERATOR_NAME EQ name.c_str() { ERASE GEN; } END_FOR } //---------------------- RelationNode::RelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode) : DdlNode(p), dsqlNode(aDsqlNode), name(p, dsqlNode->dsqlName), clauses(p) { } void RelationNode::FieldDefinition::modify(thread_db* tdbb, jrd_tra* transaction) { AutoRequest request; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) RFR IN RDB$RELATION_FIELDS WITH RFR.RDB$RELATION_NAME EQ relationName.c_str() AND RFR.RDB$FIELD_NAME EQ name.c_str() { // ASF: This is prepared only to modify view fields! MODIFY RFR strcpy(RFR.RDB$FIELD_SOURCE, fieldSource.c_str()); RFR.RDB$COLLATION_ID.NULL = TRUE; RFR.RDB$GENERATOR_NAME.NULL = TRUE; RFR.RDB$IDENTITY_TYPE.NULL = TRUE; RFR.RDB$NULL_FLAG.NULL = TRUE; RFR.RDB$DEFAULT_SOURCE.NULL = TRUE; RFR.RDB$DEFAULT_VALUE.NULL = TRUE; RFR.RDB$FIELD_POSITION.NULL = TRUE; RFR.RDB$VIEW_CONTEXT.NULL = TRUE; RFR.RDB$BASE_FIELD.NULL = TRUE; ///RFR.RDB$UPDATE_FLAG.NULL = TRUE; if (collationId.specified) { RFR.RDB$COLLATION_ID.NULL = FALSE; RFR.RDB$COLLATION_ID = collationId.value; } SLONG fieldPos = -1; if (position.specified) fieldPos = position.value; else { DYN_UTIL_generate_field_position(tdbb, relationName, &fieldPos); if (fieldPos >= 0) ++fieldPos; } if (fieldPos >= 0) { RFR.RDB$FIELD_POSITION.NULL = FALSE; RFR.RDB$FIELD_POSITION = SSHORT(fieldPos); } if (baseField.hasData()) { RFR.RDB$BASE_FIELD.NULL = FALSE; strcpy(RFR.RDB$BASE_FIELD, baseField.c_str()); } if (viewContext.specified) { fb_assert(baseField.hasData()); RFR.RDB$VIEW_CONTEXT.NULL = FALSE; RFR.RDB$VIEW_CONTEXT = viewContext.value; DYN_UTIL_find_field_source(tdbb, transaction, relationName, viewContext.value, baseField.c_str(), RFR.RDB$FIELD_SOURCE); } END_MODIFY } END_FOR } void RelationNode::FieldDefinition::store(thread_db* tdbb, jrd_tra* transaction) { Attachment* const attachment = transaction->tra_attachment; AutoCacheRequest request(tdbb, drq_s_lfields, DYN_REQUESTS); STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) RFR IN RDB$RELATION_FIELDS { strcpy(RFR.RDB$FIELD_NAME, name.c_str()); strcpy(RFR.RDB$RELATION_NAME, relationName.c_str()); strcpy(RFR.RDB$FIELD_SOURCE, fieldSource.c_str()); RFR.RDB$SYSTEM_FLAG = 0; RFR.RDB$COLLATION_ID.NULL = TRUE; RFR.RDB$GENERATOR_NAME.NULL = TRUE; RFR.RDB$IDENTITY_TYPE.NULL = TRUE; RFR.RDB$NULL_FLAG.NULL = TRUE; RFR.RDB$DEFAULT_SOURCE.NULL = TRUE; RFR.RDB$DEFAULT_VALUE.NULL = TRUE; RFR.RDB$FIELD_POSITION.NULL = TRUE; RFR.RDB$VIEW_CONTEXT.NULL = TRUE; RFR.RDB$BASE_FIELD.NULL = TRUE; ///RFR.RDB$UPDATE_FLAG.NULL = TRUE; if (collationId.specified) { RFR.RDB$COLLATION_ID.NULL = FALSE; RFR.RDB$COLLATION_ID = collationId.value; } if (identitySequence.hasData()) { RFR.RDB$GENERATOR_NAME.NULL = FALSE; strcpy(RFR.RDB$GENERATOR_NAME, identitySequence.c_str()); RFR.RDB$IDENTITY_TYPE.NULL = FALSE; RFR.RDB$IDENTITY_TYPE = IDENT_TYPE_BY_DEFAULT; } if (notNullFlag.specified) { RFR.RDB$NULL_FLAG.NULL = FALSE; RFR.RDB$NULL_FLAG = notNullFlag.value; } if (defaultSource.hasData()) { RFR.RDB$DEFAULT_SOURCE.NULL = FALSE; attachment->storeMetaDataBlob(tdbb, transaction, &RFR.RDB$DEFAULT_SOURCE, defaultSource); } if (defaultValue.length > 0) { RFR.RDB$DEFAULT_VALUE.NULL = FALSE; attachment->storeBinaryBlob(tdbb, transaction, &RFR.RDB$DEFAULT_VALUE, defaultValue); } SLONG fieldPos = -1; if (position.specified) fieldPos = position.value; else { DYN_UTIL_generate_field_position(tdbb, relationName, &fieldPos); if (fieldPos >= 0) ++fieldPos; } if (fieldPos >= 0) { RFR.RDB$FIELD_POSITION.NULL = FALSE; RFR.RDB$FIELD_POSITION = SSHORT(fieldPos); } if (baseField.hasData()) { RFR.RDB$BASE_FIELD.NULL = FALSE; strcpy(RFR.RDB$BASE_FIELD, baseField.c_str()); } if (viewContext.specified) { fb_assert(baseField.hasData()); RFR.RDB$VIEW_CONTEXT.NULL = FALSE; RFR.RDB$VIEW_CONTEXT = viewContext.value; DYN_UTIL_find_field_source(tdbb, transaction, relationName, viewContext.value, baseField.c_str(), RFR.RDB$FIELD_SOURCE); } } END_STORE } // Delete local field. // // The rules for dropping a regular column: // // 1. the column is not referenced in any views. // 2. the column is not part of any user defined indexes. // 3. the column is not used in any SQL statements inside of store // procedures or triggers // 4. the column is not part of any check-constraints // // The rules for dropping a column that was created as primary key: // // 1. the column is not defined as any foreign keys // 2. the column is not defined as part of compound primary keys // // The rules for dropping a column that was created as foreign key: // // 1. the column is not defined as a compound foreign key. A // compound foreign key is a foreign key consisted of more // than one columns. // // The RI enforcement for dropping primary key column is done by system // triggers and the RI enforcement for dropping foreign key column is // done by code and system triggers. See the functional description of // deleteKeyConstraint function for detail. void RelationNode::deleteLocalField(thread_db* tdbb, jrd_tra* transaction, const MetaName& relationName, const MetaName& fieldName) { AutoCacheRequest request(tdbb, drq_l_dep_flds, DYN_REQUESTS); bool found = false; // Make sure that column is not referenced in any views. FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$RELATION_FIELDS CROSS Y IN RDB$RELATION_FIELDS CROSS Z IN RDB$VIEW_RELATIONS WITH X.RDB$RELATION_NAME EQ relationName.c_str() AND X.RDB$FIELD_NAME EQ fieldName.c_str() AND X.RDB$FIELD_NAME EQ Y.RDB$BASE_FIELD AND X.RDB$FIELD_SOURCE EQ Y.RDB$FIELD_SOURCE AND Y.RDB$RELATION_NAME EQ Z.RDB$VIEW_NAME AND X.RDB$RELATION_NAME EQ Z.RDB$RELATION_NAME AND Y.RDB$VIEW_CONTEXT EQ Z.RDB$VIEW_CONTEXT { // msg 52: "field %s from relation %s is referenced in view %s" status_exception::raise( Arg::PrivateDyn(52) << fieldName << relationName << Y.RDB$RELATION_NAME); } END_FOR // If the column to be dropped is being used as a foreign key // and the column was not part of any compound foreign key, // then we can drop the column. But we have to drop the foreign key // constraint first. request.reset(tdbb, drq_g_rel_constr_nm, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) IDX IN RDB$INDICES CROSS IDX_SEG IN RDB$INDEX_SEGMENTS CROSS REL_CONST IN RDB$RELATION_CONSTRAINTS WITH IDX.RDB$RELATION_NAME EQ relationName.c_str() AND REL_CONST.RDB$RELATION_NAME EQ relationName.c_str() AND IDX_SEG.RDB$FIELD_NAME EQ fieldName.c_str() AND IDX.RDB$INDEX_NAME EQ IDX_SEG.RDB$INDEX_NAME AND IDX.RDB$INDEX_NAME EQ REL_CONST.RDB$INDEX_NAME AND REL_CONST.RDB$CONSTRAINT_TYPE EQ FOREIGN_KEY { if (IDX.RDB$SEGMENT_COUNT == 1) { deleteKeyConstraint(tdbb, transaction, relationName, REL_CONST.RDB$CONSTRAINT_NAME, IDX.RDB$INDEX_NAME); } else { // msg 187: "field %s from relation %s is referenced in index %s" status_exception::raise( Arg::PrivateDyn(187) << fieldName << relationName << IDX.RDB$INDEX_NAME); } } END_FOR // Make sure that column is not referenced in any user-defined indexes. // NOTE: You still could see the system generated indices even though // they were already been deleted when dropping column that was used // as foreign key before "commit". request.reset(tdbb, drq_e_l_idx, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) IDX IN RDB$INDICES CROSS IDX_SEG IN RDB$INDEX_SEGMENTS WITH IDX.RDB$INDEX_NAME EQ IDX_SEG.RDB$INDEX_NAME AND IDX.RDB$RELATION_NAME EQ relationName.c_str() AND IDX_SEG.RDB$FIELD_NAME EQ fieldName.c_str() AND NOT ANY REL_CONST IN RDB$RELATION_CONSTRAINTS WITH REL_CONST.RDB$RELATION_NAME EQ IDX.RDB$RELATION_NAME AND REL_CONST.RDB$INDEX_NAME EQ IDX.RDB$INDEX_NAME { // msg 187: "field %s from relation %s is referenced in index %s" status_exception::raise( Arg::PrivateDyn(187) << fieldName << relationName << fb_utils::exact_name_limit(IDX.RDB$INDEX_NAME, sizeof(IDX.RDB$INDEX_NAME))); } END_FOR // Delete the automatically created generator for Identity columns. request.reset(tdbb, drq_e_lfield, DYN_REQUESTS); found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) RFR IN RDB$RELATION_FIELDS WITH RFR.RDB$FIELD_NAME EQ fieldName.c_str() AND RFR.RDB$RELATION_NAME EQ relationName.c_str() { if (!RFR.RDB$GENERATOR_NAME.NULL) DropSequenceNode::deleteIdentity(tdbb, transaction, RFR.RDB$GENERATOR_NAME); ERASE RFR; if (!RFR.RDB$SECURITY_CLASS.NULL && !strncmp(RFR.RDB$SECURITY_CLASS, SQL_SECCLASS_PREFIX, SQL_SECCLASS_PREFIX_LEN)) { deleteSecurityClass(tdbb, transaction, RFR.RDB$SECURITY_CLASS); } found = true; DropRelationNode::deleteGlobalField(tdbb, transaction, RFR.RDB$FIELD_SOURCE); } END_FOR request.reset(tdbb, drq_e_fld_prvs, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$RELATION_NAME EQ relationName.c_str() AND PRIV.RDB$FIELD_NAME EQ fieldName.c_str() AND PRIV.RDB$OBJECT_TYPE = obj_relation { ERASE PRIV; } END_FOR if (!found) { // msg 176: "column %s does not exist in table/view %s" status_exception::raise(Arg::PrivateDyn(176) << fieldName << relationName); } } void RelationNode::defineField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction, AddColumnClause* clause, SSHORT position, const ObjectsArray* pkCols) { dsql_fld* field = clause->field; // 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)) { dsql_fld* permField = FB_NEW(dsqlScratch->getAttachment()->dbb_pool) dsql_fld( dsqlScratch->getAttachment()->dbb_pool); *permField = *field; field = permField; permanent = true; } field->fld_next = relation->rel_fields; relation->rel_fields = field; } try { // Check for constraints. ObjectsArray constraints; bool notNullFlag = false; if (clause->identity) notNullFlag = true; // identity columns are implicitly not null for (ObjectsArray::iterator ptr = clause->constraints.begin(); ptr != clause->constraints.end(); ++ptr) { makeConstraint(tdbb, dsqlScratch, transaction, &*ptr, constraints, ¬NullFlag); } if (!notNullFlag && pkCols) { // Let's see if the field appears in a "primary_key (a, b, c)" relation constraint. for (size_t i = 0; !notNullFlag && i < pkCols->getCount(); ++i) { if (field->fld_name == (*pkCols)[i].c_str()) notNullFlag = true; } } FieldDefinition fieldDefinition(*tdbb->getDefaultPool()); fieldDefinition.relationName = name; fieldDefinition.name = field->fld_name; fieldDefinition.notNullFlag = notNullFlag; if (position >= 0) fieldDefinition.position = position; if (field->typeOfName.hasData()) { // Get the domain information. if (!METD_get_domain(transaction, field, field->typeOfName)) { // Specified domain or source field does not exist status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_domain_not_found) << field->typeOfName); } fieldDefinition.fieldSource = field->typeOfName; } else { string computedSource; BlrDebugWriter::BlrData computedValue; if (clause->computed) { field->flags |= FLD_computed; defineComputed(dsqlScratch, dsqlNode, field, clause->computed, computedSource, computedValue); } field->collate = clause->collate; field->resolve(dsqlScratch); // Generate a domain. storeGlobalField(tdbb, transaction, fieldDefinition.fieldSource, field, computedSource, computedValue); } if ((relation->rel_flags & REL_external) && (field->dtype == dtype_blob || field->dtype == dtype_array || field->dimensions)) { const char* typeName = (field->dtype == dtype_blob ? "BLOB" : "ARRAY"); status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_type_not_supp_ext_tab) << typeName << name << field->fld_name); } if (clause->collate.hasData()) DDL_resolve_intl_type(dsqlScratch, field, clause->collate); if (clause->identity) { dsc desc; MET_get_domain(tdbb, *tdbb->getDefaultPool(), fieldDefinition.fieldSource, &desc, NULL); if (!desc.isExact() || desc.dsc_scale != 0) { // Identity column @1 of table @2 must be exact numeric with zero scale. status_exception::raise(Arg::PrivateDyn(273) << field->fld_name << name); } DYN_UTIL_generate_generator_name(tdbb, fieldDefinition.identitySequence); CreateAlterSequenceNode::store(tdbb, transaction, fieldDefinition.identitySequence, fb_sysflag_identity_generator, clause->identityStart, 1); } BlrDebugWriter::BlrData defaultValue; if (clause->defaultValue) { if (defineDefault(tdbb, dsqlScratch, field, clause->defaultValue, fieldDefinition.defaultSource, defaultValue) && notNullFlag) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_bad_default_value) << Arg::Gds(isc_invalid_clause) << "default null not null"); } } fieldDefinition.defaultValue = defaultValue; if (clause->collate.hasData()) fieldDefinition.collationId = field->collationId; fieldDefinition.store(tdbb, transaction); // Define the field constraints. for (ObjectsArray::iterator constraint(constraints.begin()); constraint != constraints.end(); ++constraint) { if (constraint->type != Constraint::TYPE_FK) constraint->columns.add(field->fld_name); defineConstraint(tdbb, dsqlScratch, transaction, *constraint); } } catch (const Exception&) { clearPermanentField(relation, permanent); throw; } clearPermanentField(relation, permanent); } // Define a DEFAULT clause. Return true for DEFAULT NULL. bool RelationNode::defineDefault(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch, dsql_fld* /*field*/, ValueSourceClause* clause, string& source, BlrDebugWriter::BlrData& value) { ValueExprNode* input = doDsqlPass(dsqlScratch, clause->value); // Generate the blr expression. dsqlScratch->getBlrData().clear(); dsqlScratch->getDebugData().clear(); dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5); GEN_expr(dsqlScratch, input); dsqlScratch->appendUChar(blr_eoc); // Generate the source text. source = clause->source; value.assign(dsqlScratch->getBlrData()); return ExprNode::is(input); } // Make a constraint object from a legacy node. void RelationNode::makeConstraint(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction, AddConstraintClause* clause, ObjectsArray& constraints, bool* notNull) { switch (clause->constraintType) { case AddConstraintClause::CTYPE_NOT_NULL: case AddConstraintClause::CTYPE_PK: if (notNull && !*notNull) { *notNull = true; Constraint& constraint = constraints.add(); constraint.type = Constraint::TYPE_NOT_NULL; if (clause->constraintType == AddConstraintClause::CTYPE_NOT_NULL) constraint.name = clause->name; } if (clause->constraintType == AddConstraintClause::CTYPE_NOT_NULL) break; // AddConstraintClause::CTYPE_PK falls into case AddConstraintClause::CTYPE_UNIQUE: { Constraint& constraint = constraints.add(); constraint.type = clause->constraintType == AddConstraintClause::CTYPE_PK ? Constraint::TYPE_PK : Constraint::TYPE_UNIQUE; constraint.name = clause->name; constraint.index = clause->index; if (constraint.index && constraint.index->name.isEmpty()) constraint.index->name = constraint.name; constraint.columns = clause->columns; break; } case AddConstraintClause::CTYPE_FK: { Constraint& constraint = constraints.add(); constraint.type = Constraint::TYPE_FK; constraint.name = clause->name; constraint.columns = clause->columns; constraint.refRelation = clause->refRelation; constraint.refColumns = clause->refColumns; // If there is a referenced table name but no referenced field names, the // primary key of the referenced table designates the referenced fields. if (clause->refColumns.isEmpty()) { Array > refColumns; METD_get_primary_key(transaction, clause->refRelation, refColumns); // If there is NEITHER an explicitly referenced field name, NOR does // the referenced table have a primary key to serve as the implicitly // referenced field, fail. if (refColumns.isEmpty()) { // "REFERENCES table" without "(column)" requires PRIMARY // KEY on referenced table status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_reftable_requires_pk)); } else { const NestConst* ptr = refColumns.begin(); for (const NestConst* const end = refColumns.end(); ptr != end; ++ptr) constraint.refColumns.add((*ptr)->dsqlName); } } if (constraint.refColumns.getCount() != constraint.columns.getCount()) { // Foreign key field count does not match primary key. status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_key_field_count_err)); } // Define the foreign key index and the triggers that may be needed // for referential integrity action. constraint.index = clause->index; if (constraint.index && constraint.index->name.isEmpty()) constraint.index->name = constraint.name; if (clause->refAction) { if (clause->refAction->updateAction != 0) { switch (clause->refAction->updateAction) { case RefActionClause::ACTION_CASCADE: constraint.refUpdateAction = RI_ACTION_CASCADE; defineUpdateCascadeTrigger(dsqlScratch, constraint); break; case RefActionClause::ACTION_SET_DEFAULT: constraint.refUpdateAction = RI_ACTION_DEFAULT; defineSetDefaultTrigger(dsqlScratch, constraint, true); break; case RefActionClause::ACTION_SET_NULL: constraint.refUpdateAction = RI_ACTION_NULL; defineSetNullTrigger(dsqlScratch, constraint, true); break; default: fb_assert(0); // fall into case RefActionClause::ACTION_NONE: constraint.refUpdateAction = RI_ACTION_NONE; break; } } if (clause->refAction->deleteAction != 0) { switch (clause->refAction->deleteAction) { case RefActionClause::ACTION_CASCADE: constraint.refDeleteAction = RI_ACTION_CASCADE; defineDeleteCascadeTrigger(dsqlScratch, constraint); break; case RefActionClause::ACTION_SET_DEFAULT: constraint.refDeleteAction = RI_ACTION_DEFAULT; defineSetDefaultTrigger(dsqlScratch, constraint, false); break; case RefActionClause::ACTION_SET_NULL: constraint.refDeleteAction = RI_ACTION_NULL; defineSetNullTrigger(dsqlScratch, constraint, false); break; default: fb_assert(0); // fall into case RefActionClause::ACTION_NONE: constraint.refDeleteAction = RI_ACTION_NONE; break; } } } break; } case AddConstraintClause::CTYPE_CHECK: { Constraint& constraint = constraints.add(); constraint.type = Constraint::TYPE_CHECK; constraint.name = clause->name; defineCheckConstraint(dsqlScratch, constraint, clause->check); break; } } } // Define a constraint. void RelationNode::defineConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction, Constraint& constraint) { if (constraint.name.isEmpty()) DYN_UTIL_generate_constraint_name(tdbb, constraint.name); AutoCacheRequest request(tdbb, drq_s_rel_con, DYN_REQUESTS); MetaName referredIndexName; STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) CRT IN RDB$RELATION_CONSTRAINTS { CRT.RDB$INDEX_NAME.NULL = TRUE; strcpy(CRT.RDB$CONSTRAINT_NAME, constraint.name.c_str()); strcpy(CRT.RDB$RELATION_NAME, name.c_str()); switch (constraint.type) { case Constraint::TYPE_CHECK: strcpy(CRT.RDB$CONSTRAINT_TYPE, CHECK_CNSTRT); break; case Constraint::TYPE_NOT_NULL: strcpy(CRT.RDB$CONSTRAINT_TYPE, NOT_NULL_CNSTRT); break; case Constraint::TYPE_PK: strcpy(CRT.RDB$CONSTRAINT_TYPE, PRIMARY_KEY); break; case Constraint::TYPE_UNIQUE: strcpy(CRT.RDB$CONSTRAINT_TYPE, UNIQUE_CNSTRT); break; case Constraint::TYPE_FK: strcpy(CRT.RDB$CONSTRAINT_TYPE, FOREIGN_KEY); break; default: fb_assert(false); } // These constraints require creation of an index. switch (constraint.type) { case Constraint::TYPE_PK: case Constraint::TYPE_UNIQUE: case Constraint::TYPE_FK: { CreateIndexNode::Definition definition; definition.relation = name; definition.type = (constraint.type == Constraint::TYPE_PK ? isc_dyn_def_primary_key : (constraint.type == Constraint::TYPE_FK ? isc_dyn_def_foreign_key : 0)); definition.unique = constraint.type != Constraint::TYPE_FK; if (constraint.index->descending) definition.descending = true; definition.columns = constraint.columns; definition.refRelation = constraint.refRelation; definition.refColumns = constraint.refColumns; CreateIndexNode::store(tdbb, transaction, constraint.index->name, definition, &referredIndexName); CRT.RDB$INDEX_NAME.NULL = FALSE; strcpy(CRT.RDB$INDEX_NAME, constraint.index->name.c_str()); checkForeignKeyTempScope(tdbb, transaction, name, referredIndexName); // Check that we have references permissions on the table and // fields that the index:referredIndexName is on. SCL_check_index(tdbb, referredIndexName, 0, SCL_references); break; } } } END_STORE if (constraint.type == Constraint::TYPE_NOT_NULL) { fb_assert(constraint.columns.getCount() == 1); DYN_UTIL_store_check_constraints(tdbb, transaction, constraint.name, *constraint.columns.begin()); } // Define the automatically generated triggers. for (ObjectsArray::iterator trigger(constraint.triggers.begin()); trigger != constraint.triggers.end(); ++trigger) { trigger->store(tdbb, dsqlScratch, transaction); DYN_UTIL_store_check_constraints(tdbb, transaction, constraint.name, trigger->name); } if (constraint.type == Constraint::TYPE_NOT_NULL || constraint.type == Constraint::TYPE_CHECK) return; // Make sure unique field names were specified for UNIQUE/PRIMARY/FOREIGN // All fields must have the NOT NULL attribute specified for UNIQUE/PRIMARY. request.reset(tdbb, drq_c_unq_nam2, DYN_REQUESTS); int allCount = 0; int uniqueCount = 0; ObjectsArray fieldList; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) IDS IN RDB$INDEX_SEGMENTS CROSS RFR IN RDB$RELATION_FIELDS CROSS FLX IN RDB$FIELDS WITH IDS.RDB$INDEX_NAME EQ constraint.index->name.c_str() AND RFR.RDB$RELATION_NAME EQ name.c_str() AND RFR.RDB$FIELD_NAME EQ IDS.RDB$FIELD_NAME AND FLX.RDB$FIELD_NAME EQ RFR.RDB$FIELD_SOURCE REDUCED TO IDS.RDB$FIELD_NAME, IDS.RDB$INDEX_NAME, FLX.RDB$NULL_FLAG SORTED BY ASCENDING IDS.RDB$FIELD_NAME { if ((FLX.RDB$NULL_FLAG.NULL || !FLX.RDB$NULL_FLAG) && (RFR.RDB$NULL_FLAG.NULL || !RFR.RDB$NULL_FLAG) && constraint.type == Constraint::TYPE_PK) { // msg 123: "Field: %s not defined as NOT NULL - can't be used in PRIMARY KEY // constraint definition" status_exception::raise(Arg::PrivateDyn(123) << MetaName(RFR.RDB$FIELD_NAME)); } ++uniqueCount; fieldList.add() = IDS.RDB$FIELD_NAME; } END_FOR request.reset(tdbb, drq_n_idx_seg, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) IDS IN RDB$INDEX_SEGMENTS WITH IDS.RDB$INDEX_NAME EQ constraint.index->name.c_str() { ++allCount; } END_FOR if (uniqueCount != allCount) { // msg 124: "A column name is repeated in the definition of constraint: %s" status_exception::raise(Arg::PrivateDyn(124) << constraint.name); } if (constraint.type == Constraint::TYPE_FK) { request.reset(tdbb, drq_s_ref_con, DYN_REQUESTS); STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) REF IN RDB$REF_CONSTRAINTS { AutoCacheRequest request2(tdbb, drq_l_intg_con, DYN_REQUESTS); FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) CRT IN RDB$RELATION_CONSTRAINTS WITH CRT.RDB$INDEX_NAME EQ referredIndexName.c_str() AND (CRT.RDB$CONSTRAINT_TYPE = PRIMARY_KEY OR CRT.RDB$CONSTRAINT_TYPE = UNIQUE_CNSTRT) { fb_utils::exact_name_limit(CRT.RDB$CONSTRAINT_NAME, sizeof(CRT.RDB$CONSTRAINT_NAME)); strcpy(REF.RDB$CONST_NAME_UQ, CRT.RDB$CONSTRAINT_NAME); strcpy(REF.RDB$CONSTRAINT_NAME, constraint.name.c_str()); REF.RDB$UPDATE_RULE.NULL = FALSE; strcpy(REF.RDB$UPDATE_RULE, constraint.refUpdateAction); REF.RDB$DELETE_RULE.NULL = FALSE; strcpy(REF.RDB$DELETE_RULE, constraint.refDeleteAction); } END_FOR } END_STORE } else { // For PRIMARY KEY/UNIQUE constraints, make sure same set of columns // is not used in another constraint of either type. request.reset(tdbb, drq_c_dup_con, DYN_REQUESTS); MetaName indexName; int listIndex = -1; bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) CRT IN RDB$RELATION_CONSTRAINTS CROSS IDS IN RDB$INDEX_SEGMENTS OVER RDB$INDEX_NAME WITH CRT.RDB$RELATION_NAME EQ name.c_str() AND CRT.RDB$CONSTRAINT_NAME NE constraint.name.c_str() AND (CRT.RDB$CONSTRAINT_TYPE EQ PRIMARY_KEY OR CRT.RDB$CONSTRAINT_TYPE EQ UNIQUE_CNSTRT) SORTED BY CRT.RDB$INDEX_NAME, DESCENDING IDS.RDB$FIELD_NAME { if (indexName != CRT.RDB$INDEX_NAME) { if (listIndex >= 0) found = false; if (found) break; listIndex = fieldList.getCount() - 1; indexName = CRT.RDB$INDEX_NAME; found = true; } if (listIndex >= 0) { if (fieldList[listIndex--] != IDS.RDB$FIELD_NAME) found = false; } else found = false; } END_FOR if (listIndex >= 0) found = false; if (found) { // msg 126: "Same set of columns cannot be used in more than one PRIMARY KEY // and/or UNIQUE constraint definition" status_exception::raise(Arg::PrivateDyn(126)); } } } // Generate triggers to implement the CHECK clause, either at the field or table level. void RelationNode::defineCheckConstraint(DsqlCompilerScratch* dsqlScratch, Constraint& constraint, BoolSourceClause* clause) { // Create the INSERT trigger. defineCheckConstraintTrigger(dsqlScratch, constraint, clause, PRE_STORE_TRIGGER); // Create the UPDATE trigger. defineCheckConstraintTrigger(dsqlScratch, constraint, clause, PRE_MODIFY_TRIGGER); } // Define a check constraint trigger. void RelationNode::defineCheckConstraintTrigger(DsqlCompilerScratch* dsqlScratch, Constraint& constraint, BoolSourceClause* clause, FB_UINT64 triggerType) { thread_db* tdbb = JRD_get_thread_data(); MemoryPool& pool = *tdbb->getDefaultPool(); AutoSetRestore autoCheckConstraintTrigger(&dsqlScratch->checkConstraintTrigger, true); Constraint::BlrWriter& blrWriter = constraint.blrWritersHolder.add(); blrWriter.init(dsqlScratch); // Specify that the trigger should abort if the condition is not met. NestConst actionNode = FB_NEW(pool) CompoundStmtNode(pool); ExceptionNode* exceptionNode = FB_NEW(pool) ExceptionNode(pool, CHECK_CONSTRAINT_EXCEPTION); exceptionNode->exception->type = ExceptionItem::GDS_CODE; actionNode->statements.add(exceptionNode); // Generate the trigger blr. dsqlScratch->getBlrData().clear(); dsqlScratch->getDebugData().clear(); dsqlScratch->appendUChar(blr_begin); // Create the "OLD" and "NEW" contexts for the trigger -- the new one could be a dummy // place holder to avoid resolving fields to that context but prevent relations referenced // in the trigger actions from referencing the predefined "1" context. dsqlScratch->resetContextStack(); // CVC: I thought I could disable the OLD context here to avoid "ambiguous field name" // errors in pre_store and pre_modify triggers. Also, what sense can I make from NEW in // pre_delete? However, we clash at JRD with "no current record for fetch operation". dsqlNode->alias = OLD_CONTEXT_NAME; dsql_ctx* oldContext = PASS1_make_context(dsqlScratch, dsqlNode); oldContext->ctx_flags |= CTX_system; dsqlNode->alias = NEW_CONTEXT_NAME; dsql_ctx* newContext = PASS1_make_context(dsqlScratch, dsqlNode); newContext->ctx_flags |= CTX_system; // Generate the condition for firing the trigger. NotBoolNode* notNode = FB_NEW(pool) NotBoolNode(pool, clause->value); BoolExprNode* condition = notNode->dsqlPass(dsqlScratch); dsqlScratch->appendUChar(blr_if); GEN_expr(dsqlScratch, condition); // Generate the action statement for the trigger. Node::doDsqlPass(dsqlScratch, actionNode)->genBlr(dsqlScratch); dsqlScratch->appendUChar(blr_end); // of if (as there's no ELSE branch) dsqlScratch->appendUChar(blr_end); // of begin dsqlScratch->appendUChar(blr_eoc); // end of the blr dsqlScratch->resetContextStack(); // Move the blr to the constraint blrWriter. blrWriter.getBlrData().join(dsqlScratch->getBlrData()); TriggerDefinition& trigger = constraint.triggers.add(); trigger.systemFlag = fb_sysflag_check_constraint; trigger.relationName = name; trigger.type = triggerType; trigger.source = clause->source; trigger.blrData = blrWriter.getBlrData(); } // Define "on delete|update set default" trigger (for referential integrity) along with its blr. void RelationNode::defineSetDefaultTrigger(DsqlCompilerScratch* dsqlScratch, Constraint& constraint, bool onUpdate) { fb_assert(constraint.columns.getCount() == constraint.refColumns.getCount()); fb_assert(constraint.columns.hasData()); Constraint::BlrWriter& blrWriter = constraint.blrWritersHolder.add(); blrWriter.init(dsqlScratch); generateUnnamedTriggerBeginning(constraint, onUpdate, blrWriter); const int BLOB_BUFFER_SIZE = 4096; // to read in blr blob for default values UCHAR defaultVal[BLOB_BUFFER_SIZE]; for (ObjectsArray::const_iterator column(constraint.columns.begin()); column != constraint.columns.end(); ++column) { blrWriter.appendUChar(blr_assignment); // ASF: This is wrong way to do the thing. See CORE-3073. // Here stuff the default value as blr_literal .... or blr_null // if this column does not have an applicable default. // The default is determined in many cases: // (1) the info. for the column is in memory. (This is because // the column is being created in this ddl statement). // (1-a) the table has a column level default. We get this by // searching the dsql parse tree. // (1-b) the table does not have a column level default, but // has a domain default. We get the domain name from the dsql // parse tree and call METD_get_domain_default to read the // default from the system tables. // (2) The default-info for this column is not in memory (This is // because this is an alter table ddl statement). The table // already exists; therefore we get the column and/or domain // default value from the system tables by calling: // METD_get_col_default(). bool foundDefault = false; bool searchForDefault = true; // Search the parse tree to find the column for (NestConst* ptr = clauses.begin(); ptr != clauses.end(); ++ptr) { if ((*ptr)->type != Clause::TYPE_ADD_COLUMN) continue; AddColumnClause* clause = static_cast(ptr->getObject()); if (*column != clause->field->fld_name) continue; // Now we have the right column in the parse tree. case (1) above if (clause->defaultValue) { // case (1-a) above: There is a column level default dsqlScratch->getBlrData().clear(); dsqlScratch->getDebugData().clear(); GEN_expr(dsqlScratch, clause->defaultValue->value); foundDefault = true; searchForDefault = false; // Move the blr to the constraint blrWriter. blrWriter.getBlrData().join(dsqlScratch->getBlrData()); } else { if (!clause->field->typeOfName.hasData()) break; // case: (1-b): Domain name is available. Column level default // is not declared. So get the domain default. const USHORT defaultLen = METD_get_domain_default(dsqlScratch->getTransaction(), clause->field->typeOfName, &foundDefault, defaultVal, sizeof(defaultVal)); searchForDefault = false; if (foundDefault) stuffDefaultBlr(ByteChunk(defaultVal, defaultLen), blrWriter); else { // Neither column level nor domain level default exists. blrWriter.appendUChar(blr_null); } } break; } if (searchForDefault) { // case 2: See if the column/domain has already been created. const USHORT defaultLen = METD_get_col_default(dsqlScratch->getTransaction(), name.c_str(), column->c_str(), &foundDefault, defaultVal, sizeof(defaultVal)); if (foundDefault) stuffDefaultBlr(ByteChunk(defaultVal, defaultLen), blrWriter); else blrWriter.appendUChar(blr_null); } // The context for the foreign key relation. blrWriter.appendUChar(blr_field); blrWriter.appendUChar(2); blrWriter.appendNullString(0, column->c_str()); } blrWriter.appendUChar(blr_end); if (onUpdate) blrWriter.appendUCharRepeated(blr_end, 3); blrWriter.appendUChar(blr_eoc); // end of the blr TriggerDefinition& trigger = constraint.triggers.add(); trigger.systemFlag = fb_sysflag_referential_constraint; trigger.fkTrigger = true; trigger.relationName = constraint.refRelation; trigger.type = (onUpdate ? POST_MODIFY_TRIGGER : POST_ERASE_TRIGGER); trigger.blrData = blrWriter.getBlrData(); } // Define "on delete/update set null" trigger (for referential integrity). // The trigger blr is the same for both the delete and update cases. Only difference is its // TRIGGER_TYPE (ON DELETE or ON UPDATE). // When onUpdate parameter == true is an update trigger. void RelationNode::defineSetNullTrigger(DsqlCompilerScratch* dsqlScratch, Constraint& constraint, bool onUpdate) { fb_assert(constraint.columns.getCount() == constraint.refColumns.getCount()); fb_assert(constraint.columns.hasData()); Constraint::BlrWriter& blrWriter = constraint.blrWritersHolder.add(); blrWriter.init(dsqlScratch); generateUnnamedTriggerBeginning(constraint, onUpdate, blrWriter); for (ObjectsArray::const_iterator column(constraint.columns.begin()); column != constraint.columns.end(); ++column) { blrWriter.appendUChar(blr_assignment); blrWriter.appendUChar(blr_null); blrWriter.appendUChar(blr_field); blrWriter.appendUChar(2); blrWriter.appendNullString(0, column->c_str()); } blrWriter.appendUChar(blr_end); if (onUpdate) blrWriter.appendUCharRepeated(blr_end, 3); blrWriter.appendUChar(blr_eoc); // end of the blr TriggerDefinition& trigger = constraint.triggers.add(); trigger.systemFlag = fb_sysflag_referential_constraint; trigger.fkTrigger = true; trigger.relationName = constraint.refRelation; trigger.type = (onUpdate ? POST_MODIFY_TRIGGER : POST_ERASE_TRIGGER); trigger.blrData = blrWriter.getBlrData(); } // Define "on delete cascade" trigger (for referential integrity) along with the trigger blr. void RelationNode::defineDeleteCascadeTrigger(DsqlCompilerScratch* dsqlScratch, Constraint& constraint) { fb_assert(constraint.columns.getCount() == constraint.refColumns.getCount()); fb_assert(constraint.columns.hasData()); Constraint::BlrWriter& blrWriter = constraint.blrWritersHolder.add(); blrWriter.init(dsqlScratch); blrWriter.appendUChar(blr_for); blrWriter.appendUChar(blr_rse); // The context for the prim. key relation. blrWriter.appendUChar(1); blrWriter.appendUChar(blr_relation); blrWriter.appendNullString(0, name.c_str()); // The context for the foreign key relation. blrWriter.appendUChar(2); // Generate the blr for: foreign_key == primary_key. stuffMatchingBlr(constraint, blrWriter); blrWriter.appendUChar(blr_erase); blrWriter.appendUChar(2); blrWriter.appendUChar(blr_eoc); // end of the blr TriggerDefinition& trigger = constraint.triggers.add(); trigger.systemFlag = fb_sysflag_referential_constraint; trigger.fkTrigger = true; trigger.relationName = constraint.refRelation; trigger.type = POST_ERASE_TRIGGER; trigger.blrData = blrWriter.getBlrData(); } // Define "on update cascade" trigger (for referential integrity) along with the trigger blr. void RelationNode::defineUpdateCascadeTrigger(DsqlCompilerScratch* dsqlScratch, Constraint& constraint) { fb_assert(constraint.columns.getCount() == constraint.refColumns.getCount()); fb_assert(constraint.columns.hasData()); Constraint::BlrWriter& blrWriter = constraint.blrWritersHolder.add(); blrWriter.init(dsqlScratch); generateUnnamedTriggerBeginning(constraint, true, blrWriter); for (ObjectsArray::const_iterator column(constraint.columns.begin()), refColumn(constraint.refColumns.begin()); column != constraint.columns.end(); ++column, ++refColumn) { blrWriter.appendUChar(blr_assignment); blrWriter.appendUChar(blr_field); blrWriter.appendUChar(1); blrWriter.appendNullString(0, refColumn->c_str()); blrWriter.appendUChar(blr_field); blrWriter.appendUChar(2); blrWriter.appendNullString(0, column->c_str()); } blrWriter.appendUCharRepeated(blr_end, 4); blrWriter.appendUChar(blr_eoc); // end of the blr TriggerDefinition& trigger = constraint.triggers.add(); trigger.systemFlag = fb_sysflag_referential_constraint; trigger.fkTrigger = true; trigger.relationName = constraint.refRelation; trigger.type = POST_MODIFY_TRIGGER; trigger.blrData = blrWriter.getBlrData(); } // Common code factored out. void RelationNode::generateUnnamedTriggerBeginning(Constraint& constraint, bool onUpdate, BlrDebugWriter& blrWriter) { // For ON UPDATE TRIGGER only: generate the trigger firing condition: // If prim_key.old_value != prim_key.new value. // Note that the key could consist of multiple columns. if (onUpdate) { stuffTriggerFiringCondition(constraint, blrWriter); blrWriter.appendUCharRepeated(blr_begin, 2); } blrWriter.appendUChar(blr_for); blrWriter.appendUChar(blr_rse); // The context for the prim. key relation. blrWriter.appendUChar(1); blrWriter.appendUChar(blr_relation); blrWriter.appendNullString(0, name.c_str()); // The context for the foreign key relation. blrWriter.appendUChar(2); // Generate the blr for: foreign_key == primary_key. stuffMatchingBlr(constraint, blrWriter); blrWriter.appendUChar(blr_modify); blrWriter.appendUChar(2); blrWriter.appendUChar(2); blrWriter.appendUChar(blr_begin); } // The defaultBlr passed is of the form: // blr_version4 blr_literal ..... blr_eoc. // Strip the blr_version4 and blr_eoc verbs and stuff the remaining blr in the blr stream in the // statement. void RelationNode::stuffDefaultBlr(const ByteChunk& defaultBlr, BlrDebugWriter& blrWriter) { fb_assert(defaultBlr.length > 2 && defaultBlr.data[defaultBlr.length - 1] == blr_eoc); fb_assert(*defaultBlr.data == blr_version4 || *defaultBlr.data == blr_version5); blrWriter.appendBytes(defaultBlr.data + 1, defaultBlr.length - 2); } // Generate blr to express: foreign_key == primary_key // ie., for_key.column_1 = prim_key.column_1 and // for_key.column_2 = prim_key.column_2 and .... so on. void RelationNode::stuffMatchingBlr(Constraint& constraint, BlrDebugWriter& blrWriter) { // count of foreign key columns fb_assert(constraint.refColumns.getCount() == constraint.columns.getCount()); fb_assert(constraint.refColumns.getCount() != 0); blrWriter.appendUChar(blr_boolean); size_t numFields = 0; for (ObjectsArray::const_iterator column(constraint.columns.begin()), refColumn(constraint.refColumns.begin()); column != constraint.columns.end(); ++column, ++refColumn, ++numFields) { if (numFields + 1 < constraint.columns.getCount()) blrWriter.appendUChar(blr_and); blrWriter.appendUChar(blr_eql); blrWriter.appendUChar(blr_field); blrWriter.appendUChar(2); blrWriter.appendNullString(0, column->c_str()); blrWriter.appendUChar(blr_field); blrWriter.appendUChar(0); blrWriter.appendNullString(0, refColumn->c_str()); }; blrWriter.appendUChar(blr_end); } // Generate blr to express: if (old.primary_key != new.primary_key). // Do a column by column comparison. void RelationNode::stuffTriggerFiringCondition(const Constraint& constraint, BlrDebugWriter& blrWriter) { blrWriter.appendUChar(blr_if); size_t numFields = 0; for (ObjectsArray::const_iterator column(constraint.refColumns.begin()); column != constraint.refColumns.end(); ++column, ++numFields) { if (numFields + 1 < constraint.refColumns.getCount()) blrWriter.appendUChar(blr_or); blrWriter.appendUChar(blr_neq); blrWriter.appendUChar(blr_field); blrWriter.appendUChar(0); blrWriter.appendNullString(0, column->c_str()); blrWriter.appendUChar(blr_field); blrWriter.appendUChar(1); blrWriter.appendNullString(0, column->c_str()); } } //---------------------- void CreateRelationNode::print(string& text) const { text.printf( "CreateRelationNode\n" " name: '%s'\n", name.c_str()); } void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { saveRelation(tdbb, dsqlScratch, name, false, true); if (externalFile) { fb_assert(dsqlScratch->relation); dsqlScratch->relation->rel_flags |= REL_external; } // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_TABLE, name); DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_relation); checkRelationTempScope(tdbb, transaction, name, relationType); AutoCacheRequest request(tdbb, drq_s_rels2, DYN_REQUESTS); STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) REL IN RDB$RELATIONS { strcpy(REL.RDB$RELATION_NAME, name.c_str()); REL.RDB$SYSTEM_FLAG = 0; REL.RDB$FLAGS = REL_sql; REL.RDB$RELATION_TYPE = relationType; REL.RDB$VIEW_BLR.NULL = TRUE; REL.RDB$VIEW_SOURCE.NULL = TRUE; REL.RDB$EXTERNAL_FILE.NULL = TRUE; if (externalFile) { if (externalFile->length() >= sizeof(REL.RDB$EXTERNAL_FILE)) status_exception::raise(Arg::Gds(isc_dyn_name_longer)); REL.RDB$EXTERNAL_FILE.NULL = FALSE; strcpy(REL.RDB$EXTERNAL_FILE, externalFile->c_str()); if (ISC_check_if_remote(externalFile->c_str(), false)) status_exception::raise(Arg::PrivateDyn(163)); // Check for any path, present in filename. // If miss it, file will be searched in External Tables Dirs, // that's why no expand_filename required. PathName path, file; PathUtils::splitLastComponent(path, file, externalFile->c_str()); if (path.hasData()) // path component present in filename { ISC_expand_filename(REL.RDB$EXTERNAL_FILE, strlen(REL.RDB$EXTERNAL_FILE), REL.RDB$EXTERNAL_FILE, sizeof(REL.RDB$EXTERNAL_FILE), false); } REL.RDB$RELATION_TYPE = rel_external; } } END_STORE storePrivileges(tdbb, transaction, name, obj_relation, ALL_PRIVILEGES); ObjectsArray constraints; const ObjectsArray* pkCols = findPkColumns(); SSHORT position = 0; for (NestConst* i = clauses.begin(); i != clauses.end(); ++i) { switch ((*i)->type) { case Clause::TYPE_ADD_COLUMN: defineField(tdbb, dsqlScratch, transaction, static_cast(i->getObject()), position, pkCols); ++position; break; case Clause::TYPE_ADD_CONSTRAINT: makeConstraint(tdbb, dsqlScratch, transaction, static_cast(i->getObject()), constraints); break; default: fb_assert(false); break; } } for (ObjectsArray::iterator constraint(constraints.begin()); constraint != constraints.end(); ++constraint) { defineConstraint(tdbb, dsqlScratch, transaction, *constraint); } dsqlScratch->relation->rel_flags &= ~REL_creating; executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_TABLE, name); savePoint.release(); // everything is ok // Update DSQL cache METD_drop_relation(transaction, name); MET_dsql_cache_release(tdbb, SYM_relation, name); } // Starting from the elements in a table definition, locate the PK columns if given in a // separate table constraint declaration. const ObjectsArray* CreateRelationNode::findPkColumns() { for (const NestConst* i = clauses.begin(); i != clauses.end(); ++i) { if ((*i)->type == Clause::TYPE_ADD_CONSTRAINT) { const AddConstraintClause* clause = static_cast(i->getObject()); if (clause->constraintType == AddConstraintClause::CTYPE_PK) return &clause->columns; } } return NULL; } //---------------------- void AlterRelationNode::print(string& text) const { text.printf( "AlterRelationNode\n" " name: '%s'\n", name.c_str()); } void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { saveRelation(tdbb, dsqlScratch, name, false, false); if (!dsqlScratch->relation) { //// TODO: /*** char linecol[64]; sprintf(linecol, "At line %d, column %d.", (int) dsqlNode->line, (int) dsqlNode->column); ***/ status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_dsql_relation_err) << Arg::Gds(isc_random) << name /***<< Arg::Gds(isc_random) << linecol***/); } // If there is an error, get rid of the cached data. try { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_TABLE, name); ObjectsArray constraints; for (NestConst* i = clauses.begin(); i != clauses.end(); ++i) { switch ((*i)->type) { case Clause::TYPE_ADD_COLUMN: defineField(tdbb, dsqlScratch, transaction, static_cast(i->getObject()), -1, NULL); break; case Clause::TYPE_ALTER_COL_TYPE: modifyField(tdbb, dsqlScratch, transaction, static_cast(i->getObject())); break; case Clause::TYPE_ALTER_COL_NAME: { const AlterColNameClause* clause = static_cast(i->getObject()); AutoRequest request; bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) RFL IN RDB$RELATION_FIELDS WITH RFL.RDB$FIELD_NAME EQ clause->fromName.c_str() AND RFL.RDB$RELATION_NAME EQ name.c_str() { found = true; MODIFY RFL checkViewDependency(tdbb, transaction, name, clause->fromName); checkSpTrigDependency(tdbb, transaction, name, clause->fromName); if (!fieldExists(tdbb, transaction, name, clause->toName)) { strcpy(RFL.RDB$FIELD_NAME, clause->toName.c_str()); AlterDomainNode::modifyLocalFieldIndex(tdbb, transaction, name, clause->fromName, clause->toName); } else { // msg 205: Cannot rename field %s to %s. A field with that name // already exists in table %s. status_exception::raise( Arg::PrivateDyn(205) << clause->fromName << clause->toName << name); } END_MODIFY } END_FOR if (!found) { // msg 176: "column %s does not exist in table/view %s" status_exception::raise(Arg::PrivateDyn(176) << clause->fromName << name); } break; } case Clause::TYPE_ALTER_COL_NULL: { const AlterColNullClause* clause = static_cast(i->getObject()); //// FIXME: This clause allows inconsistencies like setting a field with a //// not null CONSTRAINT as nullable, or setting a field based on a not null //// domain as nullable (while ISQL shows it as not null). AutoRequest request; bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) RFL IN RDB$RELATION_FIELDS WITH RFL.RDB$FIELD_NAME EQ clause->name.c_str() AND RFL.RDB$RELATION_NAME EQ name.c_str() { found = true; MODIFY RFL if (!clause->notNullFlag && !RFL.RDB$GENERATOR_NAME.NULL) { // msg 274: Identity column @1 of table @2 cannot be changed to NULLable status_exception::raise(Arg::PrivateDyn(274) << clause->name << name); } RFL.RDB$NULL_FLAG = SSHORT(clause->notNullFlag); END_MODIFY } END_FOR if (!found) { // msg 176: "column %s does not exist in table/view %s" status_exception::raise(Arg::PrivateDyn(176) << clause->name << name); } break; } case Clause::TYPE_ALTER_COL_POS: { const AlterColPosClause* clause = static_cast(i->getObject()); // CVC: Since now the parser accepts pos=1..N, let's subtract one here. const SSHORT pos = clause->newPos - 1; AutoRequest request; bool found = false; SSHORT oldPos; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) RFL IN RDB$RELATION_FIELDS WITH RFL.RDB$FIELD_NAME EQ clause->name.c_str() AND RFL.RDB$RELATION_NAME EQ name.c_str() { found = true; oldPos = RFL.RDB$FIELD_POSITION; } END_FOR if (!found) { // msg 176: "column %s does not exist in table/view %s" status_exception::raise(Arg::PrivateDyn(176) << clause->name << name); } if (pos != oldPos) modifyLocalFieldPosition(tdbb, transaction, name, clause->name, pos, oldPos); break; } case Clause::TYPE_DROP_COLUMN: { // Fix for bug 8054: // [CASCADE | RESTRICT] syntax is available in IB4.5, but not // required until v5.0. // // Option CASCADE causes an error: unsupported DSQL construct. // Option RESTRICT is default behaviour. const DropColumnClause* clause = static_cast(i->getObject()); if (clause->cascade) { // Unsupported DSQL construct status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-901) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_construct_err)); } deleteLocalField(tdbb, transaction, name, clause->name); break; } case Clause::TYPE_ADD_CONSTRAINT: makeConstraint(tdbb, dsqlScratch, transaction, static_cast(i->getObject()), constraints); break; case Clause::TYPE_DROP_CONSTRAINT: { const MetaName& constraintName = static_cast(i->getObject())->name; AutoCacheRequest request(tdbb, drq_e_rel_con, DYN_REQUESTS); bool found = false; 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; ERASE RC; } END_FOR if (!found) { // msg 130: "CONSTRAINT %s does not exist." status_exception::raise(Arg::PrivateDyn(130) << constraintName); } break; } default: fb_assert(false); break; } } for (ObjectsArray::iterator constraint(constraints.begin()); constraint != constraints.end(); ++constraint) { defineConstraint(tdbb, dsqlScratch, transaction, *constraint); } executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_TABLE, name); savePoint.release(); // everything is ok // Update DSQL cache METD_drop_relation(transaction, name); MET_dsql_cache_release(tdbb, SYM_relation, name); } catch (const Exception&) { METD_drop_relation(transaction, name); dsqlScratch->relation = NULL; throw; } } // Modify a field, as part of an alter table statement. // // If there are dependencies on the field, abort the operation // unless the dependency is an index. In this case, rebuild the // index once the operation has completed. // // If the original datatype of the field was a domain: // if the new type is a domain, just make the change to the new domain // if it exists // // if the new type is a base type, just make the change // // If the original datatype of the field was a base type: // if the new type is a base type, just make the change // // if the new type is a domain, make the change to the field // definition and remove the entry for RDB$FIELD_SOURCE from the original // field. In other words ... clean up after ourselves // // The following conversions are not allowed: // Blob to anything // Array to anything // Date to anything // Char to any numeric // Varchar to any numeric // Anything to Blob // Anything to Array void AlterRelationNode::modifyField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction, AlterColTypeClause* clause) { Attachment* const attachment = transaction->tra_attachment; dsql_fld* field = clause->field; // Add the field to the relation being defined for parsing purposes. bool permanent = false; dsql_rel* relation = dsqlScratch->relation; if (relation) { if (!(relation->rel_flags & REL_new_relation)) { dsql_fld* permField = FB_NEW(dsqlScratch->getAttachment()->dbb_pool) dsql_fld(dsqlScratch->getAttachment()->dbb_pool); *permField = *field; field = permField; permanent = true; } field->fld_next = relation->rel_fields; relation->rel_fields = field; } try { bool found = false; AutoRequest request; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) RFR IN RDB$RELATION_FIELDS CROSS REL IN RDB$RELATIONS CROSS FLD IN RDB$FIELDS WITH REL.RDB$RELATION_NAME = RFR.RDB$RELATION_NAME AND FLD.RDB$FIELD_NAME = RFR.RDB$FIELD_SOURCE AND RFR.RDB$RELATION_NAME = name.c_str() AND RFR.RDB$FIELD_NAME = field->fld_name.c_str() { found = true; bool isView = !REL.RDB$VIEW_BLR.NULL; if (!isView && (!FLD.RDB$COMPUTED_BLR.NULL != (clause->computed != NULL))) { // Cannot add or remove COMPUTED from column @1 status_exception::raise(Arg::PrivateDyn(249) << field->fld_name); } dyn_fld origDom; DSC_make_descriptor(&origDom.dyn_dsc, FLD.RDB$FIELD_TYPE, FLD.RDB$FIELD_SCALE, FLD.RDB$FIELD_LENGTH, FLD.RDB$FIELD_SUB_TYPE, FLD.RDB$CHARACTER_SET_ID, FLD.RDB$COLLATION_ID); origDom.dyn_fld_name = field->fld_name; origDom.dyn_charbytelen = FLD.RDB$FIELD_LENGTH; origDom.dyn_dtype = FLD.RDB$FIELD_TYPE; origDom.dyn_precision = FLD.RDB$FIELD_PRECISION; origDom.dyn_sub_type = FLD.RDB$FIELD_SUB_TYPE; origDom.dyn_charlen = FLD.RDB$CHARACTER_LENGTH; origDom.dyn_collation = FLD.RDB$COLLATION_ID; origDom.dyn_null_flag = !FLD.RDB$NULL_FLAG.NULL && FLD.RDB$NULL_FLAG != 0; origDom.dyn_fld_source = RFR.RDB$FIELD_SOURCE; // If the original field type is an array, force its blr type to blr_blob. const bool hasDimensions = FLD.RDB$DIMENSIONS != 0; if (hasDimensions) origDom.dyn_dtype = blr_blob; const bool wasInternalDomain = fb_utils::implicit_domain(origDom.dyn_fld_source.c_str()) && RFR.RDB$BASE_FIELD.NULL; string computedSource; BlrDebugWriter::BlrData computedValue; if (clause->computed) { defineComputed(dsqlScratch, dsqlNode, field, clause->computed, computedSource, computedValue); } if (clause->defaultValue) { MODIFY RFR if (!RFR.RDB$GENERATOR_NAME.NULL) { // msg 275: Identity column @1 of table @2 cannot have default value status_exception::raise(Arg::PrivateDyn(275) << field->fld_name << name); } if (hasDimensions) { // msg 225: "Default value is not allowed for array type in field %s" status_exception::raise(Arg::PrivateDyn(225) << field->fld_name); } if (clause->computed) { // msg 233: "Local column %s is computed, cannot set a default value" status_exception::raise(Arg::PrivateDyn(233) << field->fld_name); } string defaultSource; BlrDebugWriter::BlrData defaultValue; defineDefault(tdbb, dsqlScratch, field, clause->defaultValue, defaultSource, defaultValue); RFR.RDB$DEFAULT_SOURCE.NULL = FALSE; attachment->storeMetaDataBlob(tdbb, transaction, &RFR.RDB$DEFAULT_SOURCE, defaultSource); RFR.RDB$DEFAULT_VALUE.NULL = FALSE; attachment->storeBinaryBlob(tdbb, transaction, &RFR.RDB$DEFAULT_VALUE, defaultValue); END_MODIFY } else if (clause->dropDefault) { MODIFY RFR if (RFR.RDB$DEFAULT_VALUE.NULL) { if (FLD.RDB$DEFAULT_VALUE.NULL) { // msg 229: "Local column %s doesn't have a default" status_exception::raise(Arg::PrivateDyn(229) << field->fld_name); } else { // msg 230: "Local column %s default belongs to domain %s" status_exception::raise( Arg::PrivateDyn(230) << field->fld_name << MetaName(FLD.RDB$FIELD_NAME)); } } else { RFR.RDB$DEFAULT_SOURCE.NULL = TRUE; RFR.RDB$DEFAULT_VALUE.NULL = TRUE; } END_MODIFY } else if (clause->identityRestart) { bool found = false; AutoRequest request2; FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) GEN IN RDB$GENERATORS WITH GEN.RDB$GENERATOR_NAME EQ RFR.RDB$GENERATOR_NAME { const SLONG id = GEN.RDB$GENERATOR_ID; const MetaName genName(RFR.RDB$GENERATOR_NAME); const SINT64 val = clause->identityRestartValue.specified ? clause->identityRestartValue.value : (!GEN.RDB$INITIAL_VALUE.NULL ? GEN.RDB$INITIAL_VALUE : 0); transaction->getGenIdCache()->put(id, val); dsc desc; desc.makeText((USHORT) genName.length(), ttype_metadata, (UCHAR*) genName.c_str()); DFW_post_work(transaction, dfw_set_generator, &desc, id); found = true; } END_FOR if (!found) { // msg 285: "Column @1 is not an identity column" status_exception::raise(Arg::PrivateDyn(285) << field->fld_name); } } else { // We have the type. Default and type/domain are exclusive for now. MetaName newDomainName; dyn_fld newDom; if (field->typeOfName.hasData()) { // Case a1: Internal domain -> domain. // Case a2: Domain -> domain. newDomainName = field->typeOfName; if (fb_utils::implicit_domain(newDomainName.c_str())) { // msg 224: "Cannot use the internal domain %s as new type for field %s". status_exception::raise( Arg::PrivateDyn(224) << newDomainName << field->fld_name); } // Get the domain information. if (!METD_get_domain(dsqlScratch->getTransaction(), field, newDomainName)) { // Specified domain or source field does not exist. status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_domain_not_found) << newDomainName); } DDL_resolve_intl_type(dsqlScratch, field, NULL); // If the original definition was a base field type, remove the // entries from RDB$FIELDS. if (wasInternalDomain) { // Case a1: Internal domain -> domain. ERASE FLD; } } else { // Case b1: Internal domain -> internal domain. // Case b2: Domain -> internal domain. // If COMPUTED was specified but the type wasn't, we use the type of // the computed expression. if (clause->computed && field->dtype == dtype_unknown) { dsc desc; MAKE_desc(dsqlScratch, &desc, clause->computed->value); field->dtype = desc.dsc_dtype; field->length = desc.dsc_length; field->scale = desc.dsc_scale; if (field->dtype <= dtype_any_text) { field->charSetId = DSC_GET_CHARSET(&desc); field->collationId = DSC_GET_COLLATE(&desc); } else field->subType = desc.dsc_sub_type; } field->resolve(dsqlScratch, true); if (wasInternalDomain) // Case b1: Internal domain -> internal domain. { MODIFY FLD updateRdbFields(field, FLD.RDB$FIELD_TYPE, FLD.RDB$FIELD_LENGTH, FLD.RDB$FIELD_SUB_TYPE.NULL, FLD.RDB$FIELD_SUB_TYPE, FLD.RDB$FIELD_SCALE.NULL, FLD.RDB$FIELD_SCALE, FLD.RDB$CHARACTER_SET_ID.NULL, FLD.RDB$CHARACTER_SET_ID, FLD.RDB$CHARACTER_LENGTH.NULL, FLD.RDB$CHARACTER_LENGTH, FLD.RDB$FIELD_PRECISION.NULL, FLD.RDB$FIELD_PRECISION, FLD.RDB$COLLATION_ID.NULL, FLD.RDB$COLLATION_ID, FLD.RDB$SEGMENT_LENGTH.NULL, FLD.RDB$SEGMENT_LENGTH); END_MODIFY newDom.dyn_fld_source = origDom.dyn_fld_source; } else // Case b2: Domain -> internal domain. storeGlobalField(tdbb, transaction, newDomainName, field); } if (!clause->computed && !isView) { if (newDomainName.hasData()) newDom.dyn_fld_source = newDomainName; AlterDomainNode::getDomainType(tdbb, transaction, newDom); AlterDomainNode::checkUpdate(origDom, newDom); if (!RFR.RDB$GENERATOR_NAME.NULL) { if (!newDom.dyn_dsc.isExact() || newDom.dyn_dsc.dsc_scale != 0) { // Identity column @1 of table @2 must be exact numeric with zero scale. status_exception::raise(Arg::PrivateDyn(273) << field->fld_name << name); } } } if (newDomainName.hasData()) { MODIFY RFR USING RFR.RDB$FIELD_SOURCE.NULL = FALSE; strcpy(RFR.RDB$FIELD_SOURCE, newDomainName.c_str()); if (clause->computed) { RFR.RDB$UPDATE_FLAG.NULL = FALSE; RFR.RDB$UPDATE_FLAG = 1; } RFR.RDB$COLLATION_ID.NULL = TRUE; // CORE-2426 END_MODIFY } } if (clause->computed) { // We can alter FLD directly here because if we are setting a computed expression, // it means the field already was computed. And if it was, it should be the // "b1 case", where the field source does not change. // This assumption may change, especially when this function starts dealing // with views. MODIFY FLD FLD.RDB$COMPUTED_SOURCE.NULL = FALSE; attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$COMPUTED_SOURCE, computedSource); FLD.RDB$COMPUTED_BLR.NULL = FALSE; attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$COMPUTED_BLR, computedValue); END_MODIFY } AutoRequest request2; FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) PRM IN RDB$PROCEDURE_PARAMETERS WITH PRM.RDB$RELATION_NAME = name.c_str() AND PRM.RDB$FIELD_NAME = field->fld_name.c_str() { MODIFY PRM USING strcpy(PRM.RDB$FIELD_SOURCE, RFR.RDB$FIELD_SOURCE); END_MODIFY } END_FOR request2.reset(); FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) ARG IN RDB$FUNCTION_ARGUMENTS WITH ARG.RDB$RELATION_NAME = name.c_str() AND ARG.RDB$FIELD_NAME = field->fld_name.c_str() { MODIFY ARG USING strcpy(ARG.RDB$FIELD_SOURCE, RFR.RDB$FIELD_SOURCE); END_MODIFY } END_FOR } END_FOR if (!found) { // msg 176: "column %s does not exist in table/view %s" status_exception::raise(Arg::PrivateDyn(176) << field->fld_name << name); } // Update any indices that exist. AlterDomainNode::modifyLocalFieldIndex(tdbb, transaction, name, field->fld_name, field->fld_name); } catch (const Exception&) { clearPermanentField(relation, permanent); throw; } clearPermanentField(relation, permanent); } //---------------------- // Delete a global field if it's not used in others objects. void DropRelationNode::deleteGlobalField(thread_db* tdbb, jrd_tra* transaction, const MetaName& globalName) { AutoCacheRequest request(tdbb, drq_e_l_gfld, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FLD IN RDB$FIELDS WITH FLD.RDB$FIELD_NAME EQ globalName.c_str() AND FLD.RDB$VALIDATION_SOURCE MISSING AND FLD.RDB$NULL_FLAG MISSING AND FLD.RDB$DEFAULT_SOURCE MISSING AND FLD.RDB$FIELD_NAME STARTING WITH IMPLICIT_DOMAIN_PREFIX AND (NOT ANY RFR IN RDB$RELATION_FIELDS WITH RFR.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME) AND (NOT ANY PRM IN RDB$PROCEDURE_PARAMETERS WITH PRM.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME) AND (NOT ANY ARG IN RDB$FUNCTION_ARGUMENTS WITH ARG.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME) { DropDomainNode::deleteDimensionRecords(tdbb, transaction, globalName); ERASE FLD; } END_FOR } void DropRelationNode::print(string& text) const { text.printf( "DropRelationNode\n" " name: '%s'\n", name.c_str()); } void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { jrd_rel* rel_drop = MET_lookup_relation(tdbb, name); if (rel_drop) MET_scan_relation(tdbb, rel_drop); const dsql_rel* relation = METD_get_relation(transaction, dsqlScratch, name); if (!relation && silent) return; // Check that DROP TABLE is dropping a table and that DROP VIEW is dropping a view. if (view) { if (!relation || (relation && !(relation->rel_flags & REL_view))) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_view_not_found) << name); } } else { if (!relation || (relation && (relation->rel_flags & REL_view))) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_table_not_found) << name); } } const int ddlTriggerAction = (view ? DDL_TRIGGER_DROP_VIEW : DDL_TRIGGER_DROP_TABLE); // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); AutoCacheRequest request(tdbb, drq_l_relation, DYN_REQUESTS); bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) R IN RDB$RELATIONS WITH R.RDB$RELATION_NAME EQ name.c_str() { executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlTriggerAction, name); found = true; } END_FOR request.reset(tdbb, drq_e_rel_con2, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) CRT IN RDB$RELATION_CONSTRAINTS WITH CRT.RDB$RELATION_NAME EQ name.c_str() AND (CRT.RDB$CONSTRAINT_TYPE EQ PRIMARY_KEY OR CRT.RDB$CONSTRAINT_TYPE EQ UNIQUE_CNSTRT OR CRT.RDB$CONSTRAINT_TYPE EQ FOREIGN_KEY) SORTED BY ASCENDING CRT.RDB$CONSTRAINT_TYPE { ERASE CRT; } END_FOR request.reset(tdbb, drq_e_rel_idxs, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) IDX IN RDB$INDICES WITH IDX.RDB$RELATION_NAME EQ name.c_str() { DropIndexNode::deleteSegmentRecords(tdbb, transaction, IDX.RDB$INDEX_NAME); ERASE IDX; } END_FOR request.reset(tdbb, drq_e_trg_msgs2, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) TM IN RDB$TRIGGER_MESSAGES CROSS T IN RDB$TRIGGERS WITH T.RDB$RELATION_NAME EQ name.c_str() AND TM.RDB$TRIGGER_NAME EQ T.RDB$TRIGGER_NAME { ERASE TM; } END_FOR // CVC: Moved this block here to avoid SF Bug #1111570. request.reset(tdbb, drq_e_rel_con3, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) CRT IN RDB$RELATION_CONSTRAINTS WITH CRT.RDB$RELATION_NAME EQ name.c_str() AND (CRT.RDB$CONSTRAINT_TYPE EQ CHECK_CNSTRT OR CRT.RDB$CONSTRAINT_TYPE EQ NOT_NULL_CNSTRT) { ERASE CRT; } END_FOR request.reset(tdbb, drq_e_rel_flds, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) RFR IN RDB$RELATION_FIELDS WITH RFR.RDB$RELATION_NAME EQ name.c_str() { if (!RFR.RDB$GENERATOR_NAME.NULL) DropSequenceNode::deleteIdentity(tdbb, transaction, RFR.RDB$GENERATOR_NAME); ERASE RFR; if (!RFR.RDB$SECURITY_CLASS.NULL && !strncmp(RFR.RDB$SECURITY_CLASS, SQL_SECCLASS_PREFIX, SQL_SECCLASS_PREFIX_LEN)) { deleteSecurityClass(tdbb, transaction, RFR.RDB$SECURITY_CLASS); } deleteGlobalField(tdbb, transaction, RFR.RDB$FIELD_SOURCE); } END_FOR request.reset(tdbb, drq_e_view_rels, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) VR IN RDB$VIEW_RELATIONS WITH VR.RDB$VIEW_NAME EQ name.c_str() { ERASE VR; } END_FOR request.reset(tdbb, drq_e_relation, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) R IN RDB$RELATIONS WITH R.RDB$RELATION_NAME EQ name.c_str() { ERASE R; if (!R.RDB$SECURITY_CLASS.NULL && !strncmp(R.RDB$SECURITY_CLASS, SQL_SECCLASS_PREFIX, SQL_SECCLASS_PREFIX_LEN)) { deleteSecurityClass(tdbb, transaction, R.RDB$SECURITY_CLASS); } } END_FOR if (!found) { // msg 61: "Relation not found" status_exception::raise(Arg::PrivateDyn(61)); } // Triggers must be deleted after check constraints MetaName triggerName; request.reset(tdbb, drq_e_trigger2, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$TRIGGERS WITH X.RDB$RELATION_NAME EQ name.c_str() { triggerName = X.RDB$TRIGGER_NAME; ERASE X; AutoCacheRequest request2(tdbb, drq_e_trg_prv, DYN_REQUESTS); FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$USER EQ triggerName.c_str() AND PRIV.RDB$USER_TYPE = obj_trigger { ERASE PRIV; } END_FOR } END_FOR request.reset(tdbb, drq_e_usr_prvs, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND PRIV.RDB$OBJECT_TYPE = obj_relation { ERASE PRIV; } END_FOR request.reset(tdbb, drq_e_view_prv, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$USER EQ name.c_str() AND PRIV.RDB$USER_TYPE = obj_view { ERASE PRIV; } END_FOR if (found) executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, ddlTriggerAction, name); else { // msg 61: "Relation not found" status_exception::raise(Arg::PrivateDyn(61)); } savePoint.release(); // everything is ok METD_drop_relation(transaction, name.c_str()); MET_dsql_cache_release(tdbb, SYM_relation, name); } //---------------------- void CreateAlterViewNode::print(string& text) const { text.printf( "CreateAlterViewNode\n" " name: '%s'\n", name.c_str()); } DdlNode* CreateAlterViewNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) { source.ltrim("\n\r\t "); return DdlNode::dsqlPass(dsqlScratch); } void CreateAlterViewNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { Attachment* const attachment = transaction->tra_attachment; const string& userName = attachment->att_user->usr_user_name; const dsql_rel* modifyingView = NULL; if (alter) { modifyingView = METD_get_relation(dsqlScratch->getTransaction(), dsqlScratch, name); if (!modifyingView && !create) status_exception::raise(Arg::Gds(isc_dyn_view_not_found) << name); } saveRelation(tdbb, dsqlScratch, name, true, modifyingView == NULL); // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); const int ddlTriggerAction = (modifyingView ? DDL_TRIGGER_ALTER_VIEW : DDL_TRIGGER_CREATE_VIEW); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlTriggerAction, name); if (!modifyingView) DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_relation); // Compile the SELECT statement into a record selection expression, making sure to bump the // context number since view contexts start at 1 (except for computed fields) -- note that // calling PASS1_rse directly rather than PASS1_statement saves the context stack. dsqlScratch->resetContextStack(); ++dsqlScratch->contextNumber; RseNode* rse = PASS1_rse(dsqlScratch, selectExpr, false); dsqlScratch->getBlrData().clear(); dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5); GEN_expr(dsqlScratch, rse); dsqlScratch->appendUChar(blr_eoc); // Store the blr and source string for the view definition. if (modifyingView) { AutoCacheRequest request(tdbb, drq_m_view, DYN_REQUESTS); bool found = false; FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) REL IN RDB$RELATIONS WITH REL.RDB$RELATION_NAME EQ name.c_str() AND REL.RDB$VIEW_BLR NOT MISSING { found = true; MODIFY REL attachment->storeMetaDataBlob(tdbb, transaction, &REL.RDB$VIEW_SOURCE, source); attachment->storeBinaryBlob(tdbb, transaction, &REL.RDB$VIEW_BLR, dsqlScratch->getBlrData()); END_MODIFY } END_FOR if (!found) status_exception::raise(Arg::Gds(isc_dyn_view_not_found) << name); AutoRequest request2; FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) VR IN RDB$VIEW_RELATIONS WITH VR.RDB$VIEW_NAME EQ name.c_str() { ERASE VR; } END_FOR request2.reset(); FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) TRG IN RDB$TRIGGERS WITH TRG.RDB$RELATION_NAME EQ name.c_str() AND TRG.RDB$SYSTEM_FLAG EQ fb_sysflag_view_check { ERASE TRG; } END_FOR } else { AutoCacheRequest request(tdbb, drq_s_rels, DYN_REQUESTS); STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) REL IN RDB$RELATIONS { strcpy(REL.RDB$RELATION_NAME, name.c_str()); REL.RDB$SYSTEM_FLAG = 0; REL.RDB$FLAGS = REL_sql; REL.RDB$RELATION_TYPE = SSHORT(rel_view); attachment->storeMetaDataBlob(tdbb, transaction, &REL.RDB$VIEW_SOURCE, source); attachment->storeBinaryBlob(tdbb, transaction, &REL.RDB$VIEW_BLR, dsqlScratch->getBlrData()); } END_STORE storePrivileges(tdbb, transaction, name, obj_relation, ALL_PRIVILEGES); } // Define the view source relations from the statement contexts and union contexts. while (dsqlScratch->derivedContext.hasData()) dsqlScratch->context->push(dsqlScratch->derivedContext.pop()); while (dsqlScratch->unionContext.hasData()) dsqlScratch->context->push(dsqlScratch->unionContext.pop()); AutoCacheRequest request(tdbb, drq_s_view_rels, DYN_REQUESTS); for (DsqlContextStack::iterator temp(*dsqlScratch->context); temp.hasData(); ++temp) { const dsql_ctx* context = temp.object(); const dsql_rel* relation = context->ctx_relation; const dsql_prc* procedure = context->ctx_procedure; if (relation || procedure) { const MetaName& refName = relation ? relation->rel_name : procedure->prc_name.identifier; const char* contextName = context->ctx_alias.hasData() ? context->ctx_alias.c_str() : refName.c_str(); ViewContextType ctxType; if (relation) { if (!(relation->rel_flags & REL_view)) ctxType = VCT_TABLE; else ctxType = VCT_VIEW; } else //if (procedure) ctxType = VCT_PROCEDURE; STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) VRL IN RDB$VIEW_RELATIONS { strcpy(VRL.RDB$VIEW_NAME, name.c_str()); strcpy(VRL.RDB$RELATION_NAME, refName.c_str()); VRL.RDB$CONTEXT_TYPE = SSHORT(ctxType); VRL.RDB$VIEW_CONTEXT = context->ctx_context; strcpy(VRL.RDB$CONTEXT_NAME, contextName); if (procedure && procedure->prc_name.package.hasData()) { VRL.RDB$PACKAGE_NAME.NULL = FALSE; strcpy(VRL.RDB$PACKAGE_NAME, procedure->prc_name.package.c_str()); } else VRL.RDB$PACKAGE_NAME.NULL = TRUE; } END_STORE } } // Check privileges on base tables and views. request.reset(tdbb, drq_l_view_rels, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) VRL IN RDB$VIEW_RELATIONS CROSS PREL IN RDB$RELATIONS OVER RDB$RELATION_NAME WITH VRL.RDB$PACKAGE_NAME MISSING AND VRL.RDB$VIEW_NAME EQ name.c_str() { // CVC: This never matches so it causes unnecessary calls to verify, // so I included a call to strip trailing blanks. fb_utils::exact_name_limit(PREL.RDB$OWNER_NAME, sizeof(PREL.RDB$OWNER_NAME)); if (userName != PREL.RDB$OWNER_NAME) { SecurityClass::flags_t priv; // I think this should be the responsability of DFW or the user will find ways to // circumvent DYN. priv = SCL_get_mask(tdbb, PREL.RDB$RELATION_NAME, ""); if (!(priv & SCL_select)) { // msg 32: no permission for %s access to %s %s status_exception::raise( Arg::Gds(isc_no_priv) << Arg::Str("SELECT") << // Non-Translatable // Remember, a view may be based on a view. "TABLE/VIEW" << // Non-Translatable // We want to print the name of the base table or view. MetaName(PREL.RDB$RELATION_NAME)); } } } END_FOR // If there are field names defined for the view, match them in order with the items from the // SELECT. Otherwise use all the fields from the rse node that was created from the select // expression. const NestConst* ptr = NULL; const NestConst* end = NULL; if (viewFields) { ptr = viewFields->items.begin(); end = viewFields->items.end(); } // Go through the fields list, defining or modifying the local fields; // If an expression is specified rather than a field, define a global // field for the computed value as well. ValueListNode* items = rse->dsqlSelectList; NestConst* itemsPtr = items->items.begin(); SortedArray modifiedFields; bool updatable = true; SSHORT position = 0; for (NestConst* itemsEnd = items->items.end(); itemsPtr < itemsEnd; ++itemsPtr, ++position) { ValueExprNode* fieldNode = *itemsPtr; // Determine the proper field name, replacing the default if necessary. ValueExprNode* nameNode = fieldNode; const char* aliasName = NULL; while (nameNode->is() || nameNode->is() || nameNode->is()) { DsqlAliasNode* aliasNode; DsqlMapNode* mapNode; DerivedFieldNode* derivedField; if ((aliasNode = nameNode->as())) { if (!aliasName) aliasName = aliasNode->name.c_str(); nameNode = aliasNode->value; } else if ((mapNode = nameNode->as())) nameNode = mapNode->map->map_node; else if ((derivedField = nameNode->as())) { if (!aliasName) aliasName = derivedField->name.c_str(); nameNode = derivedField->value; } } const dsql_fld* nameField = NULL; const FieldNode* fieldNameNode = nameNode->as(); if (fieldNameNode) nameField = fieldNameNode->dsqlField; const TEXT* fieldStr = NULL; if (aliasName) fieldStr = aliasName; else if (nameField) fieldStr = nameField->fld_name.c_str(); // Check if this is a field or an expression. DsqlAliasNode* aliasNode = fieldNode->as(); if (aliasNode) fieldNode = aliasNode->value; dsql_fld* field = NULL; const dsql_ctx* context = NULL; fieldNameNode = fieldNode->as(); if (fieldNameNode) { field = fieldNameNode->dsqlField; context = fieldNameNode->dsqlContext; } else updatable = false; // If this is an expression, check to make sure there is a name specified. if (!ptr && !fieldStr) { // must specify field name for view select expression status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_specify_field_err)); } // CVC: Small modification here to catch any mismatch between number of // explicit field names in a view and number of fields in the select expression, // see comment below. This closes Firebird Bug #223059. if (ptr) { if (ptr < end) fieldStr = (*ptr)->as()->dsqlName.c_str(); else { // Generate an error when going out of this loop. ++ptr; break; } ++ptr; } // If not an expression, point to the proper base relation field, // else make up an SQL field with generated global field for calculations. dsql_fld* relField = NULL; if (modifyingView) // if we're modifying a view { for (relField = modifyingView->rel_fields; relField; relField = relField->fld_next) { if (relField->fld_name == fieldStr) { if (modifiedFields.exist(relField)) { // column @1 appears more than once in ALTER VIEW status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-104) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_col_more_than_once_view) << Arg::Str(fieldStr)); } modifiedFields.add(relField); break; } } } FieldDefinition fieldDefinition(*tdbb->getDefaultPool()); fieldDefinition.relationName = name; fieldDefinition.name = fieldStr; fieldDefinition.position = position; // CVC: Not sure if something should be done now that isc_dyn_view_context is used here, // but if alter view is going to work, maybe we need here the context type and package, too. if (field) { field->resolve(dsqlScratch); fieldDefinition.viewContext = context->ctx_context; fieldDefinition.baseField = field->fld_name; if (field->dtype <= dtype_any_text) fieldDefinition.collationId = field->collationId; if (relField) // modifying a view { // We're now modifying a field and it will be based on another one. So if the old // field was an expression, delete it now. AutoRequest request2; FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) RFL IN RDB$RELATION_FIELDS CROSS FLD IN RDB$FIELDS WITH RFL.RDB$FIELD_NAME EQ fieldStr AND RFL.RDB$RELATION_NAME EQ name.c_str() AND RFL.RDB$BASE_FIELD MISSING AND FLD.RDB$FIELD_NAME EQ RFL.RDB$FIELD_SOURCE { bool wasInternalDomain = fb_utils::implicit_domain(FLD.RDB$FIELD_NAME); fb_assert(wasInternalDomain); if (wasInternalDomain) ERASE FLD; } END_FOR fieldDefinition.modify(tdbb, transaction); } else fieldDefinition.store(tdbb, transaction); } else { dsqlScratch->getBlrData().clear(); dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5); GEN_expr(dsqlScratch, fieldNode); dsqlScratch->appendUChar(blr_eoc); // Get the type of the expression. dsc desc; MAKE_desc(dsqlScratch, &desc, fieldNode); dsql_fld newField(*tdbb->getDefaultPool()); newField.dtype = desc.dsc_dtype; newField.length = desc.dsc_length; newField.scale = desc.dsc_scale; if (desc.isText() || (desc.isBlob() && desc.getBlobSubType() == isc_blob_text)) { newField.charSetId = desc.getCharSet(); newField.collationId = desc.getCollation(); } if (desc.isText()) { newField.charLength = newField.length; newField.length *= METD_get_charset_bpc( dsqlScratch->getTransaction(), newField.charSetId); } else newField.subType = desc.dsc_sub_type; if (relField) // modifying a view { AutoRequest request2; FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) RFL IN RDB$RELATION_FIELDS CROSS FLD IN RDB$FIELDS WITH RFL.RDB$FIELD_NAME EQ fieldStr AND RFL.RDB$RELATION_NAME EQ name.c_str() AND RFL.RDB$BASE_FIELD MISSING AND FLD.RDB$FIELD_NAME EQ RFL.RDB$FIELD_SOURCE { bool wasInternalDomain = fb_utils::implicit_domain(FLD.RDB$FIELD_NAME); fb_assert(wasInternalDomain); if (wasInternalDomain) { fieldDefinition.fieldSource = FLD.RDB$FIELD_NAME; MODIFY FLD updateRdbFields(&newField, FLD.RDB$FIELD_TYPE, FLD.RDB$FIELD_LENGTH, FLD.RDB$FIELD_SUB_TYPE.NULL, FLD.RDB$FIELD_SUB_TYPE, FLD.RDB$FIELD_SCALE.NULL, FLD.RDB$FIELD_SCALE, FLD.RDB$CHARACTER_SET_ID.NULL, FLD.RDB$CHARACTER_SET_ID, FLD.RDB$CHARACTER_LENGTH.NULL, FLD.RDB$CHARACTER_LENGTH, FLD.RDB$FIELD_PRECISION.NULL, FLD.RDB$FIELD_PRECISION, FLD.RDB$COLLATION_ID.NULL, FLD.RDB$COLLATION_ID, FLD.RDB$SEGMENT_LENGTH.NULL, FLD.RDB$SEGMENT_LENGTH); FLD.RDB$COMPUTED_BLR.NULL = FALSE; attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$COMPUTED_BLR, dsqlScratch->getBlrData()); END_MODIFY } } END_FOR if (fieldDefinition.fieldSource.isEmpty()) { storeGlobalField(tdbb, transaction, fieldDefinition.fieldSource, &newField, "", dsqlScratch->getBlrData()); } fieldDefinition.modify(tdbb, transaction); } else { storeGlobalField(tdbb, transaction, fieldDefinition.fieldSource, &newField, "", dsqlScratch->getBlrData()); fieldDefinition.store(tdbb, transaction); } } if (fieldStr) saveField(tdbb, dsqlScratch, fieldStr); } // CVC: This message was not catching the case when // #fields < items in select list, see comment above. if (ptr != end) { // number of fields does not match select list status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_num_field_err)); } if (modifyingView) // modifying a view { // Delete the old fields not present in the new definition. for (dsql_fld* relField = modifyingView->rel_fields; relField; relField = relField->fld_next) { if (!modifiedFields.exist(relField)) deleteLocalField(tdbb, transaction, name, relField->fld_name); } } // Setup to define triggers for WITH CHECK OPTION. if (withCheckOption) { if (!updatable) { // Only simple column names permitted for VIEW WITH CHECK OPTION status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_col_name_err)); } RseNode* querySpec = selectExpr->querySpec->as(); fb_assert(querySpec); if (querySpec->dsqlFrom->items.getCount() != 1 || !querySpec->dsqlFrom->items[0]->is()) { // Only one table allowed for VIEW WITH CHECK OPTION status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_table_view_err)); } if (!querySpec->dsqlWhere) { // No where clause for VIEW WITH CHECK OPTION status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_where_err)); } if (querySpec->dsqlDistinct || querySpec->dsqlGroup || querySpec->dsqlHaving) { // DISTINCT, GROUP or HAVING not permitted for VIEW WITH CHECK OPTION status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_distinct_err)); } createCheckTrigger(tdbb, dsqlScratch, items, PRE_MODIFY_TRIGGER); createCheckTrigger(tdbb, dsqlScratch, items, PRE_STORE_TRIGGER); } dsqlScratch->resetContextStack(); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, ddlTriggerAction, name); savePoint.release(); // everything is ok // Update DSQL cache METD_drop_relation(transaction, name); MET_dsql_cache_release(tdbb, SYM_relation, name); } // Generate a trigger to implement the WITH CHECK OPTION clause for a VIEW. void CreateAlterViewNode::createCheckTrigger(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, ValueListNode* items, TriggerType triggerType) { MemoryPool& pool = *tdbb->getDefaultPool(); // Specify that the trigger should abort if the condition is not met. ExceptionNode* exceptionNode = FB_NEW(pool) ExceptionNode(pool, CHECK_CONSTRAINT_EXCEPTION); exceptionNode->exception->type = ExceptionItem::GDS_CODE; AutoSetRestore autoCheckConstraintTrigger(&dsqlScratch->checkConstraintTrigger, true); RelationSourceNode* relationNode = dsqlNode; // Generate the trigger blr. dsqlScratch->getBlrData().clear(); dsqlScratch->getDebugData().clear(); dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5); dsqlScratch->appendUChar(blr_begin); dsqlScratch->resetContextStack(); RseNode* querySpec = selectExpr->querySpec->as(); fb_assert(querySpec); ProcedureSourceNode* sourceNode = querySpec->dsqlFrom->items[0]->as(); if (triggerType == PRE_MODIFY_TRIGGER) { dsqlScratch->contextNumber = 2; RelationSourceNode* baseRelation = FB_NEW(pool) RelationSourceNode(pool, sourceNode->dsqlName.identifier); baseRelation->alias = sourceNode->alias; dsqlScratch->appendUChar(blr_for); RseNode* rse = FB_NEW(pool) RseNode(pool); rse->dsqlStreams = FB_NEW(pool) RecSourceListNode(pool, 1); rse->dsqlStreams->items[0] = baseRelation; rse->dsqlStreams->items[0] = doDsqlPass(dsqlScratch, rse->dsqlStreams->items[0]); rse->dsqlWhere = doDsqlPass(dsqlScratch, querySpec->dsqlWhere); dsqlScratch->contextNumber = OLD_CONTEXT_VALUE; dsql_ctx* oldContext; { /// scope AutoSetRestore autoAlias(&relationNode->alias, sourceNode->alias); relationNode->alias = OLD_CONTEXT_NAME; oldContext = PASS1_make_context(dsqlScratch, relationNode); oldContext->ctx_flags |= CTX_system; } // Get the list of values and fields to compare to -- if there is no list of fields, get all // fields in the base relation that are not computed. ValueListNode* valuesNode = viewFields; ValueListNode* fieldsNode = querySpec->dsqlSelectList; if (!fieldsNode) { const dsql_rel* relation = METD_get_relation(dsqlScratch->getTransaction(), dsqlScratch, name); fieldsNode = FB_NEW(pool) ValueListNode(pool, 0u); for (const dsql_fld* field = relation->rel_fields; field; field = field->fld_next) { if (!(field->flags & FLD_computed)) fieldsNode->add(MAKE_field_name(field->fld_name.c_str())); } } if (!valuesNode) valuesNode = fieldsNode; // Generate the list of assignments to fields in the base relation. NestConst* ptr = fieldsNode->items.begin(); const NestConst* const end = fieldsNode->items.end(); NestConst* ptr2 = valuesNode->items.begin(); const NestConst* const end2 = valuesNode->items.end(); int andArg = 0; BinaryBoolNode* andNode = FB_NEW(pool) BinaryBoolNode(pool, blr_and); for (; ptr != end && ptr2 != end2; ++ptr, ++ptr2) { NestConst fieldNod = *ptr; NestConst valueNod = *ptr2; DsqlAliasNode* aliasNode; if ((aliasNode = fieldNod->as())) fieldNod = aliasNode->value; if ((aliasNode = valueNod->as())) valueNod = aliasNode->value; FieldNode* fieldNode = fieldNod->as(); FieldNode* valueNode = valueNod->as(); // Generate the actual comparisons. if (fieldNode && valueNode) { FieldNode* oldValueNode = FB_NEW(pool) FieldNode(pool); oldValueNode->dsqlName = (aliasNode ? aliasNode->name : valueNode->dsqlName); oldValueNode->dsqlQualifier = OLD_CONTEXT_NAME; valueNod = oldValueNode->dsqlPass(dsqlScratch); fieldNod = fieldNode->dsqlPass(dsqlScratch); BoolExprNode* equivNode = FB_NEW(pool) ComparativeBoolNode(pool, blr_equiv, valueNod, fieldNod); rse->dsqlWhere = PASS1_compose(rse->dsqlWhere, equivNode, blr_and); } } GEN_expr(dsqlScratch, rse); } // ASF: We'll now map the view's base table into the trigger's NEW context. ++dsqlScratch->scopeLevel; dsqlScratch->contextNumber = NEW_CONTEXT_VALUE; dsql_ctx* newContext; { /// scope AutoSetRestore autoAlias(&relationNode->alias, sourceNode->alias); if (relationNode->alias.isEmpty()) relationNode->alias = sourceNode->dsqlName.identifier.c_str(); newContext = PASS1_make_context(dsqlScratch, relationNode); newContext->ctx_flags |= CTX_system; if (triggerType == PRE_STORE_TRIGGER) newContext->ctx_flags |= CTX_view_with_check_store; else newContext->ctx_flags |= CTX_view_with_check_modify; } // Replace the view's field names by the base table field names. Save the original names // to restore after the condition processing. dsql_fld* field = newContext->ctx_relation->rel_fields; ObjectsArray savedNames; // ASF: rel_fields entries are in reverse order. for (NestConst* ptr = items->items.end(); ptr-- != items->items.begin(); field = field->fld_next) { ValueExprNode* valueNode = *ptr; DsqlAliasNode* aliasNode; if ((aliasNode = valueNode->as())) valueNode = aliasNode->value; FieldNode* fieldNode = valueNode->as(); fb_assert(fieldNode); savedNames.add(field->fld_name); dsql_fld* queryField = fieldNode->dsqlField; field->fld_name = queryField->fld_name; field->dtype = queryField->dtype; field->scale = queryField->scale; field->subType = queryField->subType; field->length = queryField->length; field->flags = queryField->flags; field->charSetId = queryField->charSetId; field->collationId = queryField->collationId; } dsqlScratch->appendUChar(blr_if); // Process the condition for firing the trigger. NestConst condition = doDsqlPass(dsqlScratch, querySpec->dsqlWhere); // Restore the field names. This must happen before the condition's BLR generation. field = newContext->ctx_relation->rel_fields; for (ObjectsArray::iterator i = savedNames.begin(); i != savedNames.end(); ++i) { field->fld_name = *i; field = field->fld_next; } GEN_expr(dsqlScratch, condition); dsqlScratch->appendUChar(blr_begin); dsqlScratch->appendUChar(blr_end); // Generate the action statement for the trigger. exceptionNode->dsqlPass(dsqlScratch)->genBlr(dsqlScratch); dsqlScratch->appendUChar(blr_end); // of begin dsqlScratch->appendUChar(blr_eoc); dsqlScratch->resetContextStack(); TriggerDefinition trigger(pool); trigger.systemFlag = fb_sysflag_view_check; trigger.relationName = name; trigger.type = triggerType; trigger.blrData = dsqlScratch->getBlrData(); trigger.store(tdbb, dsqlScratch, dsqlScratch->getTransaction()); } //---------------------- // Store an index. void CreateIndexNode::store(thread_db* tdbb, jrd_tra* transaction, MetaName& name, const Definition& definition, MetaName* referredIndexName) { if (name.isEmpty()) DYN_UTIL_generate_index_name(tdbb, transaction, name, definition.type); DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_index); AutoCacheRequest request(tdbb, drq_s_indices, DYN_REQUESTS); ULONG keyLength = 0; STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) IDX IN RDB$INDICES { IDX.RDB$UNIQUE_FLAG.NULL = TRUE; IDX.RDB$INDEX_INACTIVE.NULL = TRUE; IDX.RDB$INDEX_TYPE.NULL = TRUE; IDX.RDB$FOREIGN_KEY.NULL = TRUE; IDX.RDB$EXPRESSION_SOURCE.NULL = TRUE; IDX.RDB$EXPRESSION_BLR.NULL = TRUE; strcpy(IDX.RDB$INDEX_NAME, name.c_str()); strcpy(IDX.RDB$RELATION_NAME, definition.relation.c_str()); IDX.RDB$RELATION_NAME.NULL = FALSE; IDX.RDB$SYSTEM_FLAG = 0; IDX.RDB$SYSTEM_FLAG.NULL = FALSE; // Check if the table is actually a view. AutoCacheRequest request2(tdbb, drq_l_view_idx, DYN_REQUESTS); FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) VREL IN RDB$RELATIONS WITH VREL.RDB$RELATION_NAME EQ IDX.RDB$RELATION_NAME { if (!VREL.RDB$VIEW_BLR.NULL) { // msg 181: "attempt to index a view" status_exception::raise(Arg::PrivateDyn(181)); } } END_FOR if (definition.unique.specified) { IDX.RDB$UNIQUE_FLAG.NULL = FALSE; IDX.RDB$UNIQUE_FLAG = SSHORT(definition.unique.value); } if (definition.inactive.specified) { IDX.RDB$INDEX_INACTIVE.NULL = FALSE; IDX.RDB$INDEX_INACTIVE = SSHORT(definition.inactive.value); } if (definition.descending.specified) { IDX.RDB$INDEX_TYPE.NULL = FALSE; IDX.RDB$INDEX_TYPE = SSHORT(definition.descending.value); } request2.reset(tdbb, drq_l_lfield, DYN_REQUESTS); for (size_t i = 0; i < definition.columns.getCount(); ++i) { for (size_t j = 0; j < i; ++j) { if (definition.columns[i] == definition.columns[j]) { // msg 240 "Field %s cannot be used twice in index %s" status_exception::raise( Arg::PrivateDyn(240) << definition.columns[i] << IDX.RDB$INDEX_NAME); } } bool found = false; FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) F IN RDB$RELATION_FIELDS CROSS GF IN RDB$FIELDS WITH GF.RDB$FIELD_NAME EQ F.RDB$FIELD_SOURCE AND F.RDB$FIELD_NAME EQ definition.columns[i].c_str() AND IDX.RDB$RELATION_NAME EQ F.RDB$RELATION_NAME { ULONG length = 0; if (GF.RDB$FIELD_TYPE == blr_blob) { // msg 116 "attempt to index blob field in index %s" status_exception::raise(Arg::PrivateDyn(116) << IDX.RDB$INDEX_NAME); } else if (!GF.RDB$DIMENSIONS.NULL) { // msg 117 "attempt to index array field in index %s" status_exception::raise(Arg::PrivateDyn(117) << IDX.RDB$INDEX_NAME); } else if (!GF.RDB$COMPUTED_BLR.NULL) { // msg 179 "attempt to index COMPUTED BY field in index %s" status_exception::raise(Arg::PrivateDyn(179) << IDX.RDB$INDEX_NAME); } else if (GF.RDB$FIELD_TYPE == blr_varying || GF.RDB$FIELD_TYPE == blr_text) { // Compute the length of the key segment allowing for international // information. Note that we we must convert a // type to an index type in order to compute the length. if (!F.RDB$COLLATION_ID.NULL) { length = INTL_key_length(tdbb, INTL_TEXT_TO_INDEX(INTL_CS_COLL_TO_TTYPE( GF.RDB$CHARACTER_SET_ID, F.RDB$COLLATION_ID)), GF.RDB$FIELD_LENGTH); } else if (!GF.RDB$COLLATION_ID.NULL) { length = INTL_key_length(tdbb, INTL_TEXT_TO_INDEX(INTL_CS_COLL_TO_TTYPE( GF.RDB$CHARACTER_SET_ID, GF.RDB$COLLATION_ID)), GF.RDB$FIELD_LENGTH); } else length = GF.RDB$FIELD_LENGTH; } else length = sizeof(double); if (keyLength) { keyLength += ((length + Ods::STUFF_COUNT - 1) / (unsigned) Ods::STUFF_COUNT) * (Ods::STUFF_COUNT + 1); } else keyLength = length; found = true; } END_FOR if (!found) { // msg 120 "Unknown columns in index %s" status_exception::raise(Arg::PrivateDyn(120) << IDX.RDB$INDEX_NAME); } } if (!definition.expressionBlr.isEmpty()) { IDX.RDB$EXPRESSION_BLR.NULL = FALSE; IDX.RDB$EXPRESSION_BLR = definition.expressionBlr; } if (!definition.expressionSource.isEmpty()) { IDX.RDB$EXPRESSION_SOURCE.NULL = FALSE; IDX.RDB$EXPRESSION_SOURCE = definition.expressionSource; } keyLength = ROUNDUP(keyLength, sizeof(SLONG)); if (keyLength >= MAX_KEY) { // msg 118 "key size too big for index %s" status_exception::raise(Arg::PrivateDyn(118) << IDX.RDB$INDEX_NAME); } if (definition.columns.hasData()) { request2.reset(tdbb, drq_s_idx_segs, DYN_REQUESTS); SSHORT position = 0; for (ObjectsArray::const_iterator segment(definition.columns.begin()); segment != definition.columns.end(); ++segment) { STORE(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) X IN RDB$INDEX_SEGMENTS { strcpy(X.RDB$INDEX_NAME, IDX.RDB$INDEX_NAME); strcpy(X.RDB$FIELD_NAME, segment->c_str()); X.RDB$FIELD_POSITION = position++; } END_STORE } } else if (IDX.RDB$EXPRESSION_BLR.NULL) { // msg 119 "no keys for index %s" status_exception::raise(Arg::PrivateDyn(119) << IDX.RDB$INDEX_NAME); } if (definition.refColumns.hasData()) { // If referring columns count <> referred columns return error. if (definition.columns.getCount() != definition.refColumns.getCount()) { // msg 133: "Number of referencing columns do not equal number of // referenced columns status_exception::raise(Arg::PrivateDyn(133)); } // Lookup a unique index in the referenced relation with the // referenced fields mentioned. request2.reset(tdbb, drq_l_unq_idx, DYN_REQUESTS); MetaName indexName; int listIndex = -1; bool found = false; FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) RC IN RDB$RELATION_CONSTRAINTS CROSS IND IN RDB$INDICES OVER RDB$INDEX_NAME CROSS ISEG IN RDB$INDEX_SEGMENTS OVER RDB$INDEX_NAME WITH IND.RDB$RELATION_NAME EQ definition.refRelation.c_str() AND IND.RDB$UNIQUE_FLAG NOT MISSING AND (RC.RDB$CONSTRAINT_TYPE = PRIMARY_KEY OR RC.RDB$CONSTRAINT_TYPE = UNIQUE_CNSTRT) SORTED BY IND.RDB$INDEX_NAME, DESCENDING ISEG.RDB$FIELD_POSITION { if (indexName != IND.RDB$INDEX_NAME) { if (listIndex >= 0) found = false; if (found) break; listIndex = definition.refColumns.getCount() - 1; indexName = IND.RDB$INDEX_NAME; found = true; } // If there are no more fields or the field name doesn't // match, then this is not the correct index. if (listIndex >= 0) { fb_utils::exact_name_limit(ISEG.RDB$FIELD_NAME, sizeof(ISEG.RDB$FIELD_NAME)); if (definition.refColumns[listIndex--] != ISEG.RDB$FIELD_NAME) found = false; } else found = false; } END_FOR if (listIndex >= 0) found = false; if (found) { IDX.RDB$FOREIGN_KEY.NULL = FALSE; strcpy(IDX.RDB$FOREIGN_KEY, indexName.c_str()); if (referredIndexName) *referredIndexName = indexName; } else { AutoRequest request3; bool isView = false; FOR(REQUEST_HANDLE request3 TRANSACTION_HANDLE transaction) X IN RDB$RELATIONS WITH X.RDB$RELATION_NAME EQ definition.refRelation.c_str() { found = true; isView = !X.RDB$VIEW_BLR.NULL; } END_FOR if (isView) { // msg 242: "attempt to reference a view (%s) in a foreign key" status_exception::raise(Arg::PrivateDyn(242) << definition.refRelation); } if (found) { // msg 18: "could not find UNIQUE or PRIMARY KEY constraint in table %s with // specified columns" status_exception::raise(Arg::PrivateDyn(18) << definition.refRelation); } else { // msg 241: "Table %s not found" status_exception::raise(Arg::PrivateDyn(241) << definition.refRelation); } } } else if (definition.refRelation.hasData()) { request2.reset(tdbb, drq_l_primary, DYN_REQUESTS); FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) IND IN RDB$INDICES CROSS RC IN RDB$RELATION_CONSTRAINTS OVER RDB$INDEX_NAME WITH IND.RDB$RELATION_NAME EQ definition.refRelation.c_str() AND RC.RDB$CONSTRAINT_TYPE EQ PRIMARY_KEY { // Number of columns in referred index should be same as number // of columns in referring index. fb_assert(IND.RDB$SEGMENT_COUNT >= 0); if (definition.columns.getCount() != ULONG(IND.RDB$SEGMENT_COUNT)) { // msg 133: "Number of referencing columns do not equal number of // referenced columns" status_exception::raise(Arg::PrivateDyn(133)); } fb_utils::exact_name_limit(IND.RDB$INDEX_NAME, sizeof(IND.RDB$INDEX_NAME)); IDX.RDB$FOREIGN_KEY.NULL = FALSE; strcpy(IDX.RDB$FOREIGN_KEY, IND.RDB$INDEX_NAME); if (referredIndexName) *referredIndexName = IND.RDB$INDEX_NAME; } END_FOR if (IDX.RDB$FOREIGN_KEY.NULL) { // msg 20: "could not find PRIMARY KEY index in specified table %s" status_exception::raise(Arg::PrivateDyn(20) << definition.refRelation); } } IDX.RDB$SEGMENT_COUNT = SSHORT(definition.columns.getCount()); } END_STORE } void CreateIndexNode::print(string& text) const { text.printf( "CreateIndexNode\n" " name: '%s'\n", name.c_str()); } // Define an index. void CreateIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { Attachment* const attachment = transaction->tra_attachment; // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_INDEX, name); CreateIndexNode::Definition definition; definition.type = isc_dyn_def_idx; definition.relation = relation->dsqlName; definition.unique = unique; definition.descending = descending; if (columns) { const NestConst* ptr = columns->items.begin(); const NestConst* const end = columns->items.end(); for (; ptr != end; ++ptr) { MetaName& column = definition.columns.add(); column = (*ptr)->as()->dsqlName; } } else if (computed) { string computedSource; BlrDebugWriter::BlrData computedValue; defineComputed(dsqlScratch, relation, NULL, computed, computedSource, computedValue); attachment->storeMetaDataBlob(tdbb, transaction, &definition.expressionSource, computedSource); attachment->storeBinaryBlob(tdbb, transaction, &definition.expressionBlr, computedValue); } store(tdbb, transaction, name, definition); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_INDEX, name); savePoint.release(); // everything is ok } //---------------------- void AlterIndexNode::print(string& text) const { text.printf( "AlterIndexNode\n" " name: '%s'\n" " active: '%d'\n", name.c_str(), active); } void AlterIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); AutoCacheRequest request(tdbb, drq_m_index, DYN_REQUESTS); bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) IDX IN RDB$INDICES WITH IDX.RDB$INDEX_NAME EQ name.c_str() { found = true; executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_INDEX, name); MODIFY IDX IDX.RDB$INDEX_INACTIVE.NULL = FALSE; IDX.RDB$INDEX_INACTIVE = active ? FALSE : TRUE; END_MODIFY } END_FOR if (found) executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_INDEX, name); else { // msg 48: "Index not found" status_exception::raise(Arg::PrivateDyn(48)); } savePoint.release(); // everything is ok } //---------------------- void SetStatisticsNode::print(string& text) const { text.printf( "SetStatisticsNode\n" " name: '%s'\n", name.c_str()); } void SetStatisticsNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); AutoCacheRequest request(tdbb, drq_m_set_statistics, DYN_REQUESTS); bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) IDX IN RDB$INDICES WITH IDX.RDB$INDEX_NAME EQ name.c_str() { found = true; executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_INDEX, name); MODIFY IDX // For V4 index selectivity can be set only to -1. IDX.RDB$STATISTICS.NULL = FALSE; IDX.RDB$STATISTICS = -1.0; END_MODIFY } END_FOR if (found) executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_INDEX, name); else { // msg 48: "Index not found" status_exception::raise(Arg::PrivateDyn(48)); } savePoint.release(); // everything is ok } //---------------------- // Delete the records in RDB$INDEX_SEGMENTS pertaining to an index. bool DropIndexNode::deleteSegmentRecords(thread_db* tdbb, jrd_tra* transaction, const MetaName& name) { AutoCacheRequest request(tdbb, drq_e_idx_segs, DYN_REQUESTS); bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) IDXSEG IN RDB$INDEX_SEGMENTS WITH IDXSEG.RDB$INDEX_NAME EQ name.c_str() { found = true; ERASE IDXSEG; } END_FOR return found; } void DropIndexNode::print(string& text) const { text.printf( "DropIndexNode\n" " name: '%s'\n", name.c_str()); } void DropIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); AutoCacheRequest request(tdbb, drq_e_indices, DYN_REQUESTS); bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) IDX IN RDB$INDICES WITH IDX.RDB$INDEX_NAME EQ name.c_str() { executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_INDEX, name); ERASE IDX; if (IDX.RDB$EXPRESSION_BLR.NULL && !deleteSegmentRecords(tdbb, transaction, name)) { // msg 50: "No segments found for index" status_exception::raise(Arg::PrivateDyn(50)); } found = true; } END_FOR if (found) executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_INDEX, name); else { // msg 48: "Index not found" status_exception::raise(Arg::PrivateDyn(48)); } savePoint.release(); // everything is ok } //---------------------- void CreateFilterNode::print(string& text) const { text.printf( "CreateFilterNode\n" " name: '%s'\n", name.c_str()); } // Define a blob filter. void CreateFilterNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); ///executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DECLARE_FILTER, name); AutoCacheRequest request(tdbb, drq_s_filters, DYN_REQUESTS); STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$FILTERS { strcpy(X.RDB$FUNCTION_NAME, name.c_str()); X.RDB$SYSTEM_FLAG = 0; moduleName.copyTo(X.RDB$MODULE_NAME, sizeof(X.RDB$MODULE_NAME)); entryPoint.copyTo(X.RDB$ENTRYPOINT, sizeof(X.RDB$ENTRYPOINT)); if (inputFilter->name.hasData()) { if (!METD_get_type(transaction, inputFilter->name, "RDB$FIELD_SUB_TYPE", &X.RDB$INPUT_SUB_TYPE)) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_dsql_datatype_err) << Arg::Gds(isc_dsql_blob_type_unknown) << inputFilter->name); } } else X.RDB$INPUT_SUB_TYPE = inputFilter->number; if (outputFilter->name.hasData()) { if (!METD_get_type(transaction, outputFilter->name, "RDB$FIELD_SUB_TYPE", &X.RDB$OUTPUT_SUB_TYPE)) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_dsql_datatype_err) << Arg::Gds(isc_dsql_blob_type_unknown) << outputFilter->name); } } else X.RDB$OUTPUT_SUB_TYPE = outputFilter->number; } END_STORE ///executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DECLARE_FILTER, name); savePoint.release(); // everything is ok } //---------------------- void DropFilterNode::print(string& text) const { text.printf( "DropFilterNode\n" " name: '%s'\n", name.c_str()); } void DropFilterNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); AutoCacheRequest request(tdbb, drq_e_filters, DYN_REQUESTS); bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$FILTERS WITH X.RDB$FUNCTION_NAME EQ name.c_str() { ERASE X; found = true; } END_FOR if (!found) { // msg 37: "Blob Filter %s not found" status_exception::raise(Arg::PrivateDyn(37) << name); } savePoint.release(); // everything is ok } //---------------------- void CreateShadowNode::print(string& text) const { text.printf( "CreateShadowNode\n" " number: '%d'\n", number); } void CreateShadowNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/, jrd_tra* transaction) { if (!tdbb->getAttachment()->locksmith()) status_exception::raise(Arg::Gds(isc_adm_task_denied)); // Should be caught by the parser. if (number == 0) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_shadow_number_err)); } // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); // If a shadow set identified by the shadow number already exists return error. AutoCacheRequest request(tdbb, drq_l_shadow, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FIRST 1 X IN RDB$FILES WITH X.RDB$SHADOW_NUMBER EQ number { // msg 165: "Shadow %ld already exists" status_exception::raise(Arg::PrivateDyn(165) << Arg::Num(number)); } END_FOR SLONG start = 0; for (NestConst* i = files.begin(); i != files.end(); ++i) { bool first = i == files.begin(); DbFileClause* file = *i; if (!first && i[-1]->length == 0 && file->start == 0) { // Preceding file did not specify length, so %s must include starting page number status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_file_length_err) << file->name); } defineFile(tdbb, transaction, number, manual && first, conditional && first, start, file->name.c_str(), file->start, file->length); } savePoint.release(); // everything is ok } //---------------------- void DropShadowNode::print(string& text) const { text.printf( "DropShadowNode\n" " number: '%d'\n", number); } void DropShadowNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/, jrd_tra* transaction) { if (!tdbb->getAttachment()->locksmith()) status_exception::raise(Arg::Gds(isc_adm_task_denied)); // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); AutoCacheRequest request(tdbb, drq_e_shadow, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FIL IN RDB$FILES WITH FIL.RDB$SHADOW_NUMBER EQ number { ERASE FIL; } END_FOR // ASF: No error is raised if the shadow is not found. savePoint.release(); // everything is ok } //---------------------- void CreateRoleNode::print(string& text) const { text.printf( "CreateRoleNode\n" " name: '%s'\n", name.c_str()); } void CreateRoleNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { MetaName ownerName(tdbb->getAttachment()->att_user->usr_user_name); ownerName.upper7(); // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_ROLE, name); if (name == ownerName) { // msg 193: "user name @1 could not be used for SQL role" status_exception::raise(Arg::PrivateDyn(193) << ownerName); } if (name == NULL_ROLE) { // msg 195: "keyword @1 could not be used as SQL role name" status_exception::raise(Arg::PrivateDyn(195) << name); } if (isItUserName(tdbb, transaction)) { // msg 193: "user name @1 could not be used for SQL role" status_exception::raise(Arg::PrivateDyn(193) << name); } MetaName dummyName; if (isItSqlRole(tdbb, transaction, name, dummyName)) { // msg 194: "SQL role @1 already exists" status_exception::raise(Arg::PrivateDyn(194) << name); } AutoCacheRequest request(tdbb, drq_role_gens, DYN_REQUESTS); STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$ROLES { strcpy(X.RDB$ROLE_NAME, name.c_str()); strcpy(X.RDB$OWNER_NAME, ownerName.c_str()); X.RDB$SYSTEM_FLAG = 0; } END_STORE executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_ROLE, name); savePoint.release(); // everything is ok } // If role name is user name returns true. Otherwise returns false. bool CreateRoleNode::isItUserName(thread_db* tdbb, jrd_tra* transaction) { bool found = false; // If there is a user with privilege or a grantor on a relation we // can infer there is a user with this name AutoCacheRequest request(tdbb, drq_get_user_priv, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH (PRIV.RDB$USER EQ name.c_str() AND PRIV.RDB$USER_TYPE = obj_user) OR (PRIV.RDB$GRANTOR EQ name.c_str() AND PRIV.RDB$OBJECT_TYPE = obj_relation) { found = true; } END_FOR if (found) return found; // We can infer that 'role name' is a user name if it owns any relations // Note we can only get here if a user creates a table and revokes all // his privileges on the table request.reset(tdbb, drq_get_rel_owner, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) REL IN RDB$RELATIONS WITH REL.RDB$OWNER_NAME EQ name.c_str() { found = true; } END_FOR return found; } //---------------------- void MappingNode::print(string& text) const { const char* null = ""; text.printf( "MappingNode\n" " op: '%s'\n" " global: '%d'\n" " mode: '%c'\n" " plugin: '%s'\n" " db: '%s'\n" " fromType: '%s'\n" " from: '%s'\n" " role: '%d'\n" " to: '%s'\n", op, global, mode, plugin ? plugin->c_str() : null, db ? db->c_str() : null, fromType ? fromType->c_str() : null, from ? from->getString().c_str() : null, role, to ? to->c_str() : null); } void MappingNode::validateAdmin() { if (to && (*to != ADMIN_ROLE)) Arg::Gds(isc_alter_role).raise(); } // add some item to DDL in "double quotes" void MappingNode::addItem(string& ddl, const char* text) { ddl += '"'; char c; while (c = *text++) { ddl += c; if (c == '"') ddl += c; } ddl += '"'; } // It's purpose is to add/drop mapping from any security name to DB security object. void MappingNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { if (!(tdbb->getAttachment() && tdbb->getAttachment()->locksmith())) status_exception::raise(Arg::Gds(isc_adm_task_denied)); if (global) { LocalStatus st; LocalStatus s2; // we will use it in DDL case and remember IStatus* s = &st; class Check { public: static void status(IStatus* s) { if (!s->isSuccess()) status_exception::raise(s->get()); } }; SecDbContext* secDbContext = transaction->getSecDbContext(); if (!secDbContext) { const char* secDb = tdbb->getDatabase()->dbb_config->getSecurityDatabase(); ClumpletWriter dpb(ClumpletWriter::WideTagged, MAX_DPB_SIZE, isc_dpb_version2); if (tdbb->getAttachment()->att_user) tdbb->getAttachment()->att_user->populateDpb(dpb); IAttachment* att = DispatcherPtr()->attachDatabase(s, secDb, dpb.getBufferLength(), dpb.getBuffer()); Check::status(s); ITransaction* tra = att->startTransaction(s, 0, NULL); Check::status(s); secDbContext = transaction->setSecDbContext(att, tra); } // run all statements under savepoint control string savePoint; savePoint.printf("GLOBALMAP%d", secDbContext->savePoint++); secDbContext->att->execute(s, secDbContext->tra, 0, ("SAVEPOINT " + savePoint).c_str(), SQL_DIALECT_V6, NULL, NULL, NULL, NULL); Check::status(s); try { // first of all try to use regenerated DDL statement // that's the best way if security database is FB3 or higher fb version string ddl; switch(op) { case MAP_ADD: ddl = "CREATE MAPPING "; break; case MAP_MOD: ddl = "ALTER MAPPING "; break; case MAP_DROP: ddl = "DROP MAPPING "; break; case MAP_RPL: ddl = "CREATE OR ALTER MAPPING "; break; } addItem(ddl, name.c_str()); if (op != MAP_DROP) { ddl += " USING "; switch (mode) { case 'P': if (!plugin) ddl += "ANY PLUGIN "; else { ddl += "PLUGIN "; addItem(ddl, plugin->c_str()); ddl += ' '; } break; case 'S': ddl += "ANY PLUGIN SERVERWIDE "; break; case '*': ddl += "* "; break; case 'M': ddl += "MAPPING "; break; } if (db) { ddl += "IN "; addItem(ddl, db->c_str()); ddl += ' '; } if (fromType) { ddl += "FROM "; if (!from) ddl += "ANY "; addItem(ddl, fromType->c_str()); ddl += ' '; if (from) { addItem(ddl, from->getString().c_str()); ddl += ' '; } } ddl += "TO "; ddl += (role ? "ROLE" : "USER"); if (to) { ddl += ' '; addItem(ddl, to->c_str()); } } // Now try to run DDL secDbContext->att->execute(&s2, secDbContext->tra, 0, ddl.c_str(), SQL_DIALECT_V6, NULL, NULL, NULL, NULL); if (!s2.isSuccess()) { // try direct access to rdb$map table in secure db // check presence of such record in the table Message check; Field nm(check, 1); nm = name.c_str(); Message result; Field cnt(result); const char* checkSql = "select count(*) from RDB$MAP where RDB$MAP_NAME = ?"; secDbContext->att->execute(s, secDbContext->tra, 0, checkSql, SQL_DIALECT_V6, check.getMetadata(), check.getBuffer(), result.getMetadata(), result.getBuffer()); Check::status(s); if (cnt > 1 && op != MAP_DROP) ERRD_bugcheck("Database mapping misconfigured"); bool hasLine = cnt > 0; switch(op) { case MAP_ADD: if (hasLine) (Arg::Gds(isc_map_already_exists) << name).raise(); break; case MAP_MOD: case MAP_DROP: if (!hasLine) (Arg::Gds(isc_map_not_exists) << name).raise(); break; case MAP_RPL: op = hasLine ? MAP_MOD : MAP_DROP; break; } // Get ready to modify table Message full; Field toType(full); Field t(full, MAX_SQL_IDENTIFIER_LEN); Field usng2(full, 1); Field plug2(full, MAX_SQL_IDENTIFIER_LEN); Field d2(full, MAX_SQL_IDENTIFIER_LEN); Field type2(full, MAX_SQL_IDENTIFIER_LEN); Field f2(full, 255); Field nm2(full, MAX_SQL_IDENTIFIER_LEN); toType = role ? 1 : 0; if (to) t = to->c_str(); usng2.set(1, &mode); if (plugin) plug2 = plugin->c_str(); if (db) d2 = db->c_str(); if (fromType) type2 = fromType->c_str(); if (from) f2 = from->getString().c_str(); nm2 = name.c_str(); Message* msg = NULL; const char* sql = NULL; switch(op) { case MAP_ADD: sql = "insert into RDB$MAP(RDB$MAP_TO_TYPE, RDB$MAP_TO, RDB$MAP_USING, " "RDB$MAP_PLUGIN, RDB$MAP_DB, RDB$MAP_FROM_TYPE, RDB$MAP_FROM, RDB$MAP_NAME) " "values (?, ?, ?, ?, ?, ?, ?, ?)"; msg = &full; break; case MAP_MOD: sql = "update RDB$MAP set RDB$MAP_TO_TYPE = ?, RDB$MAP_TO = ?, " "RDB$MAP_USING = ?, RDB$MAP_PLUGIN = ?, RDB$MAP_DB = ?, " "RDB$MAP_FROM_TYPE = ?, RDB$MAP_FROM = ? " "where RDB$MAP_NAME = ?"; msg = &full; break; case MAP_DROP: sql = "delete from RDB$MAP where RDB$MAP_NAME = ?"; msg = ✓ break; } // Actual modification fb_assert(sql && msg); secDbContext->att->execute(s, secDbContext->tra, 0, sql, SQL_DIALECT_V6, msg->getMetadata(), msg->getBuffer(), NULL, NULL); Check::status(s); secDbContext->att->execute(s, secDbContext->tra, 0, ("RELEASE SAVEPOINT " + savePoint).c_str(), SQL_DIALECT_V6, NULL, NULL, NULL, NULL); savePoint.erase(); Check::status(s); } } catch (const Exception&) { if (savePoint.hasData()) { secDbContext->att->execute(s, secDbContext->tra, 0, ("ROLLBACK TO SAVEPOINT " + savePoint).c_str(), SQL_DIALECT_V6, NULL, NULL, NULL, NULL); } if (!s2.isSuccess()) { const ISC_STATUS* stat2 = s2.get(); if (stat2[1] != isc_dsql_token_unk_err) status_exception::raise(stat2); } throw; } return; } // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); fb_assert(op == MAP_DROP || fromType); short plugNull = plugin ? FALSE : TRUE; short dbNull = db ? FALSE : TRUE; short fromNull = from ? FALSE : TRUE; char usingText[2]; usingText[0] = mode; usingText[1] = '\0'; AutoCacheRequest request1(tdbb, drq_map_mod, DYN_REQUESTS); bool found = false; int ddlTriggerAction = 0; FOR(REQUEST_HANDLE request1 TRANSACTION_HANDLE transaction) M IN RDB$MAP WITH M.RDB$MAP_NAME EQ name.c_str() { found = true; switch (op) { case MAP_ADD: break; case MAP_MOD: case MAP_RPL: ddlTriggerAction = DDL_TRIGGER_ALTER_MAPPING; executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlTriggerAction, name); MODIFY M if (to) { M.RDB$MAP_TO.NULL = FALSE; strcpy(M.RDB$MAP_TO, to->c_str()); } else M.RDB$MAP_TO.NULL = TRUE; M.RDB$MAP_TO_TYPE = role ? 1 : 0; strcpy(M.RDB$MAP_USING, usingText); M.RDB$MAP_PLUGIN.NULL = plugNull; if (!plugNull) strcpy(M.RDB$MAP_PLUGIN, plugin->c_str()); M.RDB$MAP_DB.NULL = dbNull; if (!dbNull) strcpy(M.RDB$MAP_DB, db->c_str()); strcpy(M.RDB$MAP_FROM_TYPE, fromType->c_str()); M.RDB$MAP_FROM.NULL = fromNull; if (!fromNull) strcpy(M.RDB$MAP_FROM, from->getString().c_str()); END_MODIFY break; case MAP_DROP: ddlTriggerAction = DDL_TRIGGER_DROP_MAPPING; executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlTriggerAction, name); ERASE M; break; } } END_FOR AutoCacheRequest request2(tdbb, drq_map_sto, DYN_REQUESTS); switch (op) { case MAP_ADD: if (found) { (Arg::Gds(isc_map_already_exists) << name).raise(); } // fall through ... case MAP_RPL: if (found) break; ddlTriggerAction = DDL_TRIGGER_CREATE_MAPPING; executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlTriggerAction, name); STORE(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) M IN RDB$MAP { strcpy(M.RDB$MAP_NAME, name.c_str()); strcpy(M.RDB$MAP_USING, usingText); M.RDB$MAP_PLUGIN.NULL = plugNull; if (!plugNull) strcpy(M.RDB$MAP_PLUGIN, plugin->c_str()); M.RDB$MAP_DB.NULL = dbNull; if (!dbNull) strcpy(M.RDB$MAP_DB, db->c_str()); strcpy(M.RDB$MAP_FROM_TYPE, fromType->c_str()); M.RDB$MAP_FROM.NULL = fromNull; if (!fromNull) strcpy(M.RDB$MAP_FROM, from->getString().c_str()); M.RDB$MAP_TO_TYPE = role ? 1 : 0; if (to) { M.RDB$MAP_TO.NULL = FALSE; strcpy(M.RDB$MAP_TO, to->c_str()); } else M.RDB$MAP_TO.NULL = TRUE; } END_STORE break; case MAP_MOD: case MAP_DROP: if (!found) (Arg::Gds(isc_map_not_exists) << name).raise(); break; } fb_assert(ddlTriggerAction > 0); if (ddlTriggerAction > 0) executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, ddlTriggerAction, name); DFW_post_work(transaction, dfw_clear_mapping, NULL, 0); savePoint.release(); // everything is ok } //---------------------- void DropRoleNode::print(string& text) const { text.printf( "DropRoleNode\n" " name: '%s'\n", name.c_str()); } void DropRoleNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { MetaName user(tdbb->getAttachment()->att_user->usr_user_name); user.upper7(); // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); AutoCacheRequest request(tdbb, drq_drop_role, DYN_REQUESTS); bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) ROL IN RDB$ROLES WITH ROL.RDB$ROLE_NAME EQ name.c_str() { executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_ROLE, name); const MetaName roleOwner(ROL.RDB$OWNER_NAME); if (!ROL.RDB$SYSTEM_FLAG.NULL && ROL.RDB$SYSTEM_FLAG != 0) { // msg 284: can not drop system SQL role @1 status_exception::raise(Arg::PrivateDyn(284) << name); } if (tdbb->getAttachment()->locksmith() || roleOwner == user) { AutoCacheRequest request2(tdbb, drq_del_role_1, DYN_REQUESTS); // The first OR clause finds all members of the role. // The 2nd OR clause finds all privileges granted to the role FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH (PRIV.RDB$RELATION_NAME EQ name.c_str() AND PRIV.RDB$OBJECT_TYPE = obj_sql_role) OR (PRIV.RDB$USER EQ name.c_str() AND PRIV.RDB$USER_TYPE = obj_sql_role) { ERASE PRIV; } END_FOR ERASE ROL; } else { // msg 191: "only owner of SQL role or USR_locksmith could drop SQL role" status_exception::raise(Arg::PrivateDyn(191)); } found = true; } END_FOR if (found) executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_ROLE, name); else { // msg 155: "Role %s not found" status_exception::raise(Arg::PrivateDyn(155) << name); } savePoint.release(); // everything is ok } //---------------------- void CreateAlterUserNode::print(string& text) const { text.printf( "CreateAlterUserNode\n" " name: '%s'\n", name.c_str()); } void CreateAlterUserNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { if (mode != USER_ADD && !password && !firstName && !middleName && !lastName && !adminRole.specified && !active.specified && !comment && !properties.hasData()) { // 283: ALTER USER requires at least one clause to be specified status_exception::raise(Arg::PrivateDyn(283)); } // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); Auth::DynamicUserData* userData = FB_NEW(*transaction->tra_pool) Auth::DynamicUserData; string text = name.c_str(); if (text.isEmpty() && mode == USER_MOD) { // alter current user UserId* usr = tdbb->getAttachment()->att_user; fb_assert(usr); if (!usr) (Arg::Gds(isc_random) << "Missing user name for ALTER CURRENT USER").raise(); text = usr->usr_user_name; } text.upper(); userData->op = mode == USER_ADD ? Auth::ADD_OPER : mode == USER_MOD ? Auth::MOD_OPER : Auth::ADDMOD_OPER; userData->user.set(text.c_str()); userData->user.setEntered(1); if (password) { if (password->isEmpty()) { // 250: Password should not be empty string status_exception::raise(Arg::PrivateDyn(250)); } userData->pass.set(password->c_str()); userData->pass.setEntered(1); } if (firstName) { if (firstName->hasData()) { userData->first.set(firstName->c_str()); userData->first.setEntered(1); } else { userData->first.setEntered(0); userData->first.setSpecified(1); } } if (middleName) { if (middleName->hasData()) { userData->middle.set(middleName->c_str()); userData->middle.setEntered(1); } else { userData->middle.setEntered(0); userData->middle.setSpecified(1); } } if (lastName) { if (lastName->hasData()) { userData->last.set(lastName->c_str()); userData->last.setEntered(1); } else { userData->last.setEntered(0); userData->last.setSpecified(1); } } if (comment) { if (comment->hasData()) { userData->com.set(comment->c_str()); userData->com.setEntered(1); } else { userData->com.setEntered(0); userData->com.setSpecified(1); } } if (adminRole.specified) { userData->adm.set(adminRole.value); userData->adm.setEntered(1); } if (active.specified) { userData->act.set((int) active.value); userData->act.setEntered(1); } string attributesBuffer; for (unsigned cnt = 0; cnt < properties.getCount(); ++cnt) { if (mode != USER_ADD || properties[cnt].value.hasData()) { string attribute; attribute.printf("%s=%s\n", properties[cnt].property.c_str(), properties[cnt].value.c_str()); attributesBuffer += attribute; } } if (attributesBuffer.hasData()) { userData->attr.set(attributesBuffer.c_str()); userData->attr.setEntered(1); } const int ddlAction = mode == USER_ADD ? DDL_TRIGGER_CREATE_USER : DDL_TRIGGER_ALTER_USER; executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlAction, userData->user.get()); const USHORT id = transaction->getUserManagement()->put(userData); DFW_post_work(transaction, dfw_user_management, NULL, id); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, ddlAction, userData->user.get()); savePoint.release(); // everything is ok } //---------------------- void DropUserNode::print(string& text) const { text.printf( "DropUserNode\n" " name: '%s'\n", name.c_str()); } void DropUserNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); Auth::DynamicUserData* userData = FB_NEW(*transaction->tra_pool) Auth::DynamicUserData; string text = name.c_str(); text.upper(); userData->op = Auth::DEL_OPER; userData->user.set(text.c_str()); userData->user.setEntered(1); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_USER, userData->user.get()); const USHORT id = transaction->getUserManagement()->put(userData); DFW_post_work(transaction, dfw_user_management, NULL, id); executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_USER, userData->user.get()); savePoint.release(); // everything is ok } //---------------------- void GrantRevokeNode::print(string& text) const { text.printf( "GrantRevokeNode\n" " isGrant: '%d'\n", isGrant); } void GrantRevokeNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); const GranteeClause* usersPtr; const GranteeClause* usersEnd; if (!isGrant && roles.isEmpty() && privileges.isEmpty() && !object) // REVOKE ALL ON ALL { usersEnd = users.end(); for (usersPtr = users.begin(); usersPtr != usersEnd; ++usersPtr) grantRevoke(tdbb, transaction, NULL, usersPtr, NULL, NULL, 0); } else { SSHORT option = 0; // no grant/admin option if (roles.isEmpty()) { if (grantAdminOption) option = 1; // with grant option usersEnd = users.end(); for (usersPtr = users.begin(); usersPtr != usersEnd; ++usersPtr) modifyPrivileges(tdbb, transaction, option, usersPtr); } else { if (grantAdminOption) option = 2; // with admin option const GranteeClause* rolesEnd = roles.end(); for (const GranteeClause* rolesPtr = roles.begin(); rolesPtr != rolesEnd; ++rolesPtr) { usersEnd = users.end(); for (usersPtr = users.begin(); usersPtr != usersEnd; ++usersPtr) grantRevoke(tdbb, transaction, rolesPtr, usersPtr, "M", NULL, option); } } } savePoint.release(); // everything is ok } void GrantRevokeNode::modifyPrivileges(thread_db* tdbb, jrd_tra* transaction, SSHORT option, const GranteeClause* user) { string privs; for (PrivilegeClause* i = privileges.begin(); i != privileges.end(); ++i) { if (i->first == 'A') grantRevoke(tdbb, transaction, object, user, "A", NULL, option); else if (i->second) { char privs0[2] = {i->first, '\0'}; ValueListNode* fields = i->second; for (NestConst* ptr = fields->items.begin(); ptr != fields->items.end(); ++ptr) { grantRevoke(tdbb, transaction, object, user, privs0, (*ptr)->as()->dsqlName, option); } } else privs += i->first; } if (privs.hasData()) grantRevoke(tdbb, transaction, object, user, privs.c_str(), NULL, option); } // Execute SQL grant/revoke operation. void GrantRevokeNode::grantRevoke(thread_db* tdbb, jrd_tra* transaction, const GranteeClause* object, const GranteeClause* userNod, const char* privs, const MetaName& field, int options) { SSHORT userType = userNod->first; MetaName user(userNod->second); MetaName dummyName; switch (userType) { case obj_user_or_role: // This test may become obsolete as we now allow explicit ROLE keyword. if (isItSqlRole(tdbb, transaction, user, dummyName)) { userType = obj_sql_role; if (user == NULL_ROLE) { // msg 195: keyword NONE could not be used as SQL role name. status_exception::raise(Arg::PrivateDyn(195) << user.c_str()); } } else { userType = obj_user; user.upper7(); } break; case obj_user: user.upper7(); break; case obj_sql_role: if (!isItSqlRole(tdbb, transaction, user, dummyName)) { // msg 188: Role doesn't exist. status_exception::raise(Arg::PrivateDyn(188) << user.c_str()); } if (user == NULL_ROLE) { // msg 195: keyword NONE could not be used as SQL role name. status_exception::raise(Arg::PrivateDyn(195) << user.c_str()); } break; } MetaName grantorRevoker(grantor ? *grantor : tdbb->getAttachment()->att_user->usr_user_name); if (grantor && !tdbb->getAttachment()->locksmith()) status_exception::raise(Arg::PrivateDyn(252) << SYSDBA_USER_NAME); grantorRevoker.upper7(); if (!isGrant && !privs) // REVOKE ALL ON ALL { AutoCacheRequest request(tdbb, drq_e_grant3, DYN_REQUESTS); bool grantErased = false; bool badGrantor = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$USER = user.c_str() AND PRIV.RDB$USER_TYPE = userType { if (tdbb->getAttachment()->att_user->locksmith() || grantorRevoker == PRIV.RDB$GRANTOR) { ERASE PRIV; grantErased = true; } else badGrantor = true; } END_FOR const char* all = "ALL"; if (badGrantor && !grantErased) { // msg 246: @1 is not grantor of @2 on @3 to @4. status_exception::raise(Arg::PrivateDyn(246) << grantorRevoker.c_str() << all << all << user.c_str()); } if (!grantErased) { // msg 247: Warning: @1 on @2 is not granted to @3. ERR_post_warning( Arg::Warning(isc_dyn_miss_priv_warning) << all << all << Arg::Str(user)); } return; } const SSHORT objType = object->first; const MetaName objName(object->second); char privileges[16]; strcpy(privileges, privs); if (strcmp(privileges, "A") == 0) strcpy(privileges, ALL_PRIVILEGES); if (objType == obj_sql_role && objName == NULL_ROLE) { if (isGrant) { // msg 195: keyword NONE could not be used as SQL role name. status_exception::raise(Arg::PrivateDyn(195) << objName.c_str()); } else { ///CVC: Make this a warning in the future. ///DYN_error_punt(false, 195, objName.c_str()); } } char priv[2]; priv[1] = '\0'; if (isGrant) { AutoCacheRequest request(tdbb, drq_l_grant1, DYN_REQUESTS); for (const char* pr = privileges; *pr; ++pr) { bool duplicate = false; priv[0] = *pr; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$RELATION_NAME EQ objName.c_str() AND PRIV.RDB$OBJECT_TYPE = objType AND PRIV.RDB$PRIVILEGE EQ priv AND PRIV.RDB$USER = user.c_str() AND PRIV.RDB$USER_TYPE = userType AND PRIV.RDB$GRANTOR EQ grantorRevoker.c_str() AND PRIV.RDB$FIELD_NAME EQUIV NULLIF(field.c_str(), '') { if (PRIV.RDB$GRANT_OPTION.NULL || PRIV.RDB$GRANT_OPTION || PRIV.RDB$GRANT_OPTION == options) { duplicate = true; } else ERASE PRIV; // has to be 0 and options == 1 } END_FOR if (duplicate) continue; if (objType == obj_sql_role) { checkGrantorCanGrantRole(tdbb, transaction, grantorRevoker, objName); if (userType == obj_sql_role) { // Temporary restriction. This should be removed once GRANT role1 TO rolex is // supported and this message could be reused for blocking cycles of role grants. status_exception::raise(Arg::PrivateDyn(192) << user.c_str()); } } else { // In the case where the object is a view, then the grantor must have // some kind of grant privileges on the base table(s)/view(s). If the // grantor is the owner of the view, then we have to explicitely check // this because the owner of a view by default has grant privileges on // his own view. If the grantor is not the owner of the view, then the // base table/view grant privilege checks were made when the grantor // got its grant privilege on the view and no further checks are // necessary. // As long as only locksmith can use GRANTED BY, no need specially checking // for privileges of current user. AP-2008 if (objType == 0) { // Relation or view because we cannot distinguish at this point. checkGrantorCanGrant(tdbb, transaction, tdbb->getAttachment()->att_user->usr_user_name.c_str(), priv, objName, field, true); } } storePrivilege(tdbb, transaction, objName, user, field, pr, userType, objType, options, grantorRevoker); } } else // REVOKE { AutoCacheRequest request(tdbb, (field.hasData() ? drq_e_grant1 : drq_e_grant2), DYN_REQUESTS); for (const char* pr = privileges; (priv[0] = *pr); ++pr) { bool grantErased = false; bool badGrantor = false; if (field.hasData()) { FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$RELATION_NAME EQ objName.c_str() AND PRIV.RDB$OBJECT_TYPE = objType AND PRIV.RDB$PRIVILEGE EQ priv AND PRIV.RDB$USER = user.c_str() AND PRIV.RDB$USER_TYPE = userType AND PRIV.RDB$FIELD_NAME EQ field.c_str() { if (grantorRevoker == PRIV.RDB$GRANTOR) { ERASE PRIV; grantErased = true; } else badGrantor = true; } END_FOR } else { FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$PRIVILEGE EQ priv AND PRIV.RDB$RELATION_NAME EQ objName.c_str() AND PRIV.RDB$OBJECT_TYPE = objType AND PRIV.RDB$USER EQ user.c_str() AND PRIV.RDB$USER_TYPE = userType { // Revoking a permission at the table level implies revoking the perm. on all // columns. So for all fields in this table which have been granted the // privilege, we erase the entries from RDB$USER_PRIVILEGES. if (grantorRevoker == PRIV.RDB$GRANTOR) { ERASE PRIV; grantErased = true; } else badGrantor = true; } END_FOR } if (options && grantErased) { // Add the privilege without the grant option. There is a modify trigger on the // rdb$user_privileges which disallows the table from being updated. It would have // to be changed such that only the grant_option field can be updated. storePrivilege(tdbb, transaction, objName, user, field, pr, userType, objType, 0, grantorRevoker); } if (badGrantor && !grantErased) { // msg 246: @1 is not grantor of @2 on @3 to @4. status_exception::raise(Arg::PrivateDyn(246) << grantorRevoker.c_str() << privilegeName(priv[0]) << objName.c_str() << user.c_str()); } if (!grantErased) { // msg 247: Warning: @1 on @2 is not granted to @3. ERR_post_warning( Arg::Warning(isc_dyn_miss_priv_warning) << Arg::Str(privilegeName(priv[0])) << Arg::Str(objName) << Arg::Str(user)); } } } } // Check if the grantor has grant privilege on the relation/field. void GrantRevokeNode::checkGrantorCanGrant(thread_db* tdbb, jrd_tra* transaction, const char* grantor, const char* privilege, const MetaName& relationName, const MetaName& fieldName, bool topLevel) { // Verify that the input relation exists. AutoCacheRequest request(tdbb, drq_gcg4, DYN_REQUESTS); bool sqlRelation = false; bool relationExists = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) REL IN RDB$RELATIONS WITH REL.RDB$RELATION_NAME = relationName.c_str() { relationExists = true; if (!REL.RDB$FLAGS.NULL && (REL.RDB$FLAGS & REL_sql)) sqlRelation = true; } END_FOR if (!relationExists) { // table/view .. does not exist status_exception::raise(Arg::PrivateDyn(175) << relationName.c_str()); } // Verify the the input field exists. if (fieldName.hasData()) { bool fieldExists = false; request.reset(tdbb, drq_gcg5, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) G_FLD IN RDB$RELATION_FIELDS WITH G_FLD.RDB$RELATION_NAME = relationName.c_str() AND G_FLD.RDB$FIELD_NAME = fieldName.c_str() { fieldExists = true; } END_FOR if (!fieldExists) { // column .. does not exist in table/view .. status_exception::raise(Arg::PrivateDyn(176) << fieldName.c_str() << relationName.c_str()); } } // If the current user is locksmith - allow all grants to occur if (tdbb->getAttachment()->locksmith()) return; // If this is a non-sql table, then the owner will probably not have any // entries in the rdb$user_privileges table. Give the owner of a GDML // table all privileges. bool grantorIsOwner = false; request.reset(tdbb, drq_gcg2, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) REL IN RDB$RELATIONS WITH REL.RDB$RELATION_NAME = relationName.c_str() AND REL.RDB$OWNER_NAME = UPPERCASE(grantor) { grantorIsOwner = true; } END_FOR if (!sqlRelation && grantorIsOwner) return; // Remember the grant option for non field-specific user-privileges, and // the grant option for the user-privileges for the input field. // -1 = no privilege found (yet) // 0 = privilege without grant option found // 1 = privilege with grant option found SSHORT goRel = -1; SSHORT goFld = -1; // Verify that the grantor has the grant option for this relation/field // in the rdb$user_privileges. If not, then we don't need to look further. request.reset(tdbb, drq_gcg1, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) PRV IN RDB$USER_PRIVILEGES WITH PRV.RDB$USER = UPPERCASE(grantor) AND PRV.RDB$USER_TYPE = obj_user AND PRV.RDB$RELATION_NAME = relationName.c_str() AND PRV.RDB$OBJECT_TYPE = obj_relation AND PRV.RDB$PRIVILEGE = privilege { if (PRV.RDB$FIELD_NAME.NULL) { if (PRV.RDB$GRANT_OPTION.NULL || !PRV.RDB$GRANT_OPTION) goRel = 0; else if (goRel) goRel = 1; } else { if (PRV.RDB$GRANT_OPTION.NULL || !PRV.RDB$GRANT_OPTION) { if (fieldName.hasData() && fieldName == PRV.RDB$FIELD_NAME) goFld = 0; } else { if (fieldName.hasData() && fieldName == PRV.RDB$FIELD_NAME) goFld = 1; } } } END_FOR if (fieldName.hasData()) { if (goFld == 0) { // no grant option for privilege .. on column .. of [base] table/view .. status_exception::raise(Arg::PrivateDyn(topLevel ? 167 : 168) << privilege << fieldName.c_str() << relationName.c_str()); } if (goFld == -1) { if (goRel == 0) { // no grant option for privilege .. on [base] table/view .. (for column ..) status_exception::raise(Arg::PrivateDyn(topLevel ? 169 : 170) << privilege << relationName.c_str() << fieldName.c_str()); } if (goRel == -1) { // no .. privilege with grant option on [base] table/view .. (for column ..) status_exception::raise(Arg::PrivateDyn(topLevel ? 171 : 172) << privilege << relationName.c_str() << fieldName.c_str()); } } } else { if (goRel == 0) { // no grant option for privilege .. on table/view .. status_exception::raise(Arg::PrivateDyn(173) << privilege << relationName.c_str()); } if (goRel == -1) { // no .. privilege with grant option on table/view .. status_exception::raise(Arg::PrivateDyn(174) << privilege << relationName.c_str()); } } // If the grantor is not the owner of the relation, then we don't need to // check the base table(s)/view(s) because that check was performed when // the grantor was given its privileges. if (!grantorIsOwner) return; // Find all the base fields/relations and check for the correct grant privileges on them. request.reset(tdbb, drq_gcg3, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) G_FLD IN RDB$RELATION_FIELDS CROSS G_VIEW IN RDB$VIEW_RELATIONS WITH G_FLD.RDB$RELATION_NAME = relationName.c_str() AND G_FLD.RDB$BASE_FIELD NOT MISSING AND G_VIEW.RDB$VIEW_NAME EQ G_FLD.RDB$RELATION_NAME AND G_VIEW.RDB$VIEW_CONTEXT EQ G_FLD.RDB$VIEW_CONTEXT { if (fieldName.hasData()) { if (fieldName == G_FLD.RDB$FIELD_NAME) { checkGrantorCanGrant(tdbb, transaction, grantor, privilege, G_VIEW.RDB$RELATION_NAME, G_FLD.RDB$BASE_FIELD, false); } } else { checkGrantorCanGrant(tdbb, transaction, grantor, privilege, G_VIEW.RDB$RELATION_NAME, G_FLD.RDB$BASE_FIELD, false); } } END_FOR } // Check if the grantor has admin privilege on the role. void GrantRevokeNode::checkGrantorCanGrantRole(thread_db* tdbb, jrd_tra* transaction, const MetaName& grantor, const MetaName& roleName) { // Fetch the name of the owner of the ROLE. MetaName owner; if (isItSqlRole(tdbb, transaction, roleName, owner)) { // Both SYSDBA and the owner of this ROLE can grant membership if (tdbb->getAttachment()->locksmith() || owner == grantor) return; } else { // 188: role name not exist. status_exception::raise(Arg::PrivateDyn(188) << roleName.c_str()); } AutoCacheRequest request(tdbb, drq_get_role_au, DYN_REQUESTS); bool grantable = false; bool noAdmin = false; // The 'grantor' is not the owner of the ROLE, see if they have admin privilege on the role. FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) PRV IN RDB$USER_PRIVILEGES WITH PRV.RDB$USER = UPPERCASE(grantor.c_str()) AND PRV.RDB$USER_TYPE = obj_user AND PRV.RDB$RELATION_NAME EQ roleName.c_str() AND PRV.RDB$OBJECT_TYPE = obj_sql_role AND PRV.RDB$PRIVILEGE EQ "M" { if (PRV.RDB$GRANT_OPTION == 2) grantable = true; else noAdmin = true; } END_FOR if (!grantable) { // 189: user have no admin option. // 190: user is not a member of the role. status_exception::raise(Arg::PrivateDyn(noAdmin ? 189 : 190) << grantor.c_str() << roleName.c_str()); } } void GrantRevokeNode::storePrivilege(thread_db* tdbb, jrd_tra* transaction, const MetaName& object, const MetaName& user, const MetaName& field, const TEXT* privilege, SSHORT userType, SSHORT objType, int option, const MetaName& grantor) { AutoCacheRequest request(tdbb, drq_s_grant, DYN_REQUESTS); STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES PRIV.RDB$FIELD_NAME.NULL = TRUE; strcpy(PRIV.RDB$RELATION_NAME, object.c_str()); strcpy(PRIV.RDB$USER, user.c_str()); strcpy(PRIV.RDB$GRANTOR, grantor.c_str()); PRIV.RDB$USER_TYPE = userType; PRIV.RDB$OBJECT_TYPE = objType; { if (field.hasData()) { strcpy(PRIV.RDB$FIELD_NAME, field.c_str()); PRIV.RDB$FIELD_NAME.NULL = FALSE; setFieldClassName(tdbb, transaction, object, field); } PRIV.RDB$PRIVILEGE[0] = privilege[0]; PRIV.RDB$PRIVILEGE[1] = 0; PRIV.RDB$GRANT_OPTION = option; } END_STORE } // For field level grants, be sure the field has a unique class name. void GrantRevokeNode::setFieldClassName(thread_db* tdbb, jrd_tra* transaction, const MetaName& relation, const MetaName& field) { AutoCacheRequest request(tdbb, drq_s_f_class, DYN_REQUESTS); bool unique = false; FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) RFR IN RDB$RELATION_FIELDS WITH RFR.RDB$FIELD_NAME = field.c_str() AND RFR.RDB$RELATION_NAME = relation.c_str() AND RFR.RDB$SECURITY_CLASS MISSING { MODIFY RFR while (!unique) { sprintf(RFR.RDB$SECURITY_CLASS, "%s%" SQUADFORMAT, SQL_FLD_SECCLASS_PREFIX, DPM_gen_id(tdbb, MET_lookup_generator(tdbb, SQL_SECCLASS_GENERATOR), false, 1)); unique = true; AutoCacheRequest request2(tdbb, drq_s_u_class, DYN_REQUESTS); FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) RFR1 IN RDB$RELATION_FIELDS WITH RFR1.RDB$SECURITY_CLASS = RFR.RDB$SECURITY_CLASS { unique = false; } END_FOR } RFR.RDB$SECURITY_CLASS.NULL = FALSE; END_MODIFY } END_FOR } //---------------------- void AlterDatabaseNode::print(string& text) const { text.printf( "AlterDatabaseNode\n"); } void AlterDatabaseNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction) { if (!tdbb->getAttachment()->locksmith()) status_exception::raise(Arg::Gds(isc_adm_task_denied)); // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); if (cryptPlugin.hasData()) DFW_post_work(transaction, dfw_db_crypt, cryptPlugin.c_str(), 0); if (clauses & CLAUSE_DECRYPT) DFW_post_work(transaction, dfw_db_crypt, "", 0); Attachment* const attachment = transaction->tra_attachment; SLONG dbAlloc = PageSpace::maxAlloc(tdbb->getDatabase()); SLONG start = create ? createLength + 1 : 0; AutoCacheRequest request(tdbb, drq_m_database, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) DBB IN RDB$DATABASE { MODIFY DBB USING if (clauses & CLAUSE_DROP_DIFFERENCE) changeBackupMode(tdbb, transaction, CLAUSE_DROP_DIFFERENCE); for (NestConst* i = files.begin(); i != files.end(); ++i) { DbFileClause* file = *i; start = MAX(start, file->start); defineFile(tdbb, transaction, 0, false, false, dbAlloc, file->name.c_str(), start, file->length); start += file->length; } if (differenceFile.hasData()) defineDifference(tdbb, transaction, differenceFile.c_str()); if (setDefaultCharSet.hasData()) { //// TODO: Validate! DBB.RDB$CHARACTER_SET_NAME.NULL = FALSE; strcpy(DBB.RDB$CHARACTER_SET_NAME, setDefaultCharSet.c_str()); } if (!DBB.RDB$CHARACTER_SET_NAME.NULL && setDefaultCollation.hasData()) { AlterCharSetNode alterCharSetNode(getPool(), setDefaultCharSet, setDefaultCollation); alterCharSetNode.execute(tdbb, dsqlScratch, transaction); } if (linger >= 0) { DBB.RDB$LINGER.NULL = FALSE; DBB.RDB$LINGER = linger; } if (clauses & CLAUSE_BEGIN_BACKUP) changeBackupMode(tdbb, transaction, CLAUSE_BEGIN_BACKUP); if (clauses & CLAUSE_END_BACKUP) changeBackupMode(tdbb, transaction, CLAUSE_END_BACKUP); END_MODIFY } END_FOR savePoint.release(); // everything is ok } // Drop backup difference file for the database, begin or end backup. void AlterDatabaseNode::changeBackupMode(thread_db* tdbb, jrd_tra* transaction, unsigned clause) { AutoCacheRequest request(tdbb, drq_d_difference, DYN_REQUESTS); bool invalidState = false; bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$FILES { if (X.RDB$FILE_FLAGS & FILE_difference) { found = true; switch (clause) { case CLAUSE_DROP_DIFFERENCE: ERASE X; break; case CLAUSE_BEGIN_BACKUP: if (X.RDB$FILE_FLAGS & FILE_backing_up) invalidState = true; else { MODIFY X USING X.RDB$FILE_FLAGS |= FILE_backing_up; END_MODIFY } break; case CLAUSE_END_BACKUP: if (X.RDB$FILE_FLAGS & FILE_backing_up) { if (X.RDB$FILE_NAME.NULL) ERASE X; else { MODIFY X USING X.RDB$FILE_FLAGS &= ~FILE_backing_up; END_MODIFY } } else invalidState = true; break; } } } END_FOR if (!found && clause == CLAUSE_BEGIN_BACKUP) { request.reset(tdbb, drq_s2_difference, DYN_REQUESTS); STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$FILES { X.RDB$FILE_FLAGS = FILE_difference | FILE_backing_up; X.RDB$FILE_START = 0; } END_STORE found = true; } if (invalidState) { // msg 217: "Database is already in the physical backup mode" // msg 218: "Database is not in the physical backup mode" status_exception::raise(Arg::PrivateDyn(clause == CLAUSE_BEGIN_BACKUP ? 217 : 218)); } if (!found) { // msg 218: "Database is not in the physical backup mode" // msg 215: "Difference file is not defined" status_exception::raise(Arg::PrivateDyn(clause == CLAUSE_END_BACKUP ? 218 : 215)); } } // Define backup difference file. void AlterDatabaseNode::defineDifference(thread_db* tdbb, jrd_tra* transaction, const PathName& file) { AutoCacheRequest request(tdbb, drq_l_difference, DYN_REQUESTS); bool found = false; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FIL IN RDB$FILES { if (FIL.RDB$FILE_FLAGS & FILE_difference) found = true; } END_FOR if (found) { // msg 216: "Difference file is already defined" status_exception::raise(Arg::PrivateDyn(216)); } request.reset(tdbb, drq_s_difference, DYN_REQUESTS); STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FIL IN RDB$FILES { if (file.length() >= sizeof(FIL.RDB$FILE_NAME)) status_exception::raise(Arg::Gds(isc_dyn_name_longer)); strcpy(FIL.RDB$FILE_NAME, file.c_str()); FIL.RDB$FILE_FLAGS = FILE_difference; FIL.RDB$FILE_START = 0; } END_STORE } } // namespace Jrd