/* * 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 "../jrd/common.h" #include "../dsql/DdlNodes.h" #include "../dsql/node.h" #include "../jrd/blr.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/tra.h" #include "../jrd/IntlManager.h" #include "../jrd/PreparedStatement.h" #include "../jrd/blb_proto.h" #include "../jrd/cmp_proto.h" #include "../jrd/dsc_proto.h" #include "../jrd/dyn_dl_proto.h" #include "../jrd/dyn_ut_proto.h" #include "../jrd/exe_proto.h" #include "../jrd/intl_proto.h" #include "../jrd/met_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 "../common/StatusArg.h" using namespace Firebird; namespace Jrd { using namespace Firebird; using namespace Dsql; static void rethrowMetaException(const status_exception& ex, ISC_STATUS code, bool metaDataException); DATABASE DB = STATIC "ODS.RDB"; //---------------------- // Rethrow an exception with isc_no_meta_update and code prefix when necessary. static void rethrowMetaException(const status_exception& ex, ISC_STATUS code, bool metaDataException) { Arg::StatusVector newVector; const ISC_STATUS* status = ex.value(); if (metaDataException) { if (status[1] != isc_no_meta_update) newVector << Arg::Gds(isc_no_meta_update); if (code != 0) newVector << Arg::Gds(code); } newVector.append(Arg::StatusVector(status)); status_exception::raise(newVector); } // 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.type == dtype_blob) { fieldSubTypeNull = FALSE; fieldSubType = type.subType; fieldScaleNull = FALSE; fieldScale = 0; if (type.subType == isc_blob_text) { characterSetIdNull = FALSE; characterSetId = type.charSetId; collationIdNull = !type.collateSpecified; collationId = type.collationId; } if (type.segLength != 0) { segmentLengthNull = FALSE; segmentLength = type.segLength; } } else if (type.type <= dtype_any_text) { fieldSubTypeNull = FALSE; fieldSubType = type.subType; fieldScaleNull = FALSE; fieldScale = 0; characterLengthNull = FALSE; characterLength = type.charLength; characterSetIdNull = FALSE; characterSetId = type.charSetId; collationIdNull = !type.collateSpecified; collationId = type.collationId; } else { fieldScaleNull = FALSE; fieldScale = type.scale; if (DTYPE_IS_EXACT(type.type)) { fieldPrecisionNull = FALSE; fieldPrecision = type.precision; fieldSubTypeNull = FALSE; fieldSubType = type.subType; } } if (type.type == dtype_varying) { fb_assert(type.length <= MAX_SSHORT); fieldLength = (SSHORT) (type.length - sizeof(USHORT)); } else fieldLength = type.length; fieldType = blr_dtypes[type.type]; } //---------------------- void DdlNode::executeDdlTrigger(thread_db* tdbb, jrd_tra* transaction, DdlTriggerWhen when, int action, const MetaName& objectName, const string& sqlText) { Attachment* 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.ddlEvent = DDL_TRIGGER_ACTION_NAMES[action]; 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, jrd_tra* transaction, DdlNode::DdlTriggerWhen when, int action, const MetaName& objectName) { executeDdlTrigger(tdbb, transaction, when, action, objectName, sqlText); } void DdlNode::putType(const TypeClause& type, bool useSubType) { #ifdef DEV_BUILD // Check if the field describes a known datatype if (type.type > FB_NELEM(blr_dtypes) || !blr_dtypes[type.type]) { SCHAR buffer[100]; sprintf(buffer, "Invalid dtype %d in put_dtype", type.type); ERRD_bugcheck(buffer); } #endif if (type.notNull) dsqlScratch->appendUChar(blr_not_nullable); if (type.typeOfName.hasData()) { if (type.typeOfTable.hasData()) { if (type.collateSpecified) { dsqlScratch->appendUChar(blr_column_name2); dsqlScratch->appendUChar(type.fullDomain ? blr_domain_full : blr_domain_type_of); dsqlScratch->appendMetaString(type.typeOfTable.c_str()); dsqlScratch->appendMetaString(type.typeOfName.c_str()); dsqlScratch->appendUShort(type.textType); } else { dsqlScratch->appendUChar(blr_column_name); dsqlScratch->appendUChar(type.fullDomain ? blr_domain_full : blr_domain_type_of); dsqlScratch->appendMetaString(type.typeOfTable.c_str()); dsqlScratch->appendMetaString(type.typeOfName.c_str()); } } else { if (type.collateSpecified) { dsqlScratch->appendUChar(blr_domain_name2); dsqlScratch->appendUChar(type.fullDomain ? blr_domain_full : blr_domain_type_of); dsqlScratch->appendMetaString(type.typeOfName.c_str()); dsqlScratch->appendUShort(type.textType); } else { dsqlScratch->appendUChar(blr_domain_name); dsqlScratch->appendUChar(type.fullDomain ? blr_domain_full : blr_domain_type_of); dsqlScratch->appendMetaString(type.typeOfName.c_str()); } } return; } switch (type.type) { case dtype_cstring: case dtype_text: case dtype_varying: case dtype_blob: if (!useSubType) dsqlScratch->appendUChar(blr_dtypes[type.type]); else if (type.type == dtype_varying) { dsqlScratch->appendUChar(blr_varying2); dsqlScratch->appendUShort(type.textType); } else if (type.type == dtype_cstring) { dsqlScratch->appendUChar(blr_cstring2); dsqlScratch->appendUShort(type.textType); } else if (type.type == dtype_blob) { dsqlScratch->appendUChar(blr_blob2); dsqlScratch->appendUShort(type.subType); dsqlScratch->appendUShort(type.textType); } else { dsqlScratch->appendUChar(blr_text2); dsqlScratch->appendUShort(type.textType); } if (type.type == dtype_varying) dsqlScratch->appendUShort(type.length - sizeof(USHORT)); else if (type.type != dtype_blob) dsqlScratch->appendUShort(type.length); break; default: dsqlScratch->appendUChar(blr_dtypes[type.type]); if (DTYPE_IS_EXACT(type.type) || dtype_quad == type.type) dsqlScratch->appendUChar(type.scale); break; } } void DdlNode::resetContextStack() { dsqlScratch->context->clear(); dsqlScratch->contextNumber = 0; } MetaName DdlNode::storeGlobalField(thread_db* tdbb, jrd_tra* transaction, const TypeClause& field, MetaName& name) { bool endStore = false; try { if (name.isEmpty()) DYN_UTIL_generate_field_name(tdbb, NULL, 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()); 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); endStore = true; } END_STORE } catch (const status_exception& ex) { // STORE RDB$FIELDS failed rethrowMetaException(ex, ENCODE_ISC_MSG(13, DYN_MSG_FAC), endStore); } return name; } //---------------------- TypeClause::TypeClause(dsql_fld* aLegacyField, const MetaName& aCollate) : legacyField(aLegacyField), collate(aCollate) { } void TypeClause::resolve(DsqlCompilerScratch* dsqlScratch) { DDL_resolve_intl_type(dsqlScratch, legacyField, (collate.isEmpty() ? NULL : MAKE_cstring(collate.c_str()))); type = legacyField->fld_dtype; length = legacyField->fld_length; scale = legacyField->fld_scale; subType = legacyField->fld_sub_type; segLength = legacyField->fld_seg_length; precision = legacyField->fld_precision; charLength = legacyField->fld_character_length; charSetId = legacyField->fld_character_set_id; collationId = legacyField->fld_collation_id; collateSpecified = collate.hasData(); textType = legacyField->fld_ttype; fullDomain = legacyField->fld_full_domain; notNull = legacyField->fld_not_nullable; fieldSource = legacyField->fld_source; if (legacyField->fld_type_of_table) typeOfTable = legacyField->fld_type_of_table->str_data; typeOfName = legacyField->fld_type_of_name; } void TypeClause::print(string& text) const { text.printf("typeOfTable: '%s' typeOfName: '%s' notNull: %d fieldSource: '%s'", typeOfTable.c_str(), typeOfName.c_str(), notNull, fieldSource.c_str()); } //---------------------- ParameterClause::ParameterClause(dsql_fld* field, const MetaName& aCollate, dsql_nod* dflt, dsql_nod* aLegacyParameter) : TypeClause(field, aCollate), name(field->fld_name), legacyDefault(dflt), legacyParameter(aLegacyParameter) { } void ParameterClause::print(string& text) const { string s; TypeClause::print(s); text.printf("name: '%s' %s", name.c_str(), s.c_str()); } //---------------------- void AlterCharSetNode::print(string& text, Array& /*nodes*/) const { text.printf( "AlterCharSetNode\n" " charSet: %s\n" " defaultCollation: %s\n", charSet.c_str(), defaultCollation.c_str()); } void AlterCharSetNode::execute(thread_db* tdbb, 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, 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_no_meta_update) << Arg::Gds(isc_charset_not_found) << Arg::Str(charSet)); } if (!collationFound) { status_exception::raise(Arg::Gds(isc_no_meta_update) << Arg::Gds(isc_collation_not_found) << Arg::Str(defaultCollation) << Arg::Str(charSet)); } executeDdlTrigger(tdbb, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_CHARACTER_SET, charSet); } //---------------------- void CommentOnNode::print(string& text, Array& /*nodes*/) const { text.printf( "CommentOnNode\n" " objType: %s\n" " objName: %s\n" " text: %s\n", objType, objName.c_str(), 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. void CommentOnNode::execute(thread_db* tdbb, jrd_tra* transaction) { Attachment* attachment = transaction->tra_attachment; const char* tableClause = NULL; const char* columnClause = NULL; const char* subColumnClause = NULL; const char* addWhereClause = NULL; Arg::StatusVector status; switch (objType) { case ddl_database: tableClause = "rdb$database"; break; case ddl_domain: tableClause = "rdb$fields"; columnClause = "rdb$field_name"; status << Arg::Gds(isc_dyn_domain_not_found); break; case ddl_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 ddl_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 ddl_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 ddl_trigger: tableClause = "rdb$triggers"; columnClause = "rdb$trigger_name"; status << Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(objName); break; case ddl_udf: tableClause = "rdb$functions"; columnClause = "rdb$function_name"; addWhereClause = "rdb$package_name is null"; status << Arg::Gds(isc_dyn_func_not_found) << Arg::Str(objName); break; case ddl_blob_filter: tableClause = "rdb$filters"; columnClause = "rdb$function_name"; status << Arg::Gds(isc_dyn_filter_not_found) << Arg::Str(objName); break; case ddl_exception: tableClause = "rdb$exceptions"; columnClause = "rdb$exception_name"; status << Arg::Gds(isc_dyn_exception_not_found) << Arg::Str(objName); break; case ddl_generator: tableClause = "rdb$generators"; columnClause = "rdb$generator_name"; status << Arg::Gds(isc_dyn_gen_not_found) << Arg::Str(objName); break; case ddl_index: tableClause = "rdb$indices"; columnClause = "rdb$index_name"; status << Arg::Gds(isc_dyn_index_not_found) << Arg::Str(objName); break; case ddl_role: tableClause = "rdb$roles"; columnClause = "rdb$role_name"; status << Arg::Gds(isc_dyn_role_not_found) << Arg::Str(objName); break; case ddl_charset: tableClause = "rdb$character_sets"; columnClause = "rdb$character_set_name"; status << Arg::Gds(isc_dyn_charset_not_found) << Arg::Str(objName); break; case ddl_collation: tableClause = "rdb$collations"; columnClause = "rdb$collation_name"; status << Arg::Gds(isc_dyn_collation_not_found) << Arg::Str(objName); break; case ddl_package: tableClause = "rdb$packages"; columnClause = "rdb$package_name"; status << Arg::Gds(isc_dyn_package_not_found) << Arg::Str(objName); break; case ddl_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; } TriStateType description; if (!text.isEmpty()) description = attachment->stringToMetaCharSet(tdbb, text, textCharSet); 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, Array& /*nodes*/) 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::internalDsqlPass() { DsqlCompiledStatement* const statement = dsqlScratch->getStatement(); statement->setBlockNode(this); dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_FUNCTION); const dsql_nod* 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 dsql_nod* const* ptr = variables->nod_arg; for (const dsql_nod* const* const end = ptr + variables->nod_count; ptr < end; ptr++) { if ((*ptr)->nod_type == nod_def_field) { const dsql_fld* field = (dsql_fld*) (*ptr)->nod_arg[e_dfl_field]; 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.legacyDefault) { hasDefaultParams = true; parameter.legacyDefault->nod_arg[e_dft_default] = PASS1_node(dsqlScratch, parameter.legacyDefault->nod_arg[e_dft_default]); } 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].resolve(dsqlScratch); returnType.resolve(dsqlScratch); return DdlNode::internalDsqlPass(); } void CreateAlterFunctionNode::execute(thread_db* tdbb, 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, transaction, false, true)) { altered = true; } else { if (create) // create or alter { executeCreate(tdbb, transaction); } else { status_exception::raise( Arg::Gds(isc_no_meta_update) << Arg::Gds(isc_dyn_func_not_found) << Arg::Str(name)); } } } else { executeCreate(tdbb, transaction); } compile(tdbb, transaction); executeAlter(tdbb, transaction, true, false); // second pass if (package.isEmpty()) { executeDdlTrigger(tdbb, 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.c_str(), package); } } void CreateAlterFunctionNode::executeCreate(thread_db* tdbb, jrd_tra* transaction) { Attachment* attachment = transaction->getAttachment(); if (package.isEmpty()) { executeDdlTrigger(tdbb, 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()); FUN.RDB$LEGACY_FLAG.NULL = FALSE; FUN.RDB$LEGACY_FLAG = 0; FUN.RDB$INVARIANT_FLAG.NULL = FALSE; FUN.RDB$INVARIANT_FLAG = invariant ? TRUE : FALSE; FUN.RDB$RETURN_ARGUMENT.NULL = FALSE; FUN.RDB$RETURN_ARGUMENT = 0; 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, attachment->att_user->usr_user_name.c_str()); } } END_STORE break; } catch (const status_exception& ex) { if (ex.value()[1] != isc_no_dup) throw; if (++faults > MAX_SSHORT) throw; fb_utils::init_status(tdbb->tdbb_status_vector); } } if (package.isEmpty()) { for (const TEXT* p = ALL_PROC_PRIVILEGES; *p; p++) { requestHandle.reset(tdbb, drq_s_fun_usr_prvs, DYN_REQUESTS); STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) X IN RDB$USER_PRIVILEGES { strcpy(X.RDB$RELATION_NAME, name.c_str()); strcpy(X.RDB$USER, attachment->att_user->usr_user_name.c_str()); X.RDB$USER_TYPE = obj_user; X.RDB$OBJECT_TYPE = obj_udf; X.RDB$PRIVILEGE[0] = *p; X.RDB$PRIVILEGE[1] = 0; } END_STORE } } executeAlter(tdbb, transaction, false, false); } bool CreateAlterFunctionNode::executeAlter(thread_db* tdbb, jrd_tra* transaction, bool secondPass, bool runTriggers) { Attachment* attachment = transaction->getAttachment(); bool modified = false; 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_no_meta_update) << Arg::Gds(isc_dyn_cannot_mod_sysfunc) << FUN.RDB$FUNCTION_NAME); } if (!secondPass && runTriggers && package.isEmpty()) executeDdlTrigger(tdbb, 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$ENTRYPOINT.NULL = TRUE; FUN.RDB$FUNCTION_SOURCE.NULL = TRUE; FUN.RDB$VALID_BLR.NULL = FALSE; FUN.RDB$VALID_BLR = 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); } if (external) { if (!secondPass) { FUN.RDB$ENGINE_NAME.NULL = FALSE; strcpy(FUN.RDB$ENGINE_NAME, external->engine.c_str()); if (external->name.length() >= sizeof(FUN.RDB$ENTRYPOINT)) { status_exception::raise( Arg::Gds(isc_arith_except) << Arg::Gds(isc_string_truncation)); } FUN.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty(); strcpy(FUN.RDB$ENTRYPOINT, external->name.c_str()); } } else if (body) { if (secondPass) { 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; } END_MODIFY modified = true; } END_FOR if (!secondPass && modified) { // delete all old arguments DropFunctionNode::dropArguments(tdbb, transaction, name, package); // and insert the new ones ParameterClause returnParameter(returnType.legacyField, returnType.collate, NULL, NULL); returnParameter.resolve(dsqlScratch); storeArgument(tdbb, transaction, 0, returnParameter); for (unsigned i = 0; i < parameters.getCount(); ++i) { ParameterClause& parameter = parameters[i]; storeArgument(tdbb, transaction, i + 1, parameter); } } return modified; } void CreateAlterFunctionNode::storeArgument(thread_db* tdbb, jrd_tra* transaction, unsigned pos, const ParameterClause& parameter) { Attachment* attachment = transaction->getAttachment(); DsqlCompiledStatement* statement = dsqlScratch->getStatement(); 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()); // Avoid the return with name INTERNAL_FIELD_NAME. if (parameter.name.hasData() && pos != 0) { ARG.RDB$ARGUMENT_NAME.NULL = FALSE; strcpy(ARG.RDB$ARGUMENT_NAME, parameter.name.c_str()); } else ARG.RDB$ARGUMENT_NAME.NULL = TRUE; if (package.hasData()) { ARG.RDB$PACKAGE_NAME.NULL = FALSE; strcpy(ARG.RDB$PACKAGE_NAME, package.c_str()); } ARG.RDB$ARGUMENT_POSITION.NULL = FALSE; ARG.RDB$ARGUMENT_POSITION = pos; ARG.RDB$ARGUMENT_MECHANISM.NULL = TRUE; 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 = FALSE; ARG.RDB$ARGUMENT_MECHANISM = (USHORT) (parameter.fullDomain ? prm_mech_normal : prm_mech_type_of); if (parameter.notNull) { ARG.RDB$NULL_FLAG.NULL = FALSE; ARG.RDB$NULL_FLAG = TRUE; } ARG.RDB$FIELD_SOURCE.NULL = FALSE; if (parameter.typeOfTable.isEmpty()) { if (parameter.typeOfName.hasData()) strcpy(ARG.RDB$FIELD_SOURCE, parameter.typeOfName.c_str()); else { MetaName fieldName; storeGlobalField(tdbb, transaction, parameter, fieldName); strcpy(ARG.RDB$FIELD_SOURCE, fieldName.c_str()); } } else { ARG.RDB$RELATION_NAME.NULL = FALSE; strcpy(ARG.RDB$RELATION_NAME, parameter.typeOfTable.c_str()); ARG.RDB$FIELD_NAME.NULL = FALSE; strcpy(ARG.RDB$FIELD_NAME, parameter.typeOfName.c_str()); strcpy(ARG.RDB$FIELD_SOURCE, parameter.fieldSource.c_str()); } // ASF: If we used a collate with a domain or table.column type, write it // into RDB$FUNCTION_ARGUMENTS. if (parameter.collateSpecified && parameter.typeOfName.hasData()) { ARG.RDB$COLLATION_ID.NULL = FALSE; ARG.RDB$COLLATION_ID = parameter.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.legacyDefault) { ARG.RDB$DEFAULT_VALUE.NULL = FALSE; ARG.RDB$DEFAULT_SOURCE.NULL = FALSE; dsql_str* defaultString = (dsql_str*) parameter.legacyDefault->nod_arg[e_dft_default_source]; string defaultSource = string(defaultString->str_data, defaultString->str_length); attachment->storeMetaDataBlob(tdbb, transaction, &ARG.RDB$DEFAULT_SOURCE, defaultSource); dsqlScratch->getBlrData().clear(); if (statement->getFlags() & DsqlCompiledStatement::FLAG_BLR_VERSION4) dsqlScratch->appendUChar(blr_version4); else dsqlScratch->appendUChar(blr_version5); GEN_expr(dsqlScratch, parameter.legacyDefault->nod_arg[e_dft_default]); dsqlScratch->appendUChar(blr_eoc); attachment->storeBinaryBlob(tdbb, transaction, &ARG.RDB$DEFAULT_VALUE, dsqlScratch->getBlrData()); } } END_STORE } void CreateAlterFunctionNode::compile(thread_db* tdbb, jrd_tra* /*transaction*/) { if (invalid) status_exception::raise(Arg::Gds(isc_dyn_invalid_ddl_func) << name); if (compiled) return; compiled = true; invalid = true; DsqlCompiledStatement* statement = dsqlScratch->getStatement(); if (body) { dsqlScratch->beginDebug(); dsqlScratch->getBlrData().clear(); if (statement->getFlags() & DsqlCompiledStatement::FLAG_BLR_VERSION4) dsqlScratch->appendUChar(blr_version4); else dsqlScratch->appendUChar(blr_version5); dsqlScratch->appendUChar(blr_begin); if (parameters.getCount() != 0) { fb_assert(parameters.getCount() < size_t(MAX_USHORT / 2)); dsqlScratch->appendUChar(blr_message); dsqlScratch->appendUChar(0); dsqlScratch->appendUShort(2 * parameters.getCount()); for (unsigned i = 0; i < parameters.getCount(); ++i) { ParameterClause& parameter = parameters[i]; dsqlScratch->putDebugArgument(fb_dbg_arg_input, i, parameter.name.c_str()); putType(parameter, true); // add slot for null flag (parameter2) dsqlScratch->appendUChar(blr_short); dsqlScratch->appendUChar(0); variables.add(MAKE_variable(parameter.legacyField, parameter.name.c_str(), VAR_input, 0, (USHORT) (2 * i), 0)); } } dsqlScratch->appendUChar(blr_message); dsqlScratch->appendUChar(1); dsqlScratch->appendUShort(2); dsqlScratch->putDebugArgument(fb_dbg_arg_output, 0, ""); putType(returnType, true); // add slot for null flag (parameter2) dsqlScratch->appendUChar(blr_short); dsqlScratch->appendUChar(0); dsql_nod* const var = MAKE_variable(returnType.legacyField, "", VAR_output, 1, 0, 0); variables.add(var); outputVariables.add(var); 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.fullDomain || parameter.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 = (dsql_var*) outputVariables[0]->nod_arg[Dsql::e_var_variable]; putLocalVariable(dsqlScratch, 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); putLocalVariables(dsqlScratch, localDeclList, 1); 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); dsqlScratch->loopLevel = 0; dsqlScratch->cursorNumber = 0; GEN_statement(dsqlScratch, PASS1_statement(dsqlScratch, body)); statement->setType(DsqlCompiledStatement::TYPE_DDL); dsqlScratch->appendUChar(blr_end); genReturn(dsqlScratch, false); dsqlScratch->appendUChar(blr_end); dsqlScratch->appendUChar(blr_eoc); dsqlScratch->endDebug(); } invalid = false; } 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) { 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 { bool erase = true; AutoCacheRequest requestHandle3(tdbb, drq_e_arg_gfld2, DYN_REQUESTS); FOR (REQUEST_HANDLE requestHandle3 TRANSACTION_HANDLE transaction) ARG2 IN RDB$FUNCTION_ARGUMENTS WITH ARG2.RDB$FUNCTION_NAME = ARG.RDB$FUNCTION_NAME AND ARG2.RDB$PACKAGE_NAME EQUIV NULLIF(packageName.c_str(), '') AND ARG2.RDB$ARGUMENT_NAME = ARG.RDB$ARGUMENT_NAME { if (!ARG2.RDB$RELATION_NAME.NULL && !ARG2.RDB$FIELD_NAME.NULL) erase = false; } END_FOR if (erase) ERASE FLD; } END_FOR } ERASE ARG; } END_FOR } void DropFunctionNode::print(string& text, Array& /*nodes*/) const { text.printf( "DropFunctionNode\n" " name: '%s'\n", name.c_str()); } DdlNode* DropFunctionNode::internalDsqlPass() { dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_FUNCTION); return DdlNode::internalDsqlPass(); } void DropFunctionNode::execute(thread_db* tdbb, 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_no_meta_update) << Arg::Gds(isc_dyn_cannot_mod_sysfunc) << FUN.RDB$FUNCTION_NAME); } if (package.isEmpty()) executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_FUNCTION, name); ERASE FUN; if (!FUN.RDB$SECURITY_CLASS.NULL) DYN_delete_security_class2(transaction, FUN.RDB$SECURITY_CLASS); found = true; } END_FOR if (!found && !silent) { status_exception::raise( Arg::Gds(isc_no_meta_update) << 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, 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.c_str(), package); } //---------------------- void RecreateFunctionNode::print(string& text, Array& /*nodes*/) const { text.printf("RecreateFunctionNode\n"); } DdlNode* RecreateFunctionNode::internalDsqlPass() { createNode->dsqlPass(dsqlScratch); dropNode.dsqlPass(dsqlScratch); return DdlNode::internalDsqlPass(); } void RecreateFunctionNode::execute(thread_db* tdbb, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); dropNode.executeDdl(tdbb, transaction); createNode->executeDdl(tdbb, transaction); savePoint.release(); // everything is ok } //---------------------- void CreateAlterProcedureNode::print(string& text, Array& /*nodes*/) 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::internalDsqlPass() { DsqlCompiledStatement* statement = dsqlScratch->getStatement(); statement->setBlockNode(this); dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_PROCEDURE); const dsql_nod* 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 dsql_nod* const* ptr = variables->nod_arg; for (const dsql_nod* const* const end = ptr + variables->nod_count; ptr < end; ptr++) { if ((*ptr)->nod_type == nod_def_field) { const dsql_fld* field = (dsql_fld*) (*ptr)->nod_arg[e_dfl_field]; 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.legacyDefault) { hasDefaultParams = true; parameter.legacyDefault->nod_arg[e_dft_default] = PASS1_node(dsqlScratch, parameter.legacyDefault->nod_arg[e_dft_default]); } 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].resolve(dsqlScratch); for (unsigned i = 0; i < returns.getCount(); ++i) returns[i].resolve(dsqlScratch); return DdlNode::internalDsqlPass(); } void CreateAlterProcedureNode::execute(thread_db* tdbb, 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, transaction, false, true)) altered = true; else { if (create) // create or alter executeCreate(tdbb, transaction); else { status_exception::raise( Arg::Gds(isc_no_meta_update) << Arg::Gds(isc_dyn_proc_not_found) << Arg::Str(name)); } } } else executeCreate(tdbb, transaction); compile(tdbb, transaction); executeAlter(tdbb, transaction, true, false); // second pass if (package.isEmpty()) { executeDdlTrigger(tdbb, 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.c_str(), package); } } void CreateAlterProcedureNode::executeCreate(thread_db* tdbb, jrd_tra* transaction) { Attachment* attachment = transaction->getAttachment(); if (package.isEmpty()) { executeDdlTrigger(tdbb, 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, attachment->att_user->usr_user_name.c_str()); } } END_STORE break; } catch (const status_exception& ex) { if (ex.value()[1] != isc_no_dup) throw; if (++faults > MAX_SSHORT) throw; fb_utils::init_status(tdbb->tdbb_status_vector); } } if (package.isEmpty()) { for (const TEXT* p = ALL_PROC_PRIVILEGES; *p; p++) { requestHandle.reset(tdbb, drq_s_prc_usr_prvs, DYN_REQUESTS); STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) X IN RDB$USER_PRIVILEGES { strcpy(X.RDB$RELATION_NAME, name.c_str()); strcpy(X.RDB$USER, attachment->att_user->usr_user_name.c_str()); X.RDB$USER_TYPE = obj_user; X.RDB$OBJECT_TYPE = obj_procedure; X.RDB$PRIVILEGE[0] = *p; X.RDB$PRIVILEGE[1] = 0; } END_STORE } } executeAlter(tdbb, transaction, false, false); } bool CreateAlterProcedureNode::executeAlter(thread_db* tdbb, jrd_tra* transaction, bool secondPass, bool runTriggers) { Attachment* 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_no_meta_update) << Arg::Gds(isc_dyn_cannot_mod_sysproc) << P.RDB$PROCEDURE_NAME); } if (!secondPass && runTriggers && package.isEmpty()) executeDdlTrigger(tdbb, 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 = 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_arith_except) << Arg::Gds(isc_string_truncation)); } P.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty(); strcpy(P.RDB$ENTRYPOINT, external->name.c_str()); } } else if (body) { if (secondPass) { 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); } } END_MODIFY modified = true; } END_FOR if (!secondPass && modified) { // delete all old input and output parameters DropProcedureNode::dropParameters(tdbb, transaction, name, package); // and insert the new ones for (unsigned i = 0; i < parameters.getCount(); ++i) { ParameterClause& parameter = parameters[i]; storeParameter(tdbb, transaction, 0, i, parameter); } for (unsigned i = 0; i < returns.getCount(); ++i) { ParameterClause& parameter = returns[i]; storeParameter(tdbb, transaction, 1, i, parameter); } 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, jrd_tra* transaction, USHORT type, unsigned pos, const ParameterClause& parameter) { Attachment* attachment = transaction->getAttachment(); 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 = type; PRM.RDB$PARAMETER_MECHANISM.NULL = FALSE; PRM.RDB$PARAMETER_MECHANISM = (USHORT) (parameter.fullDomain ? prm_mech_normal : prm_mech_type_of); PRM.RDB$NULL_FLAG.NULL = !parameter.notNull; PRM.RDB$NULL_FLAG = parameter.notNull; PRM.RDB$RELATION_NAME.NULL = parameter.typeOfTable.isEmpty(); PRM.RDB$FIELD_NAME.NULL = PRM.RDB$RELATION_NAME.NULL || parameter.typeOfName.isEmpty(); PRM.RDB$FIELD_SOURCE.NULL = FALSE; if (PRM.RDB$RELATION_NAME.NULL) { if (parameter.typeOfName.hasData()) strcpy(PRM.RDB$FIELD_SOURCE, parameter.typeOfName.c_str()); else { MetaName fieldName; storeGlobalField(tdbb, transaction, parameter, fieldName); strcpy(PRM.RDB$FIELD_SOURCE, fieldName.c_str()); } } else { strcpy(PRM.RDB$RELATION_NAME, parameter.typeOfTable.c_str()); strcpy(PRM.RDB$FIELD_NAME, parameter.typeOfName.c_str()); strcpy(PRM.RDB$FIELD_SOURCE, parameter.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 = !(parameter.collateSpecified && parameter.typeOfName.hasData()); if (!PRM.RDB$COLLATION_ID.NULL) PRM.RDB$COLLATION_ID = parameter.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.legacyDefault; PRM.RDB$DEFAULT_SOURCE.NULL = !parameter.legacyDefault; if (parameter.legacyDefault) { dsql_str* defaultString = (dsql_str*) parameter.legacyDefault->nod_arg[e_dft_default_source]; string defaultSource = string(defaultString->str_data, defaultString->str_length); attachment->storeMetaDataBlob(tdbb, transaction, &PRM.RDB$DEFAULT_SOURCE, defaultSource); DsqlCompiledStatement* statement = dsqlScratch->getStatement(); dsqlScratch->getBlrData().clear(); if (statement->getFlags() & DsqlCompiledStatement::FLAG_BLR_VERSION4) dsqlScratch->appendUChar(blr_version4); else dsqlScratch->appendUChar(blr_version5); GEN_expr(dsqlScratch, parameter.legacyDefault->nod_arg[e_dft_default]); dsqlScratch->appendUChar(blr_eoc); attachment->storeBinaryBlob(tdbb, transaction, &PRM.RDB$DEFAULT_VALUE, dsqlScratch->getBlrData()); } } END_STORE } void CreateAlterProcedureNode::compile(thread_db* tdbb, jrd_tra* /*transaction*/) { if (invalid) status_exception::raise(Arg::Gds(isc_dyn_invalid_ddl_proc) << name); if (compiled) return; compiled = true; if (!body) return; invalid = true; DsqlCompiledStatement* statement = dsqlScratch->getStatement(); dsqlScratch->beginDebug(); dsqlScratch->getBlrData().clear(); if (statement->getFlags() & DsqlCompiledStatement::FLAG_BLR_VERSION4) dsqlScratch->appendUChar(blr_version4); else dsqlScratch->appendUChar(blr_version5); dsqlScratch->appendUChar(blr_begin); if (parameters.getCount() != 0) { fb_assert(parameters.getCount() < MAX_USHORT / 2); dsqlScratch->appendUChar(blr_message); dsqlScratch->appendUChar(0); dsqlScratch->appendUShort(2 * parameters.getCount()); for (unsigned i = 0; i < parameters.getCount(); ++i) { ParameterClause& parameter = parameters[i]; dsqlScratch->putDebugArgument(fb_dbg_arg_input, i, parameter.name.c_str()); putType(parameter, true); // add slot for null flag (parameter2) dsqlScratch->appendUChar(blr_short); dsqlScratch->appendUChar(0); variables.add(MAKE_variable(parameter.legacyField, parameter.name.c_str(), VAR_input, 0, (USHORT) (2 * i), 0)); } } fb_assert(returns.getCount() < MAX_USHORT / 2); dsqlScratch->appendUChar(blr_message); dsqlScratch->appendUChar(1); dsqlScratch->appendUShort(2 * returns.getCount() + 1); if (returns.getCount() != 0) { for (unsigned i = 0; i < returns.getCount(); ++i) { ParameterClause& parameter = returns[i]; dsqlScratch->putDebugArgument(fb_dbg_arg_output, i, parameter.name.c_str()); putType(parameter, true); // add slot for null flag (parameter2) dsqlScratch->appendUChar(blr_short); dsqlScratch->appendUChar(0); dsql_nod* const var = MAKE_variable(parameter.legacyField, parameter.name.c_str(), VAR_output, 1, (USHORT) (2 * i), i); variables.add(var); outputVariables.add(var); } } // add slot for EOS dsqlScratch->appendUChar(blr_short); dsqlScratch->appendUChar(0); 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.fullDomain || parameter.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 = outputVariables.begin(); i != outputVariables.end(); ++i) { dsql_nod* parameter = *i; dsql_var* const variable = (dsql_var*) parameter->nod_arg[Dsql::e_var_variable]; putLocalVariable(dsqlScratch, 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); putLocalVariables(dsqlScratch, localDeclList, returns.getCount()); 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); dsqlScratch->loopLevel = 0; dsqlScratch->cursorNumber = 0; GEN_statement(dsqlScratch, PASS1_statement(dsqlScratch, body)); statement->setType(DsqlCompiledStatement::TYPE_DDL); dsqlScratch->appendUChar(blr_end); genReturn(dsqlScratch, true); dsqlScratch->appendUChar(blr_end); dsqlScratch->appendUChar(blr_eoc); dsqlScratch->endDebug(); invalid = false; } //---------------------- 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, Array& /*nodes*/) const { text.printf( "DropProcedureNode\n" " name: '%s'\n", name.c_str()); } DdlNode* DropProcedureNode::internalDsqlPass() { dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_PROCEDURE); return DdlNode::internalDsqlPass(); } void DropProcedureNode::execute(thread_db* tdbb, 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_no_meta_update) << Arg::Gds(isc_dyn_cannot_mod_sysproc) << PRC.RDB$PROCEDURE_NAME); } if (package.isEmpty()) executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_PROCEDURE, name); ERASE PRC; if (!PRC.RDB$SECURITY_CLASS.NULL) DYN_delete_security_class2(transaction, PRC.RDB$SECURITY_CLASS); found = true; } END_FOR if (!found && !silent) { status_exception::raise( Arg::Gds(isc_no_meta_update) << 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, 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.c_str(), package); } //---------------------- void RecreateProcedureNode::print(string& text, Array& /*nodes*/) const { text.printf("RecreateProcedureNode\n"); } DdlNode* RecreateProcedureNode::internalDsqlPass() { dropNode.dsqlPass(dsqlScratch); createNode->dsqlPass(dsqlScratch); return DdlNode::internalDsqlPass(); } void RecreateProcedureNode::execute(thread_db* tdbb, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); dropNode.executeDdl(tdbb, transaction); createNode->executeDdl(tdbb, transaction); savePoint.release(); // everything is ok } //---------------------- void CreateAlterTriggerNode::print(string& text, Array& /*nodes*/) 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::internalDsqlPass() { DsqlCompiledStatement* statement = dsqlScratch->getStatement(); statement->setBlockNode(this); 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::internalDsqlPass(); } void CreateAlterTriggerNode::execute(thread_db* tdbb, jrd_tra* transaction) { fb_assert(create || alter); Attachment* 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_no_meta_update) << Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(name)); } } compile(tdbb, transaction); if (alter) { if (!executeAlter(tdbb, transaction, true)) { if (create) // create or alter executeCreate(tdbb, transaction); else { status_exception::raise( Arg::Gds(isc_no_meta_update) << Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(name)); } } } else executeCreate(tdbb, transaction); savePoint.release(); // everything is ok } void CreateAlterTriggerNode::executeCreate(thread_db* tdbb, jrd_tra* transaction) { executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_TRIGGER, name); AutoCacheRequest requestHandle(tdbb, drq_s_triggers2, DYN_REQUESTS); bool endStore = false; try { STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) TRG IN RDB$TRIGGERS { TRG.RDB$SYSTEM_FLAG = 0; TRG.RDB$FLAGS = TRG_sql; // ASF: For FK triggers, TRG_ignore_perm will also be needed. 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); endStore = true; } END_STORE } catch (const status_exception& ex) { rethrowMetaException(ex, ENCODE_ISC_MSG(31, DYN_MSG_FAC), endStore); // DEFINE TRIGGER failed } executeAlter(tdbb, transaction, false); executeDdlTrigger(tdbb, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_TRIGGER, name); } bool CreateAlterTriggerNode::executeAlter(thread_db* tdbb, jrd_tra* transaction, bool runTriggers) { Attachment* 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); try { 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 && ((create && relationName.isEmpty()) || TRG.RDB$RELATION_NAME.NULL)) { status_exception::raise( Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_db_trigger_type_cant_change)); } if (!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_no_meta_update) << Arg::Gds(isc_dyn_cant_modify_auto_trig)); break; case fb_sysflag_system: status_exception::raise(Arg::Gds(isc_no_meta_update) << Arg::Gds(isc_dyn_cannot_mod_systrig) << TRG.RDB$TRIGGER_NAME); break; default: break; } } if (runTriggers) executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_TRIGGER, name); MODIFY TRG if (body || external) { fb_assert(!(body && 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; } 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_arith_except) << Arg::Gds(isc_string_truncation)); } TRG.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty(); strcpy(TRG.RDB$ENTRYPOINT, external->name.c_str()); } else if (body) { TRG.RDB$TRIGGER_BLR.NULL = FALSE; attachment->storeBinaryBlob(tdbb, transaction, &TRG.RDB$TRIGGER_BLR, dsqlScratch->getBlrData()); TRG.RDB$DEBUG_INFO.NULL = FALSE; attachment->storeBinaryBlob(tdbb, transaction, &TRG.RDB$DEBUG_INFO, dsqlScratch->getDebugData()); } if (source.hasData()) { TRG.RDB$TRIGGER_SOURCE.NULL = FALSE; attachment->storeMetaDataBlob(tdbb, transaction, &TRG.RDB$TRIGGER_SOURCE, source); } TRG.RDB$VALID_BLR = TRUE; END_MODIFY modified = true; } END_FOR } catch (const status_exception& ex) { rethrowMetaException(ex, ENCODE_ISC_MSG(102, DYN_MSG_FAC), modified); // MODIFY TRIGGER failed } if (modified && runTriggers) executeDdlTrigger(tdbb, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_TRIGGER, name); return modified; } void CreateAlterTriggerNode::compile(thread_db* tdbb, jrd_tra* /*transaction*/) { if (invalid) status_exception::raise(Arg::Gds(isc_dyn_invalid_ddl_trig) << name); if (compiled) return; compiled = true; invalid = true; if (body) { DsqlCompiledStatement* statement = dsqlScratch->getStatement(); 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) resetContextStack(); if (relationName.hasData()) { dsql_nod* relationNode = FB_NEW_RPT(getPool(), e_rln_count) dsql_nod; ///trigger_node->nod_arg[e_trg_table] = relationNode; relationNode->nod_type = nod_relation_name; relationNode->nod_count = e_rln_count; relationNode->nod_arg[e_rln_name] = (dsql_nod*) MAKE_string(relationName.c_str(), relationName.length()); dsql_nod* const temp = relationNode->nod_arg[e_rln_alias]; if (hasOldContext(type.value)) { relationNode->nod_arg[e_rln_alias] = (dsql_nod*) MAKE_cstring(OLD_CONTEXT); dsql_ctx* oldContext = PASS1_make_context(dsqlScratch, relationNode); oldContext->ctx_flags |= CTX_system; } else dsqlScratch->contextNumber++; if (hasNewContext(type.value)) { relationNode->nod_arg[e_rln_alias] = (dsql_nod*) MAKE_cstring(NEW_CONTEXT); dsql_ctx* newContext = PASS1_make_context(dsqlScratch, relationNode); newContext->ctx_flags |= CTX_system; } else dsqlScratch->contextNumber++; relationNode->nod_arg[e_rln_alias] = temp; } // generate the trigger blr if (statement->getFlags() & DsqlCompiledStatement::FLAG_BLR_VERSION4) dsqlScratch->appendUChar(blr_version4); else dsqlScratch->appendUChar(blr_version5); dsqlScratch->appendUChar(blr_begin); dsqlScratch->setPsql(true); putLocalVariables(dsqlScratch, 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; GEN_statement(dsqlScratch, PASS1_statement(dsqlScratch, body)); 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. statement->setType(DsqlCompiledStatement::TYPE_DDL); } invalid = false; } //---------------------- void DropTriggerNode::print(string& text, Array& /*nodes*/) const { text.printf( "DropTriggerNode\n" " name: '%s'\n", name.c_str()); } DdlNode* DropTriggerNode::internalDsqlPass() { dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_TRIGGER); return DdlNode::internalDsqlPass(); } void DropTriggerNode::execute(thread_db* tdbb, 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_no_meta_update) << Arg::Gds(isc_dyn_cant_modify_auto_trig)); break; case fb_sysflag_system: status_exception::raise(Arg::Gds(isc_no_meta_update) << 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, 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_no_meta_update) << 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, transaction, DTW_AFTER, DDL_TRIGGER_DROP_TRIGGER, name); savePoint.release(); // everything is ok } //---------------------- void RecreateTriggerNode::print(string& text, Array& /*nodes*/) const { text.printf("RecreateTriggerNode\n"); } DdlNode* RecreateTriggerNode::internalDsqlPass() { dropNode.dsqlPass(dsqlScratch); createNode->dsqlPass(dsqlScratch); return DdlNode::internalDsqlPass(); } void RecreateTriggerNode::execute(thread_db* tdbb, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); dropNode.executeDdl(tdbb, transaction); createNode->executeDdl(tdbb, transaction); savePoint.release(); // everything is ok } //---------------------- void CreateCollationNode::print(string& text, Array& /*nodes*/) 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, jrd_tra* transaction) { Attachment* attachment = transaction->tra_attachment; // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_COLLATION, name); bool endStore = false; try { 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$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()) { 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::Gds(isc_no_meta_update) << Arg::Gds(ENCODE_ISC_MSG(223, DYN_MSG_FAC)) << 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::Gds(isc_no_meta_update) << Arg::Gds(ENCODE_ISC_MSG(222, DYN_MSG_FAC))); } 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::Gds(isc_no_meta_update) << Arg::Gds(ENCODE_ISC_MSG(222, DYN_MSG_FAC))); } 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::Gds(isc_no_meta_update) << Arg::Gds(ENCODE_ISC_MSG(222, DYN_MSG_FAC))); } // 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_no_meta_update) << Arg::Gds(isc_max_coll_per_charset)); } endStore = true; } END_STORE } catch (const status_exception& ex) { // DEFINE COLLATION failed rethrowMetaException(ex, ENCODE_ISC_MSG(219, DYN_MSG_FAC), endStore); } executeDdlTrigger(tdbb, 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.c_str()); } DdlNode* CreateCollationNode::internalDsqlPass() { 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::internalDsqlPass(); } //---------------------- void DropCollationNode::print(string& text, Array& /*nodes*/) const { text.printf( "DropCollationNode\n" " name: '%s'\n", name.c_str()); } void DropCollationNode::execute(thread_db* tdbb, 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, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_COLLATION, name); if (!COLL.RDB$SYSTEM_FLAG.NULL && COLL.RDB$SYSTEM_FLAG) { status_exception::raise(Arg::Gds(isc_no_meta_update) << 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_no_meta_update) << 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_no_meta_update) << 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_no_meta_update) << 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_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_no_meta_update) << Arg::Gds(isc_dyn_coll_used_domain) << COLL.RDB$COLLATION_NAME << F.RDB$FIELD_NAME); } END_FOR ERASE COLL; } END_FOR if (found) executeDdlTrigger(tdbb, transaction, DTW_AFTER, DDL_TRIGGER_DROP_COLLATION, name); else { status_exception::raise( Arg::Gds(isc_no_meta_update) << 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.c_str()); } //---------------------- void CreateDomainNode::print(string& text, Array& nodes) const { string nameTypeStr; nameType.print(nameTypeStr); text = "CreateDomainNode\n" " " + nameTypeStr + "\n"; } void CreateDomainNode::execute(thread_db* tdbb, jrd_tra* transaction) { Attachment* attachment = transaction->tra_attachment; if (fb_utils::implicit_domain(nameType.name.c_str())) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-637) << Arg::Gds(isc_dsql_implicit_domain_name) << nameType.name); } const dsql_nod* elements = nameType.legacyField->fld_ranges; const USHORT dims = elements ? elements->nod_count / 2 : 0; if (nameType.legacyDefault && dims != 0) { // Default value is not allowed for array type in domain %s status_exception::raise(Arg::Gds(isc_no_meta_update) << Arg::Gds(ENCODE_ISC_MSG(226, DYN_MSG_FAC)) << nameType.name); } if (dims > MAX_ARRAY_DIMENSIONS) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-604) << Arg::Gds(isc_dsql_max_arr_dim_exceeded)); } nameType.resolve(dsqlScratch); dsqlScratch->domainValue.dsc_dtype = nameType.type; dsqlScratch->domainValue.dsc_length = nameType.length; dsqlScratch->domainValue.dsc_scale = nameType.scale; // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_DOMAIN, nameType.name); storeGlobalField(tdbb, transaction, nameType, nameType.name); if (nameType.legacyDefault || check || notNull || dims != 0) { 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.legacyDefault) { dsql_str* defaultString = (dsql_str*) nameType.legacyDefault->nod_arg[e_dft_default_source]; string defaultSource = string(defaultString->str_data, defaultString->str_length); FLD.RDB$DEFAULT_SOURCE.NULL = FALSE; attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$DEFAULT_SOURCE, defaultSource); dsqlScratch->getBlrData().clear(); dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5); dsql_nod* node = PASS1_node(dsqlScratch, nameType.legacyDefault->nod_arg[e_dft_default]); GEN_hidden_variables(dsqlScratch, true); 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) { dsql_str* checkString = (dsql_str*) check->nod_arg[e_cnstr_source]; string checkSource = string(checkString->str_data, checkString->str_length); FLD.RDB$VALIDATION_SOURCE.NULL = FALSE; attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$VALIDATION_SOURCE, checkSource); 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; dsql_nod* node = PASS1_node(dsqlScratch, check->nod_arg[e_cnstr_condition]); GEN_hidden_variables(dsqlScratch, true); 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; } if (dims != 0) { FLD.RDB$DIMENSIONS.NULL = FALSE; FLD.RDB$DIMENSIONS = dims; } END_MODIFY } END_FOR } if (elements) // Is the type an array? { AutoCacheRequest request(tdbb, drq_s_fld_dym, DYN_REQUESTS); SSHORT position = 0; const dsql_nod* const* ptr = elements->nod_arg; for (const dsql_nod* const* const end = ptr + elements->nod_count; ptr < end; ++ptr, ++position) { const dsql_nod* element = *ptr++; const SLONG lrange = element->getSlong(); element = *ptr; const SLONG hrange = element->getSlong(); if (lrange >= hrange) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-604) << Arg::Gds(isc_dsql_arr_range_error)); } bool endStore = false; try { STORE (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) DIM IN RDB$FIELD_DIMENSIONS { strcpy(DIM.RDB$FIELD_NAME, nameType.name.c_str()); DIM.RDB$DIMENSION = position; DIM.RDB$UPPER_BOUND = hrange; DIM.RDB$LOWER_BOUND = lrange; endStore = true; } END_STORE } catch (const status_exception& ex) { // STORE RDB$FIELD_DIMENSIONS failed rethrowMetaException(ex, ENCODE_ISC_MSG(3, DYN_MSG_FAC), endStore); } } } executeDdlTrigger(tdbb, 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; // 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: { // CVC: Because our caller invoked DSC_make_descriptor() on newFld previously, // we should have the added bytes for varchar. For cstring, we are done, since // DSC_make_descriptor(DSC_string_length) != DSC_string_length(DSC_make_descriptor). const USHORT maxflen = DSC_string_length(&origFld.dyn_dsc); // We can have this assertion since this case is for both string fields. const ULONG new_len = DSC_string_length(&newFld.dyn_dsc); fb_assert(new_len - maxflen == (ULONG) newFld.dyn_charbytelen - origFld.dyn_charbytelen); // if (newFld.dyn_dsc.dsc_length < maxflen) if (new_len < maxflen) { // 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 = 87; // 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: { const USHORT maxflen = DSC_string_length(&origFld.dyn_dsc); // CVC: Solve bug #910423, missing DSC_string_length call. // if (newFld.dyn_dsc.dsc_length < maxflen) if (DSC_string_length(&newFld.dyn_dsc) < maxflen) { // 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: { const USHORT maxflen = DSC_string_length(&origFld.dyn_dsc); // CVC: Solve bug #910423, missing DSC_string_length call. // if (newFld.dyn_dsc.dsc_length < maxflen) if (DSC_string_length(&newFld.dyn_dsc) < maxflen) { // 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 = 87; // MODIFY RDB$FIELDS FAILED break; } break; default: fb_assert(FALSE); errorCode = 87; // 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(isc_no_meta_update) << 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(isc_no_meta_update) << 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(isc_no_meta_update) << Arg::Gds(errorCode) << origFld.dyn_fld_name.c_str() << Arg::Num(DSC_string_length(&origFld.dyn_dsc))); 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(isc_no_meta_update) << 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(isc_no_meta_update) << 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::Gds(ENCODE_ISC_MSG(95, DYN_MSG_FAC))); } } // 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; } // 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, Array& nodes) const { text.printf( "AlterDomainNode\n" " %s\n", name.c_str()); } void AlterDomainNode::execute(thread_db* tdbb, jrd_tra* transaction) { Attachment* attachment = transaction->tra_attachment; // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); bool found = false; bool endModify = false; try { AutoCacheRequest request(tdbb, drq_m_fld2, DYN_REQUESTS); FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) FLD IN RDB$FIELDS WITH FLD.RDB$FIELD_NAME EQ name.c_str() { found = true; executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_DOMAIN, 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::Gds(isc_no_meta_update) << Arg::Gds(ENCODE_ISC_MSG(160, DYN_MSG_FAC))); } dsql_fld localField(dsqlScratch->getStatement()->getPool()); // Get the attributes of the domain, and set any occurances of // keyword VALUE to the correct type, length, scale, etc. if (!METD_get_domain(dsqlScratch->getTransaction(), &localField, name.c_str())) { // 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.fld_dtype; dsqlScratch->domainValue.dsc_length = localField.fld_length; dsqlScratch->domainValue.dsc_scale = localField.fld_scale; dsql_str* checkString = (dsql_str*) setConstraint->nod_arg[e_cnstr_source]; string checkSource = string(checkString->str_data, checkString->str_length); FLD.RDB$VALIDATION_SOURCE.NULL = FALSE; attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$VALIDATION_SOURCE, checkSource); 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; dsql_nod* node = PASS1_node(dsqlScratch, setConstraint->nod_arg[e_cnstr_condition]); GEN_hidden_variables(dsqlScratch, true); 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::Gds(isc_no_meta_update) << Arg::Gds(ENCODE_ISC_MSG(226, DYN_MSG_FAC)) << name); } dsql_str* defaultString = (dsql_str*) setDefault->nod_arg[e_dft_default_source]; string defaultSource = string(defaultString->str_data, defaultString->str_length); FLD.RDB$DEFAULT_SOURCE.NULL = FALSE; attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$DEFAULT_SOURCE, defaultSource); dsqlScratch->getBlrData().clear(); dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5); dsql_nod* node = PASS1_node(dsqlScratch, setDefault->nod_arg[e_dft_default]); GEN_hidden_variables(dsqlScratch, true); 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 (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 bool has_dimensions = false; if (FLD.RDB$DIMENSIONS != 0) origDom.dyn_dtype = blr_blob; USHORT typeLength = type->length; switch (type->type) { 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->type], type->scale, typeLength, type->subType, type->charSetId, type->collationId); newDom.dyn_fld_name = name; newDom.dyn_charbytelen = typeLength; newDom.dyn_dtype = blr_dtypes[type->type]; 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::Gds(isc_no_meta_update) << Arg::Gds(ENCODE_ISC_MSG(276, DYN_MSG_FAC)) << 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 endModify = true; } END_FOR } catch (const status_exception& ex) { // msg 87: "MODIFY RDB$FIELDS failed" rethrowMetaException(ex, ENCODE_ISC_MSG(87, DYN_MSG_FAC), endModify); } if (!found) { // msg 89: "Global field not found" status_exception::raise( Arg::Gds(isc_no_meta_update) << Arg::Gds(ENCODE_ISC_MSG(89, DYN_MSG_FAC))); } executeDdlTrigger(tdbb, 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::Gds(isc_no_meta_update) << Arg::Gds(ENCODE_ISC_MSG(204, DYN_MSG_FAC)) << 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; try { 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 } catch (const status_exception& ex) { // msg 35: "ERASE RDB$FIELDS failed" rethrowMetaException(ex, ENCODE_ISC_MSG(35, DYN_MSG_FAC), true); } return found; } void DropDomainNode::print(string& text, Array& /*nodes*/) const { text.printf( "DropDomainNode\n" " name: '%s'\n", name.c_str()); } void DropDomainNode::execute(thread_db* tdbb, jrd_tra* transaction) { // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); bool found = false; try { AutoCacheRequest request(tdbb, drq_e_gfields, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$FIELDS WITH X.RDB$FIELD_NAME EQ name.c_str() { executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_DOMAIN, name); check(tdbb, transaction); deleteDimensionRecords(tdbb, transaction, name); ERASE X; found = true; } END_FOR } catch (const status_exception& ex) { // msg 44: "ERASE RDB$FIELDS failed" rethrowMetaException(ex, ENCODE_ISC_MSG(44, DYN_MSG_FAC), found); } if (found) executeDdlTrigger(tdbb, transaction, DTW_AFTER, DDL_TRIGGER_DROP_DOMAIN, name); else { // msg 89: "Domain not found" status_exception::raise( Arg::Gds(isc_no_meta_update) << Arg::Gds(ENCODE_ISC_MSG(89, DYN_MSG_FAC))); } 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::Gds(isc_no_meta_update) << Arg::Gds(ENCODE_ISC_MSG(43, DYN_MSG_FAC)) << 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::Gds(isc_no_meta_update) << Arg::Gds(ENCODE_ISC_MSG(239, DYN_MSG_FAC)) << 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 //// FIXME: Check domain usage in functions. } //---------------------- void CreateSequenceNode::print(string& text, Array& /*nodes*/) const { text.printf( "CreateSequenceNode\n" " name: %s\n", name.c_str()); } void CreateSequenceNode::execute(thread_db* tdbb, jrd_tra* transaction) { executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_SEQUENCE, name); store(tdbb, transaction, name, fb_sysflag_user); executeDdlTrigger(tdbb, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_SEQUENCE, name); } void CreateSequenceNode::store(thread_db* tdbb, jrd_tra* transaction, const MetaName& name, fb_sysflag sysFlag) { bool endStore = false; try { DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_generator); AutoCacheRequest request(tdbb, drq_s_gens, DYN_REQUESTS); int faults = 0; while (true) { try { SINT64 id = DYN_UTIL_gen_unique_id(tdbb, drq_g_nxt_gen_id, "RDB$GENERATORS"); 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()); endStore = true; } END_STORE break; } catch (const status_exception& ex) { if (ex.value()[1] != isc_no_dup) throw; if (++faults > MAX_SSHORT) throw; endStore = false; fb_utils::init_status(tdbb->tdbb_status_vector); } } } catch (const status_exception& ex) { rethrowMetaException(ex, ENCODE_ISC_MSG(8, DYN_MSG_FAC), endStore); // DEFINE GENERATOR failed } } } // namespace Jrd