/* * PROGRAM: Dynamic SQL runtime support * MODULE: ddl.cpp * DESCRIPTION: Utilities for generating ddl * * 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): ______________________________________. * * 2001.5.20 Claudio Valderrama: Stop null pointer that leads to a crash, * caused by incomplete yacc syntax that allows ALTER DOMAIN dom SET; * * 2001.07.06 Sean Leyne - Code Cleanup, removed "#ifdef READONLY_DATABASE" * conditionals, as the engine now fully supports * readonly databases. * December 2001 Mike Nordell - Attempt to make it C++ * * 2001.5.20 Claudio Valderrama: Stop null pointer that leads to a crash, * caused by incomplete yacc syntax that allows ALTER DOMAIN dom SET; * 2001.5.29 Claudio Valderrama: Check for view v/s relation in DROP * command will stop a user that uses DROP VIEW and drops a table by * accident and vice-versa. * 2001.5.30 Claudio Valderrama: alter column should use 1..N for the * position argument since the call comes from SQL DDL. * 2001.6.27 Claudio Valderrama: DDL_resolve_intl_type() was adding 2 to the * length of varchars instead of just checking that len+2<=MAX_COLUMN_SIZE. * It required a minor change to put_field() where it was decremented, too. * 2001.6.27 Claudio Valderrama: Finally stop users from invoking the same option * several times when altering a domain. Specially dangerous with text data types. * Ex: alter domain d type char(5) type varchar(5) default 'x' default 'y'; * Bear in mind that if DYN functions are addressed directly, this protection * becomes a moot point. * 2001.6.30 Claudio Valderrama: revert changes from 2001.6.26 because the code * is called from several places and there are more functions, even in metd.c, * playing the same nonsense game with the field's length, so it needs more * careful examination. For now, the new checks in DYN_MOD should catch most anomalies. * 2001.7.3 Claudio Valderrama: fix Firebird Bug #223059 with mismatch between number * of declared fields for a VIEW and effective fields in the SELECT statement. * 2001.07.22 Claudio Valderrama: minor fixes and improvements. * 2001.08.18 Claudio Valderrama: RECREATE PROCEDURE. * 2001.10.01 Claudio Valderrama: modify_privilege() should recognize that a ROLE can * now be made an explicit grantee. * 2001.10.08 Claudio Valderrama: implement fb_sysflag enum values for autogenerated * non-system triggers so DFW can recognize them easily. * 2001.10.26 Claudio Valderrama: added a call to the new METD_drop_function() * in DDL_execute() so the metadata cache for udfs can be refreshed. * 2001.12.06 Claudio Valderrama: DDL_resolve_intl_type should calculate field length * 2002.08.04 Claudio Valderrama: allow declaring and defining variables at the same time * 2002.08.04 Dmitry Yemanov: ALTER VIEW * 2002.08.31 Dmitry Yemanov: allowed user-defined index names for PK/FK/UK constraints * 2002.09.01 Dmitry Yemanov: RECREATE VIEW * 2002.09.12 Nickolay Samofatov: fixed cached metadata errors * 2004.01.16 Vlad Horsun: added support for default parameters and * EXECUTE BLOCK statement * Adriano dos Santos Fernandes */ #include "firebird.h" #include #include #include "../jrd/SysFunction.h" #include "../common/classes/MetaName.h" #include "../dsql/dsql.h" #include "../dsql/node.h" #include "../jrd/ibase.h" #include "../jrd/intl.h" #include "../jrd/intl_classes.h" #include "../jrd/jrd.h" #include "../jrd/flags.h" #include "../jrd/constants.h" #include "../dsql/errd_proto.h" #include "../dsql/ddl_proto.h" #include "../dsql/gen_proto.h" #include "../dsql/make_proto.h" #include "../dsql/metd_proto.h" #include "../dsql/pass1_proto.h" #include "../dsql/utld_proto.h" #include "../jrd/intl_proto.h" #include "../jrd/met_proto.h" #include "../jrd/thread_proto.h" #include "../jrd/gds_proto.h" #include "../jrd/jrd_proto.h" #include "../jrd/why_proto.h" #include "../common/utils_proto.h" #include "../dsql/DdlNodes.h" #include "../common/StatusArg.h" #ifdef DSQL_DEBUG #include "../gpre/prett_proto.h" #endif using namespace Jrd; using namespace Dsql; using namespace Firebird; const int BLOB_BUFFER_SIZE = 4096; // to read in blr blob for default values static void assign_field_length(dsql_fld*, USHORT); static bool is_array_or_blob(CompiledStatement*, const dsql_nod*); static void check_constraint(CompiledStatement*, dsql_nod*, bool); static void check_one_call(USHORT*, SSHORT, const TEXT*); static void create_view_triggers(CompiledStatement*, dsql_nod*, dsql_nod*); static void define_computed(CompiledStatement*, dsql_nod*, dsql_fld*, dsql_nod*); static void define_constraint_trigger(CompiledStatement*, dsql_nod*); static void define_database(CompiledStatement*); static bool define_default(CompiledStatement*, const dsql_nod*); static void define_del_cascade_trg(CompiledStatement*, const dsql_nod*, const dsql_nod*, const dsql_nod*, const char*, const char*); //static void define_del_default_trg(CompiledStatement*, dsql_nod*, dsql_nod*, dsql_nod*, TEXT*, TEXT*); static void define_dimensions(CompiledStatement*, const dsql_fld*); static void define_domain(CompiledStatement*); static void define_exception(CompiledStatement*, NOD_TYPE); static void define_field(CompiledStatement*, dsql_nod*, SSHORT, const dsql_str*, const dsql_nod* pkcols); static void define_filter(CompiledStatement*); static SSHORT getBlobFilterSubType(CompiledStatement* statement, const dsql_nod* node); static void define_collation(CompiledStatement*); static void define_generator(CompiledStatement*); static void define_role(CompiledStatement*); static void define_index(CompiledStatement*); #ifdef NOT_USED_OR_REPLACED static dsql_nod* define_insert_action(CompiledStatement*); #endif static void define_procedure(CompiledStatement*, NOD_TYPE); static void define_rel_constraint(CompiledStatement*, dsql_nod*); static void define_relation(CompiledStatement*); static void define_set_null_trg(CompiledStatement*, const dsql_nod*, const dsql_nod*, const dsql_nod*, const char*, const char*, bool); static void define_set_default_trg(CompiledStatement*, const dsql_nod*, const dsql_nod*, const dsql_nod*, const char*, const char*, bool); static void define_shadow(CompiledStatement*); static void define_trigger(CompiledStatement*, NOD_TYPE); static void define_udf(CompiledStatement*); static void define_update_action(CompiledStatement*, dsql_nod**, dsql_nod**, dsql_nod*); static void define_upd_cascade_trg(CompiledStatement*, const dsql_nod*, const dsql_nod*, const dsql_nod*, const char*, const char*); static void define_view(CompiledStatement*, NOD_TYPE); static void define_view_trigger(CompiledStatement*, dsql_nod*, dsql_nod*, dsql_nod*); static void delete_collation(CompiledStatement*); static void delete_exception(CompiledStatement*, dsql_nod*, bool); static void delete_procedure(CompiledStatement*, dsql_nod*, bool); static void delete_relation_view(CompiledStatement*, dsql_nod*, bool); static void delete_trigger(CompiledStatement*, dsql_nod*, bool); static const dsql_nod* find_pk_columns(const dsql_nod* def_rel_elements); static ULONG find_start_of_body(const dsql_str* string); static void fix_default_source(dsql_str* string); static void foreign_key(CompiledStatement*, dsql_nod*, const char* index_name); static void generate_dyn(CompiledStatement*, dsql_nod*); static void grant_revoke(CompiledStatement*); static void make_comment(CompiledStatement*); static void make_index(CompiledStatement*, const dsql_nod*, const dsql_nod*, const dsql_nod*, const char*, const char*); static void make_index_trg_ref_int(CompiledStatement*, dsql_nod*, dsql_nod*, dsql_nod*, const char*, const char*); static void modify_database(CompiledStatement*); static void modify_domain(CompiledStatement*); static void modify_field(CompiledStatement*, dsql_nod*, SSHORT, const dsql_str*); static void modify_index(CompiledStatement*); static void modify_privilege(CompiledStatement* statement, NOD_TYPE type, SSHORT option, const UCHAR* privs, const dsql_nod* table, const dsql_nod* user, const dsql_nod* grantor, const dsql_str* field_name); static char modify_privileges(CompiledStatement*, NOD_TYPE, SSHORT, const dsql_nod*, const dsql_nod*, const dsql_nod*, const dsql_nod*); static void modify_relation(CompiledStatement*); static void modify_udf(CompiledStatement*); static void modify_map(CompiledStatement*); static dsql_par* parameter_reverse_order(dsql_par* parameter, dsql_par* prev); static void process_role_nm_list(CompiledStatement*, SSHORT, const dsql_nod*, const dsql_nod*, NOD_TYPE, const dsql_nod*); static void put_descriptor(CompiledStatement*, const dsc*); static void put_dtype(CompiledStatement*, const dsql_fld*, bool); static void put_field(CompiledStatement*, dsql_fld*, bool); static void put_local_variable(CompiledStatement*, dsql_var*, dsql_nod*, const dsql_str*); static void put_local_variables(CompiledStatement*, dsql_nod*, SSHORT); static void put_msg_field(CompiledStatement*, const dsql_fld*); static dsql_nod* replace_field_names(dsql_nod*, dsql_nod*, dsql_nod*, bool, const char*); static void reset_context_stack(CompiledStatement*); static void save_field(CompiledStatement*, const SCHAR*); static void save_relation(CompiledStatement*, const dsql_str*); static void set_statistics(CompiledStatement*); static void stuff_default_blr(CompiledStatement*, const UCHAR*, USHORT); static void stuff_matching_blr(CompiledStatement*, const dsql_nod*, const dsql_nod*); static void stuff_trg_firing_cond(CompiledStatement*, const dsql_nod*); static void set_nod_value_attributes(dsql_nod*, const dsql_fld*); static void clearPermanentField (dsql_rel*, bool); static void define_user(CompiledStatement*, UCHAR); static void put_grantor(CompiledStatement* statement, const dsql_nod* grantor); static void post_607(const Arg::StatusVector& v); static void put_user_grant(CompiledStatement* statement, const dsql_nod* user); enum trigger_type { PRE_STORE_TRIGGER = 1, POST_STORE_TRIGGER = 2, PRE_MODIFY_TRIGGER = 3, POST_MODIFY_TRIGGER = 4, PRE_ERASE_TRIGGER = 5, POST_ERASE_TRIGGER = 6 }; const int DEFAULT_BUFFER = 2048; const int DEFAULT_BLOB_SEGMENT_SIZE = 80; // bytes static const USHORT blr_dtypes[] = { 0, blr_text, // dtype_text blr_cstring, // dtype_cstring blr_varying, // dtype_varying 0, 0, 0, // dtype_packed 0, // dtype_byte blr_short, // dtype_short blr_long, // dtype_long blr_quad, // dtype_quad blr_float, // dtype_real blr_double, // dtype_double blr_double, // dtype_d_float blr_sql_date, // dtype_sql_date blr_sql_time, // dtype_sql_time blr_timestamp, // dtype_timestamp blr_blob, // dtype_blob // ASF: CAST use blr_blob2 because blr_blob don't fit in UCHAR blr_short, // dtype_array blr_int64 // dtype_int64 }; static const UCHAR nonnull_validation_blr[] = { blr_version5, blr_not, blr_missing, blr_fid, 0, 0, 0, blr_eoc }; static inline bool hasOldContext(const int value) { const int val1 = ((value + 1) >> 1) & 3; const int val2 = ((value + 1) >> 3) & 3; const int val3 = ((value + 1) >> 5) & 3; return (val1 && val1 != 1) || (val2 && val2 != 1) || (val3 && val3 != 1); } static inline bool hasNewContext(const int value) { const int val1 = ((value + 1) >> 1) & 3; const int val2 = ((value + 1) >> 3) & 3; const int val3 = ((value + 1) >> 5) & 3; return (val1 && val1 != 3) || (val2 && val2 != 3) || (val3 && val3 != 3); } void CompiledStatement::append_raw_string(const char* string, USHORT len) { req_blr_data.add(reinterpret_cast(string), len); } void CompiledStatement::append_raw_string(const UCHAR* string, USHORT len) { req_blr_data.add(string, len); } // // Write out a string valued attribute. (Overload 2.) // void CompiledStatement::append_string(UCHAR verb, const MetaName& name) { append_string(verb, name.c_str(), name.length()); } // // Write out a string valued attribute. (Overload 3.) // void CompiledStatement::append_string(UCHAR verb, const string& name) { append_string(verb, name.c_str(), name.length()); } void DDL_execute(dsql_req* request) { /************************************** * * D D L _ e x e c u t e * ************************************** * * Functional description * Call access method layered service DYN * to interpret dyn string and perform * metadata updates. * **************************************/ thread_db* tdbb = JRD_get_thread_data(); #ifdef DSQL_DEBUG if (DSQL_debug & 4) { dsql_trace("Output DYN string for DDL:"); PRETTY_print_dyn(request->req_blr_data.begin(), gds__trace_printer, NULL, 0); } #endif // for delete & modify, get rid of the cached relation metadata const dsql_str* string = NULL; SYM_TYPE sym_type; const dsql_nod* relation_node; const NOD_TYPE type = request->req_ddl_node->nod_type; switch (type) { case nod_mod_relation: case nod_redef_relation: relation_node = request->req_ddl_node->nod_arg[e_alt_name]; string = (dsql_str*) relation_node->nod_arg[e_rln_name]; // fall into case nod_mod_view: case nod_replace_view: case nod_redef_view: case nod_del_relation: case nod_del_view: if (!string) string = (dsql_str*) request->req_ddl_node->nod_arg[e_alt_name]; sym_type = SYM_relation; METD_drop_relation(request, string); break; case nod_mod_procedure: case nod_del_procedure: case nod_replace_procedure: case nod_redef_procedure: // for delete & modify, get rid of the cached procedure metadata string = (dsql_str*) request->req_ddl_node->nod_arg[e_prc_name]; sym_type = SYM_procedure; METD_drop_procedure(request, string); break; case nod_del_collation: // for delete, get rid of the cached collation metadata string = (dsql_str*) request->req_ddl_node->nod_arg[e_del_coll_name]; sym_type = SYM_intlsym_collation; METD_drop_collation(request, string); break; case nod_del_udf: case nod_mod_udf: // Signal UDF for obsolescence string = (dsql_str*) request->req_ddl_node->nod_arg[e_udf_name]; sym_type = SYM_udf; METD_drop_function (request, string); break; } if (string) MET_dsql_cache_release(tdbb, sym_type, string->str_data); if (type == nod_class_node) { reinterpret_cast(request->req_ddl_node->nod_arg[0])->execute(tdbb, request->req_transaction); JRD_autocommit_ddl(tdbb, request->req_transaction); } else { JRD_ddl(tdbb, request->req_dbb->dbb_attachment, request->req_transaction, request->req_blr_data.getCount(), request->req_blr_data.begin()); } } void DDL_generate(CompiledStatement* statement, dsql_nod* node) { /************************************** * * D D L _ g e n e r a t e * ************************************** * * Functional description * Generate the DYN string for a * metadata update. Done during the * prepare phase. * **************************************/ if (statement->req_dbb->dbb_read_only) { ERRD_post(Arg::Gds(isc_read_only_database)); return; } statement->append_uchar(isc_dyn_version_1); generate_dyn(statement, node); statement->append_uchar(isc_dyn_eoc); } // // Determine whether ids or names should be referenced // when generating blr for fields and relations. // bool DDL_ids(const dsql_req* request) { return !request->req_ddl_node; /* const dsql_nod* ddl_node = request->req_ddl_node; if (!ddl_node) { return true; } switch (ddl_node->nod_type) { case nod_def_constraint: case nod_def_computed: case nod_def_view: case nod_redef_view: case nod_mod_view: case nod_replace_view: case nod_def_trigger: case nod_redef_trigger: case nod_mod_trigger: case nod_replace_trigger: case nod_def_procedure: case nod_redef_procedure: case nod_mod_procedure: case nod_replace_procedure: return false; default: return true; } */ } // // Emit blr that describes a descriptor. // Note that this depends on the same stuff variant // as used in gen.cpp // void DDL_put_field_dtype(CompiledStatement* statement, const dsql_fld* field, bool use_subtype) { put_dtype(statement, field, use_subtype); } // // See the next function for description. This is only a // wrapper that sets the last parameter to false to indicate // we are creating a field, not modifying one. // void DDL_resolve_intl_type(CompiledStatement* statement, dsql_fld* field, const dsql_str* collation_name) { DDL_resolve_intl_type2 (statement, field, collation_name, false); } void DDL_resolve_intl_type2(CompiledStatement* statement, dsql_fld* field, const dsql_str* collation_name, bool modifying) { /************************************** * * D D L _ r e s o l v e _ i n t l _ t y p e 2 * ************************************** * * Function * If the field is defined with a character set or collation, * resolve the names to a subtype now. * * Also resolve the field length & whatnot. * * If the field is being created, it will pick the db-wide charset * and collation if not specified. If the field is being modified, * since we don't allow changes to those attributes, we'll go and * calculate the correct old lenth from the field itself so DYN * can validate the change properly. * * For International text fields, this is a good time to calculate * their actual size - when declared they were declared in * lengths of CHARACTERs, not BYTES. * **************************************/ if (field->fld_type_of_name.hasData()) { if (ENCODE_ODS(statement->req_dbb->dbb_ods_version, statement->req_dbb->dbb_minor_version) < ODS_11_1) { // Feature not supported on ODS version older than %d.%d ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-804) << Arg::Gds(isc_dsql_feature_not_supported_ods) << Arg::Num(11) << Arg::Num(1)); } if (field->fld_type_of_table) { dsql_rel* relation = METD_get_relation(statement, field->fld_type_of_table); const dsql_fld* fld = NULL; if (relation) { const MetaName fieldName(field->fld_type_of_name); for (fld = relation->rel_fields; fld; fld = fld->fld_next) { if (fieldName == fld->fld_name) { field->fld_source = fld->fld_source; field->fld_length = fld->fld_length; field->fld_scale = fld->fld_scale; field->fld_sub_type = fld->fld_sub_type; field->fld_character_set_id = fld->fld_character_set_id; field->fld_collation_id = fld->fld_collation_id; field->fld_character_length = fld->fld_character_length; field->fld_flags = fld->fld_flags; field->fld_dtype = fld->fld_dtype; field->fld_seg_length = fld->fld_seg_length; break; } } } if (!fld) { // column @1 does not exist in table/view @2 post_607(Arg::Gds(isc_dyn_column_does_not_exist) << Arg::Str(field->fld_type_of_name) << Arg::Str(field->fld_type_of_table->str_data)); } } else { if (!METD_get_domain(statement, field, field->fld_type_of_name.c_str())) { // Specified domain or source field does not exist post_607(Arg::Gds(isc_dsql_domain_not_found) << Arg::Str(field->fld_type_of_name)); } } } if ((field->fld_dtype > dtype_any_text) && field->fld_dtype != dtype_blob) { if (field->fld_character_set || collation_name || field->fld_flags & FLD_national) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_dsql_datatype_err) << Arg::Gds(isc_collation_requires_text)); } return; } if (field->fld_dtype == dtype_blob) { if (field->fld_sub_type_name) { SSHORT blob_sub_type; if (!METD_get_type(statement, reinterpret_cast(field->fld_sub_type_name), "RDB$FIELD_SUB_TYPE", &blob_sub_type)) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_dsql_datatype_err) << Arg::Gds(isc_dsql_blob_type_unknown) << Arg::Str(((dsql_str*) field->fld_sub_type_name)->str_data)); } field->fld_sub_type = blob_sub_type; } if (field->fld_sub_type > isc_blob_text) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_dsql_datatype_err) << Arg::Gds(isc_subtype_for_internal_use)); } if (field->fld_character_set && (field->fld_sub_type == isc_blob_untyped)) { field->fld_sub_type = isc_blob_text; } if (field->fld_character_set && (field->fld_sub_type != isc_blob_text)) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_dsql_datatype_err) << Arg::Gds(isc_collation_requires_text)); } if (collation_name && (field->fld_sub_type != isc_blob_text)) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_dsql_datatype_err) << Arg::Gds(isc_collation_requires_text)); } if (field->fld_sub_type != isc_blob_text) { return; } } if (field->fld_character_set_id != 0 && !collation_name) { // This field has already been resolved once, and the collation // hasn't changed. Therefore, no need to do it again. return; } if (modifying) { const dsql_rel* relation = statement->req_relation; const dsql_fld* afield = field->fld_next; USHORT bpc = 0; while (afield) { // The first test is redundant. if (afield != field && afield->fld_relation && afield->fld_name == field->fld_name) { fb_assert(afield->fld_relation == relation || !relation); break; } afield = afield->fld_next; } if (afield) { field->fld_character_set_id = afield->fld_character_set_id; bpc = METD_get_charset_bpc (statement, field->fld_character_set_id); field->fld_collation_id = afield->fld_collation_id; field->fld_ttype = afield->fld_ttype; if (afield->fld_flags & FLD_national) { field->fld_flags |= FLD_national; } else { field->fld_flags &= ~FLD_national; } assign_field_length (field, bpc); return; } } if (!(field->fld_character_set || field->fld_character_set_id || // set if a domain (field->fld_flags & FLD_national))) { // Attach the database default character set, if not otherwise specified const dsql_str* dfl_charset = METD_get_default_charset(statement); if (dfl_charset) { field->fld_character_set = (dsql_nod*) dfl_charset; } else { /* If field is not specified with NATIONAL, or CHARACTER SET * treat it as a single-byte-per-character field of character set NONE. */ assign_field_length (field, 1); field->fld_ttype = 0; if (!collation_name) { return; } } } const char* charset_name = 0; if (field->fld_flags & FLD_national) { charset_name = NATIONAL_CHARACTER_SET; } else if (field->fld_character_set) { charset_name = ((dsql_str*) field->fld_character_set)->str_data; } // Find an intlsym for any specified character set name & collation name const dsql_intlsym* resolved_type = NULL; if (charset_name) { const dsql_intlsym* resolved_charset = METD_get_charset(statement, (USHORT) strlen(charset_name), charset_name); // Error code -204 (IBM's DB2 manual) is close enough if (!resolved_charset) { // specified character set not found ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_dsql_datatype_err) << Arg::Gds(isc_charset_not_found) << Arg::Str(charset_name)); } field->fld_character_set_id = resolved_charset->intlsym_charset_id; resolved_type = resolved_charset; } if (collation_name) { const dsql_intlsym* resolved_collation = METD_get_collation(statement, collation_name, field->fld_character_set_id); if (!resolved_collation) { MetaName charSetName; if (charset_name) charSetName = charset_name; else charSetName = METD_get_charset_name(statement, field->fld_character_set_id); // Specified collation not found ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_dsql_datatype_err) << Arg::Gds(isc_collation_not_found) << Arg::Str(collation_name->str_data) << Arg::Str(charSetName)); } // If both specified, must be for same character set // A "literal constant" must be handled (charset as ttype_dynamic) resolved_type = resolved_collation; if ((field->fld_character_set_id != resolved_type->intlsym_charset_id) && (field->fld_character_set_id != ttype_dynamic)) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_dsql_datatype_err) << Arg::Gds(isc_collation_not_for_charset) << Arg::Str(collation_name->str_data)); } field->fld_explicit_collation = true; } assign_field_length (field, resolved_type->intlsym_bytes_per_char); field->fld_ttype = resolved_type->intlsym_ttype; field->fld_character_set_id = resolved_type->intlsym_charset_id; field->fld_collation_id = resolved_type->intlsym_collate_id; } static void assign_field_length(dsql_fld* field, USHORT bytes_per_char) { /************************************** * * a s s i g n _ f i e l d _ l e n g t h * ************************************** * * Function * We'll see if the field's length fits in the maximum * allowed field, including charset and space for varchars. * Either we raise an error or assign the field's length. * If the charlen comes as zero, we do nothing, although we * know that DYN, MET and DFW will blindly set field length * to zero if they don't catch charlen or another condition. * **************************************/ if (field->fld_character_length) { ULONG field_length = (ULONG) bytes_per_char * field->fld_character_length; if (field->fld_dtype == dtype_varying) { field_length += sizeof(USHORT); } if (field_length > MAX_COLUMN_SIZE) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_dsql_datatype_err) << Arg::Gds(isc_imp_exc) << Arg::Gds(isc_field_name) << Arg::Str(field->fld_name)); } field->fld_length = (USHORT) field_length; } } static bool is_array_or_blob(CompiledStatement* statement, const dsql_nod* node) { /************************************** * * i s _ a r r a y _ o r _ b l o b * ************************************** * * Functional description * Return true if there is an array or blob in expression, else false. * Array and blob expressions have limited usefullness in a computed * expression - so we detect it here to report a syntax error at * definition time, rather than a runtime error at execution. * **************************************/ switch (node->nod_type) { case nod_agg_count: //case nod_count: case nod_gen_id: case nod_gen_id2: case nod_dbkey: case nod_current_date: case nod_current_time: case nod_current_timestamp: case nod_constant: case nod_strlen: case nod_null: case nod_substr: case nod_internal_info: return false; case nod_via: return is_array_or_blob(statement, node->nod_arg[e_via_value_1]); case nod_map: { const dsql_map* map = (dsql_map*) node->nod_arg[e_map_map]; return is_array_or_blob(statement, map->map_node); } case nod_agg_max: case nod_agg_min: case nod_agg_average: case nod_agg_total: case nod_agg_average2: case nod_agg_total2: case nod_agg_list: case nod_upcase: case nod_lowcase: case nod_negate: return is_array_or_blob(statement, node->nod_arg[0]); case nod_cast: { const dsql_fld* fld = (dsql_fld*) node->nod_arg[e_cast_target]; if (fld->fld_dtype == dtype_blob || fld->fld_dtype == dtype_array) return true; } return is_array_or_blob(statement, node->nod_arg[e_cast_source]); case nod_add: case nod_subtract: case nod_concatenate: case nod_multiply: case nod_divide: case nod_add2: case nod_subtract2: case nod_multiply2: case nod_divide2: if (is_array_or_blob(statement, node->nod_arg[0])) { return true; } return is_array_or_blob(statement, node->nod_arg[1]); case nod_alias: return is_array_or_blob(statement, node->nod_arg[e_alias_value]); case nod_udf: { const dsql_udf* userFunc = (dsql_udf*) node->nod_arg[0]; if (userFunc->udf_dtype == dtype_blob || userFunc->udf_dtype == dtype_array) return true; } // parameters to UDF don't need checking, a blob or array can be passed return false; case nod_sys_function: { const dsql_str* name = (dsql_str*) node->nod_arg[e_sysfunc_name]; dsql_nod* nodeArgs = node->nod_arg[e_sysfunc_args]; Array args; if (nodeArgs) { fb_assert(nodeArgs->nod_type == nod_list); for (dsql_nod** p = nodeArgs->nod_arg; p < nodeArgs->nod_arg + nodeArgs->nod_count; ++p) { MAKE_desc(statement, &(*p)->nod_desc, *p, NULL); args.add(&(*p)->nod_desc); } } dsc desc; DSqlDataTypeUtil(statement).makeSysFunction(&desc, name->str_data, args.getCount(), args.begin()); return DTYPE_IS_BLOB_OR_QUAD(desc.dsc_dtype); } case nod_extract: case nod_list: { const dsql_nod* const* const end = node->nod_arg + node->nod_count; for (const dsql_nod* const* ptr = node->nod_arg; ptr < end; ++ptr) { if (is_array_or_blob(statement, *ptr)) { return true; } } } return false; case nod_field: case nod_coalesce: case nod_simple_case: case nod_searched_case: if (node->nod_desc.dsc_dtype == dtype_blob || node->nod_desc.dsc_dtype == dtype_array) { return true; } return false; case nod_trim: return is_array_or_blob(statement, node->nod_arg[e_trim_value]); case nod_derived_field: return is_array_or_blob(statement, node->nod_arg[e_derived_field_value]); default: fb_assert(false); return false; } } static void check_constraint(CompiledStatement* statement, dsql_nod* element, bool delete_trigger_required) { /************************************** * * c h e c k _ c o n s t r a i n t * ************************************** * * Function * Generate triggers to implement the CHECK * clause, either at the field or table level. * **************************************/ dsql_nod* ddl_node = statement->req_ddl_node; if (!element->nod_arg[e_cnstr_table]) { element->nod_arg[e_cnstr_table] = ddl_node->nod_arg[e_drl_name]; } // specify that the trigger should abort if the condition is not met dsql_nod* list_node = MAKE_node(nod_list, 1); element->nod_arg[e_cnstr_actions] = list_node; list_node->nod_arg[0] = MAKE_node(nod_gdscode, 1); dsql_nod** errorcode_node = &list_node->nod_arg[0]->nod_arg[0]; *errorcode_node = (dsql_nod*) MAKE_cstring("check_constraint"); // create the INSERT trigger element->nod_arg[e_cnstr_type] = MAKE_const_slong(PRE_STORE_TRIGGER); define_constraint_trigger(statement, element); // create the UPDATE trigger element->nod_arg[e_cnstr_type] = MAKE_const_slong(PRE_MODIFY_TRIGGER); define_constraint_trigger(statement, element); // create the DELETE trigger, if required if (delete_trigger_required) { element->nod_arg[e_cnstr_type] = MAKE_const_slong(PRE_ERASE_TRIGGER); define_constraint_trigger(statement, element); } statement->append_uchar(isc_dyn_end); // For CHECK constraint definition } static void check_one_call (USHORT* repetition_count, SSHORT pos, const TEXT* error_msg) { /************************************** * * c h e c k _ o n e _ c a l l * ************************************** * * Function * Ensure that each option in modify_domain() is called only once. * This restriction cannot be enforced by the DSQL parser. * **************************************/ if (++repetition_count[pos] > 1) { ERRD_post (Arg::Gds(isc_sqlerr) << Arg::Num(-637) << Arg::Gds(isc_dsql_duplicate_spec) << Arg::Str(error_msg)); } } static void create_view_triggers(CompiledStatement* statement, dsql_nod* element, dsql_nod* items) { // Fields in the VIEW actually /************************************** * * c r e a t e _ v i e w _ t r i g g e r s * ************************************** * * Function * Generate triggers to implement the WITH CHECK OPTION * clause for a VIEW * **************************************/ dsql_nod* ddl_node = statement->req_ddl_node; if (!element->nod_arg[e_cnstr_table]) { element->nod_arg[e_cnstr_table] = ddl_node->nod_arg[e_drl_name]; } // specify that the trigger should abort if the condition is not met dsql_nod* list_node = MAKE_node(nod_list, 1); element->nod_arg[e_cnstr_actions] = list_node; list_node->nod_arg[0] = MAKE_node(nod_gdscode, 1); dsql_nod** errorcode_node = &list_node->nod_arg[0]->nod_arg[0]; *errorcode_node = (dsql_nod*) MAKE_cstring("check_constraint"); // create the UPDATE trigger element->nod_arg[e_cnstr_type] = MAKE_const_slong(PRE_MODIFY_TRIGGER); dsql_nod* base_and_node = 0; dsql_nod* base_relation = 0; define_update_action(statement, &base_and_node, &base_relation, items); fb_assert(base_and_node); fb_assert(base_relation); dsql_nod* rse = MAKE_node(nod_rse, e_rse_count); rse->nod_arg[e_rse_boolean] = base_and_node; dsql_nod* temp = MAKE_node(nod_list, 1); rse->nod_arg[e_rse_streams] = temp; temp->nod_arg[0] = base_relation; define_view_trigger(statement, element, rse, items); // create the INSERT trigger element->nod_arg[e_cnstr_type] = MAKE_const_slong(PRE_STORE_TRIGGER); define_view_trigger(statement, element, NULL, items); statement->append_uchar(isc_dyn_end); // For triggers definition } static void define_computed(CompiledStatement* statement, dsql_nod* relation_node, dsql_fld* field, dsql_nod* node) { /************************************** * * d e f i n e _ c o m p u t e d * ************************************** * * Function * Create the ddl to define a computed field * or an expression index. * **************************************/ dsql_nod* const saved_ddl_node = statement->req_ddl_node; statement->req_ddl_node = node; // Get the table node & set up correct context reset_context_stack(statement); dsc save_desc; // Save the size of the field if it is specified save_desc.dsc_dtype = 0; if (field && field->fld_dtype) { fb_assert(field->fld_dtype <= MAX_UCHAR); save_desc.dsc_dtype = (UCHAR) field->fld_dtype; save_desc.dsc_length = field->fld_length; fb_assert(field->fld_scale <= MAX_SCHAR); save_desc.dsc_scale = (SCHAR) field->fld_scale; save_desc.dsc_sub_type = field->fld_sub_type; field->fld_dtype = 0; field->fld_length = 0; field->fld_scale = 0; field->fld_sub_type = 0; } PASS1_make_context(statement, relation_node); dsql_nod* input = PASS1_node(statement, node->nod_arg[e_cmp_expr]); // check if array or blobs are used in expression if (is_array_or_blob(statement, input)) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_no_array_computed)); } // try to calculate size of the computed field. The calculated size // may be ignored, but it will catch self references dsc desc; MAKE_desc(statement, &desc, input, NULL); // generate the blr expression statement->begin_blr(isc_dyn_fld_computed_blr); GEN_hidden_variables(statement, true); GEN_expr(statement, input); statement->end_blr(); if (save_desc.dsc_dtype) { // restore the field size/type overrides field->fld_dtype = save_desc.dsc_dtype; field->fld_length = save_desc.dsc_length; field->fld_scale = save_desc.dsc_scale; if (field->fld_dtype <= dtype_any_text) { field->fld_character_set_id = DSC_GET_CHARSET(&save_desc); field->fld_collation_id= DSC_GET_COLLATE(&save_desc); } else field->fld_sub_type = save_desc.dsc_sub_type; } else if (field) { // use size calculated field->fld_dtype = desc.dsc_dtype; field->fld_length = desc.dsc_length; field->fld_scale = desc.dsc_scale; if (field->fld_dtype <= dtype_any_text) { field->fld_character_set_id = DSC_GET_CHARSET(&desc); field->fld_collation_id= DSC_GET_COLLATE(&desc); } else field->fld_sub_type = desc.dsc_sub_type; } statement->req_type = REQ_DDL; statement->req_ddl_node = saved_ddl_node; reset_context_stack(statement); // generate the source text const dsql_str* source = (dsql_str*) node->nod_arg[e_cmp_text]; fb_assert(source->str_length <= MAX_USHORT); statement->append_string(isc_dyn_fld_computed_source, source->str_data, (USHORT) source->str_length); } static void define_constraint_trigger(CompiledStatement* statement, dsql_nod* node) { /************************************** * * d e f i n e _ c o n s t r a i n t _ t r i g g e r * ************************************** * * Function * Create the ddl to define or alter a constraint trigger. * This is a SQL's check constraint. * **************************************/ /* make the "define trigger" node the current statement ddl node so that generating of BLR will be appropriate for trigger */ dsql_nod* const saved_ddl_node = statement->req_ddl_node; statement->req_ddl_node = node; if (node->nod_type != nod_def_constraint) { return; } statement->append_string(isc_dyn_def_trigger, "", 0); dsql_nod* relation_node = node->nod_arg[e_cnstr_table]; const dsql_str* relation_name = (dsql_str*) relation_node->nod_arg[e_rln_name]; statement->append_string(isc_dyn_rel_name, relation_name->str_data, (USHORT) relation_name->str_length); const dsql_str* source = (dsql_str*) node->nod_arg[e_cnstr_source]; if (source) { fb_assert(source->str_length <= MAX_USHORT); ULONG j = find_start_of_body(source); if (j < source->str_length) { statement->append_string(isc_dyn_trg_source, source->str_data + j, source->str_length - j); } } statement->append_number(isc_dyn_trg_sequence, 0); const dsql_nod* constant = node->nod_arg[e_cnstr_type]; if (constant) { const SSHORT type = (SSHORT) constant->getSlong(); statement->append_number(isc_dyn_trg_type, type); } statement->append_uchar(isc_dyn_sql_object); // generate the trigger blr if (node->nod_arg[e_cnstr_condition] && node->nod_arg[e_cnstr_actions]) { statement->begin_blr(isc_dyn_trg_blr); statement->append_uchar(blr_begin); /* create the "OLD" and "NEW" contexts for the trigger -- the new one could be a dummy place holder to avoid resolving fields to that context but prevent relations referenced in the trigger actions from referencing the predefined "1" context */ reset_context_stack(statement); // CVC: check_constraint() is the only caller and it always receives // false for the delete_trigger_required flag. Hence, I thought I could // disable the OLD context here to avoid "ambiguous field name" errors // in pre_store and pre_modify triggers. Also, what sense can I make // from NEW in pre_delete? However, we clash at JRD with "no current // record for fetch operation". relation_node->nod_arg[e_rln_alias] = (dsql_nod*) MAKE_cstring(OLD_CONTEXT); dsql_ctx* oldContext = PASS1_make_context(statement, relation_node); oldContext->ctx_flags |= CTX_system; relation_node->nod_arg[e_rln_alias] = (dsql_nod*) MAKE_cstring(NEW_CONTEXT); dsql_ctx* newContext = PASS1_make_context(statement, relation_node); newContext->ctx_flags |= CTX_system; // generate the condition for firing the trigger dsql_nod* condition = MAKE_node(nod_not, 1); condition->nod_arg[0] = node->nod_arg[e_cnstr_condition]; condition = PASS1_node(statement, condition); GEN_hidden_variables(statement, false); statement->append_uchar(blr_if); GEN_expr(statement, condition); // generate the action statements for the trigger dsql_nod* actions = node->nod_arg[e_cnstr_actions]; dsql_nod** ptr = actions->nod_arg; for (const dsql_nod* const* const end = ptr + actions->nod_count; ptr < end; ptr++) { GEN_statement(statement, PASS1_statement(statement, *ptr)); } statement->append_uchar(blr_end); // of if (as there's no ELSE branch) statement->append_uchar(blr_end); // of begin statement->end_blr(); } statement->append_number(isc_dyn_system_flag, fb_sysflag_check_constraint); statement->append_uchar(isc_dyn_end); /* 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->req_type = REQ_DDL; statement->req_ddl_node = saved_ddl_node; reset_context_stack(statement); } static void define_database(CompiledStatement* statement) { /************************************** * * d e f i n e _ d a t a b a s e * ************************************** * * Function * Create a database. Assumes that * database is created elsewhere with * initial options. Modify the * database using DYN to add the remaining * options. * **************************************/ SLONG start = 0; const dsql_nod* ddl_node = statement->req_ddl_node; statement->append_uchar(isc_dyn_mod_database); /* statement->append_number(isc_dyn_rel_sql_protection, 1); */ const dsql_nod* elements = ddl_node->nod_arg[e_database_initial_desc]; if (elements) { const dsql_nod* const* ptr = elements->nod_arg; for (const dsql_nod* const* const end = ptr + elements->nod_count; ptr < end; ptr++) { const dsql_nod* element = *ptr; switch (element->nod_type) { case nod_file_length: start = (IPTR) element->nod_arg[0] + 1; break; default: break; } } } const dsql_str* name; const dsql_fil* file; elements = ddl_node->nod_arg[e_database_rem_desc]; if (elements) { const dsql_nod* const* ptr = elements->nod_arg; for (const dsql_nod* const* const end = ptr + elements->nod_count; ptr < end; ptr++) { const dsql_nod* element = *ptr; switch (element->nod_type) { case nod_difference_file: statement->append_cstring(isc_dyn_def_difference, ((dsql_str*)element->nod_arg[0])->str_data); break; case nod_file_desc: file = (dsql_fil*) element->nod_arg[0]; statement->append_cstring(isc_dyn_def_file, file->fil_name->str_data); start = MAX(start, file->fil_start); statement->append_file_start(start); statement->append_file_length(file->fil_length); statement->append_uchar(isc_dyn_end); start += file->fil_length; break; case nod_dfl_charset: name = (dsql_str*) element->nod_arg[0]; statement->append_cstring(isc_dyn_fld_character_set_name, name->str_data); break; case nod_dfl_collate: name = (dsql_str*) element->nod_arg[0]; statement->append_cstring(isc_dyn_fld_collation, name->str_data); break; default: break; } } } statement->append_uchar(isc_dyn_end); } static bool define_default(CompiledStatement* statement, const dsql_nod* node) { /***************************************** * * d e f i n e _ d e f a u l t * ***************************************** * * Function * Define default value for domain/column. * Returns whether it is specified as being NULL. * **************************************/ fb_assert(node && node->nod_type == nod_def_default); dsql_nod* const value = PASS1_node(statement, node->nod_arg[e_dft_default]); fb_assert(value); statement->begin_blr(isc_dyn_fld_default_value); GEN_hidden_variables(statement, true); GEN_expr(statement, value); statement->end_blr(); dsql_str* const string = (dsql_str*) node->nod_arg[e_dft_default_source]; fb_assert(string && string->str_length <= MAX_USHORT); fix_default_source(string); statement->append_string(isc_dyn_fld_default_source, string->str_data, string->str_length); return (value->nod_type == nod_null); } static void define_del_cascade_trg( CompiledStatement* statement, const dsql_nod* element, const dsql_nod* for_columns, const dsql_nod* prim_columns, const char* prim_rel_name, const char* for_rel_name) { /***************************************************** * * d e f i n e _ d e l _ c a s c a d e _ t r g * ***************************************************** * * Function * define "on delete cascade" trigger (for referential integrity) * along with its blr * *****************************************************/ if (element->nod_type != nod_foreign) { return; } // stuff a trigger_name of size 0. So the dyn-parser will make one up. statement->append_string(isc_dyn_def_trigger, "", 0); statement->append_number(isc_dyn_trg_type, (SSHORT) POST_ERASE_TRIGGER); statement->append_uchar(isc_dyn_sql_object); statement->append_number(isc_dyn_trg_sequence, (SSHORT) 1); statement->append_number(isc_dyn_trg_inactive, (SSHORT) 0); statement->append_cstring(isc_dyn_rel_name, prim_rel_name); // the trigger blr statement->begin_blr(isc_dyn_trg_blr); statement->append_uchar(blr_for); statement->append_uchar(blr_rse); // the context for the prim. key relation statement->append_uchar(1); statement->append_uchar(blr_relation); statement->append_cstring(0, for_rel_name); // the context for the foreign key relation statement->append_uchar(2); stuff_matching_blr(statement, for_columns, prim_columns); statement->append_uchar(blr_erase); statement->append_uchar(2); statement->end_blr(); // end of the blr statement->append_number(isc_dyn_system_flag, fb_sysflag_referential_constraint); // no trg_source and no trg_description statement->append_uchar(isc_dyn_end); } static void define_set_default_trg( CompiledStatement* statement, const dsql_nod* element, const dsql_nod* for_columns, const dsql_nod* prim_columns, const char* prim_rel_name, const char* for_rel_name, bool on_upd_trg) { /***************************************************** * * d e f i n e _ s e t _ d e f a u l t _ t r g * ***************************************************** * * Function * define "on delete|update set default" trigger (for * referential integrity) along with its blr * *****************************************************/ if (element->nod_type != nod_foreign) { return; } statement->generate_unnamed_trigger_beginning(on_upd_trg, prim_rel_name, prim_columns, for_rel_name, for_columns); UCHAR default_val[BLOB_BUFFER_SIZE]; USHORT num_fields = 0; const dsql_nod* const* for_key_flds = for_columns->nod_arg; const dsql_nod* ddl_node = statement->req_ddl_node; do { // for every column in the foreign key .... const dsql_str* for_key_fld_name_str = (dsql_str*) (*for_key_flds)->nod_arg[1]; statement->append_uchar(blr_assignment); /* here stuff the default value as blr_literal .... or blr_null if this col. does not have an applicable default */ /* the default is determined in many cases: (1) the info. for the column is in memory. (This is because the column is being created in this ddl statement) (1-a) the table has a column level default. We get this by searching the dsql parse tree starting from the ddl node. (1-b) the table does not have a column level default, but has a domain default. We get the domain name from the dsql parse tree and call METD_get_domain_default to read the default from the system tables. (2) The default-info for this column is not in memory (This is because this is an alter table ddl statement). The table already exists; therefore we get the column and/or domain default value from the system tables by calling: METD_get_col_default(). */ bool found_default = false; bool search_for_default = true; // search the parse tree to find the column const dsql_nod* elem = ddl_node->nod_arg[e_drl_elements]; const dsql_nod* const* ptr = elem->nod_arg; for (const dsql_nod* const* const end = ptr + elem->nod_count; ptr < end; ++ptr) { elem = *ptr; if (elem->nod_type != nod_def_field) { continue; } const dsql_fld* field = (dsql_fld*) elem->nod_arg[e_dfl_field]; if (field->fld_name != for_key_fld_name_str->str_data) { continue; } // Now, we have the right column in the parse tree. case (1) above dsql_nod* default_node = elem->nod_arg[e_dfl_default]; if (default_node) { // case (1-a) above: there is a col. level default fb_assert(default_node->nod_type == nod_def_default); GEN_hidden_variables(statement, true); GEN_expr(statement, default_node->nod_arg[e_dft_default]); found_default = true; search_for_default = false; } else { const TEXT* domain_name; const dsql_str* domain_name_str; const dsql_nod* tmp_node; const dsql_nod* domain_node = elem->nod_arg[e_dfl_domain]; if (!domain_node || !(tmp_node = domain_node->nod_arg[e_dom_name]) || !(domain_name_str = (dsql_str*) tmp_node->nod_arg[e_fln_name]) || !(domain_name = domain_name_str->str_data)) { break; } /* case: (1-b): domain name is available. Column level default is not declared. so get the domain default */ const USHORT def_len = METD_get_domain_default(statement, domain_name, &found_default, default_val, sizeof(default_val)); search_for_default = false; if (found_default) { stuff_default_blr(statement, default_val, def_len); } else { // neither col level nor domain level default exists statement->append_uchar(blr_null); } } break; } if (search_for_default) { // case 2: see if the column/domain has already been created const USHORT def_len = METD_get_col_default(statement, for_rel_name, for_key_fld_name_str->str_data, &found_default, default_val, sizeof(default_val)); if (found_default) { stuff_default_blr(statement, default_val, def_len); } else { statement->append_uchar(blr_null); } } // the context for the foreign key relation statement->append_uchar(blr_field); statement->append_uchar(2); statement->append_cstring(0, for_key_fld_name_str->str_data); num_fields++; for_key_flds++; } while (num_fields < for_columns->nod_count); statement->append_uchar(blr_end); if (on_upd_trg) { statement->append_uchars(blr_end, 3); } statement->end_blr(); statement->append_number(isc_dyn_system_flag, fb_sysflag_referential_constraint); // no trg_source and no trg_description statement->append_uchar(isc_dyn_end); } static void define_dimensions( CompiledStatement* statement, const dsql_fld* field) { /***************************************** * * d e f i n e _ d i m e n s i o n s * ***************************************** * * Function * Define dimensions of an array * **************************************/ const dsql_nod* elements = field->fld_ranges; const USHORT dims = elements->nod_count / 2; if (dims > MAX_ARRAY_DIMENSIONS) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-604) << Arg::Gds(isc_dsql_max_arr_dim_exceeded)); } statement->append_number(isc_dyn_fld_dimensions, (SSHORT) dims); 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) { statement->append_number(isc_dyn_def_dimension, position); const dsql_nod* element = *ptr++; statement->append_uchar(isc_dyn_dim_lower); const SLONG lrange = element->getSlong(); statement->append_ulong_with_length(lrange); element = *ptr; statement->append_uchar(isc_dyn_dim_upper); const SLONG hrange = element->getSlong(); statement->append_ulong_with_length(hrange); statement->append_uchar(isc_dyn_end); if (lrange >= hrange) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-604) << Arg::Gds(isc_dsql_arr_range_error)); } } } static void define_domain(CompiledStatement* statement) { /************************************** * * d e f i n e _ d o m a i n * ************************************** * * Function * Define a domain (global field) * **************************************/ dsql_nod* element = statement->req_ddl_node; dsql_fld* field = (dsql_fld*) element->nod_arg[e_dom_name]; if (fb_utils::implicit_domain(field->fld_name.c_str())) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-637) << Arg::Gds(isc_dsql_implicit_domain_name) << Arg::Str(field->fld_name)); } statement->append_string(isc_dyn_def_global_fld, field->fld_name); DDL_resolve_intl_type(statement, field, (dsql_str*) element->nod_arg[e_dom_collate]); put_field(statement, field, false); // check for a default value dsql_nod* node = element->nod_arg[e_dom_default]; if (node) { define_default(statement, node); } if (field->fld_ranges) { define_dimensions(statement, field); } bool null_flag = false; bool check_flag = false; // check for constraints node = element->nod_arg[e_dom_constraint]; if (node) { dsql_nod** ptr = node->nod_arg; const dsql_nod* const* const end_ptr = ptr + node->nod_count; for (; ptr < end_ptr; ++ptr) { if ((*ptr)->nod_type == nod_rel_constraint) { dsql_nod* node1 = (*ptr)->nod_arg[e_rct_type]; if (node1->nod_type == nod_null) { if (!null_flag) { statement->append_uchar(isc_dyn_fld_not_null); null_flag = true; } else { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-637) << Arg::Gds(isc_dsql_duplicate_spec) << Arg::Str("NOT NULL")); } } else if (node1->nod_type == nod_def_constraint) { if (check_flag) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-637) << Arg::Gds(isc_dsql_duplicate_spec) << Arg::Str("DOMAIN CHECK CONSTRAINT")); } check_flag = true; const dsql_str* string = (dsql_str*) node1->nod_arg[e_cnstr_source]; if (string) { fb_assert(string->str_length <= MAX_USHORT); statement->append_string(isc_dyn_fld_validation_source, string->str_data, string->str_length); } statement->begin_blr(isc_dyn_fld_validation_blr); // Set any VALUE nodes to the type of the domain being defined. if (node1->nod_arg[e_cnstr_condition]) { set_nod_value_attributes(node1->nod_arg[e_cnstr_condition], field); } /* 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 */ statement->req_context_number++; node = PASS1_node(statement, node1->nod_arg[e_cnstr_condition]); GEN_hidden_variables(statement, true); GEN_expr(statement, node); statement->end_blr(); } } } } statement->append_uchar(isc_dyn_end); } static void define_exception( CompiledStatement* statement, NOD_TYPE op) { /************************************** * * d e f i n e _ e x c e p t i o n * ************************************** * * Function * Generate ddl to create an exception code. * **************************************/ const dsql_nod* ddl_node = statement->req_ddl_node; const dsql_str* name = (dsql_str*) ddl_node->nod_arg[e_xcp_name]; switch (op) { case nod_replace_exception: if (METD_get_exception(statement, name)) { define_exception(statement, nod_mod_exception); } else { define_exception(statement, nod_def_exception); } break; case nod_def_exception: case nod_redef_exception: statement->append_cstring(isc_dyn_def_exception, name->str_data); break; case nod_mod_exception: statement->append_cstring(isc_dyn_mod_exception, name->str_data); break; default: fb_assert(false); } const dsql_str* text = (dsql_str*) ddl_node->nod_arg[e_xcp_text]; fb_assert(text->str_length <= MAX_USHORT); statement->append_string(isc_dyn_xcp_msg, text->str_data, text->str_length); statement->append_uchar(isc_dyn_end); } static void define_field(CompiledStatement* statement, dsql_nod* element, SSHORT position, const dsql_str* relation_name, const dsql_nod* pkcols) { /************************************** * * d e f i n e _ f i e l d * ************************************** * * Function * Define a field, either as part of a create * table or an alter table statement. * **************************************/ dsql_fld* field = (dsql_fld*) element->nod_arg[e_dfl_field]; // add the field to the relation being defined for parsing purposes bool permanent = false; dsql_rel* relation = statement->req_relation; if (relation != NULL) { if (! (relation->rel_flags & REL_new_relation)) { dsql_fld* perm_field = FB_NEW(statement->req_dbb->dbb_pool) dsql_fld(statement->req_dbb->dbb_pool); *perm_field = *field; field = perm_field; permanent = true; } field->fld_next = relation->rel_fields; relation->rel_fields = field; } try { const dsql_nod* domain_node = element->nod_arg[e_dfl_domain]; if (domain_node) { statement->append_string(isc_dyn_def_local_fld, field->fld_name); const dsql_nod* node1 = domain_node->nod_arg[e_dom_name]; const dsql_str* domain_name = (dsql_str*) node1->nod_arg[e_fln_name]; statement->append_cstring(isc_dyn_fld_source, domain_name->str_data); // Get the domain information if (!METD_get_domain(statement, field, domain_name->str_data)) { // Specified domain or source field does not exist post_607(Arg::Gds(isc_dsql_domain_not_found) << Arg::Str(domain_name->str_data)); } DDL_resolve_intl_type(statement, field, reinterpret_cast(element->nod_arg[e_dfl_collate])); if (element->nod_arg[e_dfl_collate]) { statement->append_number(isc_dyn_fld_collation, field->fld_collation_id); } } else { statement->append_string(isc_dyn_def_sql_fld, field->fld_name); if (relation_name) { statement->append_cstring(isc_dyn_rel_name, relation_name->str_data); } if (element->nod_arg[e_dfl_computed]) { field->fld_flags |= FLD_computed; dsql_nod* computed_node = element->nod_arg[e_dfl_computed]; define_computed(statement, statement->req_ddl_node->nod_arg[e_drl_name], field, computed_node); } DDL_resolve_intl_type(statement, field, reinterpret_cast(element->nod_arg[e_dfl_collate])); put_field(statement, field, false); } if ((relation->rel_flags & REL_external) && (field->fld_dtype == dtype_blob || field->fld_dtype == dtype_array || field->fld_dimensions)) { const char* typeName = (field->fld_dtype == dtype_blob ? "BLOB" : "ARRAY"); post_607(Arg::Gds(isc_dsql_type_not_supp_ext_tab) << Arg::Str(typeName) << Arg::Str(relation->rel_name) << Arg::Str(field->fld_name)); } if (position != -1) statement->append_number(isc_dyn_fld_position, position); // check for a default value bool default_null_flag = false; dsql_nod* node = element->nod_arg[e_dfl_default]; if (node) { default_null_flag = define_default(statement, node); } if (field->fld_ranges) { define_dimensions(statement, field); } // dimitr: store the final position of the vector to insert a not null // item later, if required. This is a kind of a hack, but I see // no other way to ensure that NOT NULL is properly understood // everywhere in the column constraint definition. A better // solution would be a special class which handles all append_* // operations for BLR/DYN (we could store constraints in the // separate object and then merge them into req_blr_data), but // this is for another day. const size_t end = statement->req_blr_data.getCount(); statement->append_uchar(isc_dyn_end); // check for constraints bool not_null_flag = false; if ( (node = element->nod_arg[e_dfl_constraint]) ) { const dsql_nod* const* const end_ptr = node->nod_arg + node->nod_count; for (dsql_nod** ptr = node->nod_arg; ptr < end_ptr; ++ptr) { if ((*ptr)->nod_type == nod_rel_constraint) { const dsql_str* string = (dsql_str*) (*ptr)->nod_arg[e_rct_name]; dsql_nod* node1 = (*ptr)->nod_arg[e_rct_type]; switch (node1->nod_type) { case nod_null: case nod_primary: if (default_null_flag) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_bad_default_value) << Arg::Gds(isc_invalid_clause) << Arg::Str("default null not null")); } if (!not_null_flag) { statement->append_cstring(isc_dyn_rel_constraint, string && node1->nod_type == nod_null ? string->str_data : 0); statement->append_uchar(isc_dyn_fld_not_null); statement->append_uchar(isc_dyn_end); not_null_flag = true; } if (node1->nod_type == nod_null) break; // nod_primary falls into case nod_unique: { const char* constraint_name = string ? string->str_data : 0; statement->append_cstring(isc_dyn_rel_constraint, constraint_name); const dsql_nod* index = node1->nod_arg[e_pri_index]; fb_assert(index); const char* index_name = constraint_name; string = (dsql_str*) index->nod_arg[e_idx_name]; if (string) { index_name = string->str_data; } if (node1->nod_type == nod_primary) { statement->append_cstring(isc_dyn_def_primary_key, index_name); } else if (node1->nod_type == nod_unique) { statement->append_cstring(isc_dyn_def_unique, index_name); } statement->append_number(isc_dyn_idx_unique, 1); if (index->nod_arg[e_idx_asc_dsc]) { statement->append_number(isc_dyn_idx_type, 1); } statement->append_string(isc_dyn_fld_name, field->fld_name); statement->append_uchar(isc_dyn_end); } break; case nod_foreign: { const char* constraint_name = string ? string->str_data : 0; statement->append_cstring(isc_dyn_rel_constraint, constraint_name); foreign_key(statement, node1, constraint_name); } break; case nod_def_constraint: statement->append_cstring(isc_dyn_rel_constraint, string ? string->str_data : 0); check_constraint(statement, node1, false); // No delete trigger break; } } } } if (not_null_flag) { // dimitr: insert a not null item right before column's isc_dyn_end statement->req_blr_data.insert(end, isc_dyn_fld_not_null); } else if (pkcols) { // Let's see if it appears in a "primary_key(a, b, c)" relation constraint. for (int i = 0; i < pkcols->nod_count; ++i) { const dsql_str* pkfield_name = (dsql_str*) pkcols->nod_arg[i]->nod_arg[e_fln_name]; if (field->fld_name == pkfield_name->str_data) { statement->req_blr_data.insert(end, isc_dyn_fld_not_null); break; } } } } // try catch (const Exception&) { clearPermanentField(relation, permanent); throw; } clearPermanentField(relation, permanent); } static SSHORT getBlobFilterSubType(CompiledStatement* statement, const dsql_nod* node) { /******************************************* * * g e t B l o b F i l t e r S u b T y p e * ******************************************* * * Function * get sub_type value from nod_constant. * **************************************/ fb_assert(node->nod_type == nod_constant); switch (node->nod_desc.dsc_dtype) { case dtype_long: return (SSHORT) node->getSlong(); case dtype_text: break; default: fb_assert(false); return 0; } // fall thru for dtype_text SSHORT blob_sub_type; if (!METD_get_type(statement, (const dsql_str*)(node->nod_arg[0]), "RDB$FIELD_SUB_TYPE", &blob_sub_type)) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_dsql_datatype_err) << Arg::Gds(isc_dsql_blob_type_unknown) << Arg::Str(((const dsql_str*)(node->nod_arg[0]))->str_data)); } return blob_sub_type; } static void define_filter(CompiledStatement* statement) { /************************************** * * d e f i n e _ f i l t e r * ************************************** * * Function * define a filter to the database. * **************************************/ const dsql_nod* filter_node = statement->req_ddl_node; const dsql_nod* const* ptr = filter_node->nod_arg; statement->append_cstring(isc_dyn_def_filter, ((dsql_str*) (ptr[e_filter_name]))->str_data); statement->append_number(isc_dyn_filter_in_subtype, getBlobFilterSubType(statement, ptr[e_filter_in_type])); statement->append_number(isc_dyn_filter_out_subtype, getBlobFilterSubType(statement, ptr[e_filter_out_type])); statement->append_cstring(isc_dyn_func_entry_point, ((dsql_str*) (ptr[e_filter_entry_pt]))->str_data); statement->append_cstring(isc_dyn_func_module_name, ((dsql_str*) (ptr[e_filter_module]))->str_data); statement->append_uchar(isc_dyn_end); } static void define_collation(CompiledStatement* statement) { /************************************** * * d e f i n e _ c o l l a t i o n * ************************************** * * Function * Create a collation. * **************************************/ const dsql_str* coll_name = (dsql_str*) statement->req_ddl_node->nod_arg[e_def_coll_name]; const dsql_str* coll_for = (dsql_str*) statement->req_ddl_node->nod_arg[e_def_coll_for]; const dsql_nod* coll_from = statement->req_ddl_node->nod_arg[e_def_coll_from]; const dsql_nod* coll_attributes = statement->req_ddl_node->nod_arg[e_def_coll_attributes]; const dsql_nod* coll_specific_attributes = PASS1_node(statement, statement->req_ddl_node->nod_arg[e_def_coll_specific_attributes]); const dsql_intlsym* resolved_charset = METD_get_charset(statement, (USHORT) strlen(coll_for->str_data), coll_for->str_data); if (!resolved_charset) { // specified character set not found ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-504) << Arg::Gds(isc_charset_not_found) << Arg::Str(coll_for->str_data)); } if (coll_specific_attributes) coll_specific_attributes = coll_specific_attributes->nod_arg[0]; statement->append_cstring(isc_dyn_def_collation, coll_name->str_data); statement->append_number(isc_dyn_coll_for_charset, resolved_charset->intlsym_charset_id); if (coll_from) { if (coll_from->nod_type == nod_collation_from) { const dsql_intlsym* resolved_collation = METD_get_collation(statement, (dsql_str*)coll_from->nod_arg[0], resolved_charset->intlsym_charset_id); if (!resolved_collation) { // Specified collation not found ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_collation_not_found) << Arg::Str(((dsql_str*)coll_from->nod_arg[0])->str_data) << Arg::Str(resolved_charset->intlsym_name)); } statement->append_number(isc_dyn_coll_from, INTL_CS_COLL_TO_TTYPE(resolved_collation->intlsym_charset_id, resolved_collation->intlsym_collate_id)); } else if (coll_from->nod_type == nod_collation_from_external) { statement->append_cstring(isc_dyn_coll_from_external, ((dsql_str*)coll_from->nod_arg[0])->str_data); } else fb_assert(false); } if (coll_attributes) { const dsql_nod* const* ptr = coll_attributes->nod_arg; for (const dsql_nod* const* const end = ptr + coll_attributes->nod_count; ptr < end; ptr++) { const dsql_nod* attribute = *ptr; switch (attribute->nod_type) { case nod_collation_attr: statement->append_number(isc_dyn_coll_attribute, (IPTR) attribute->nod_arg[0]); break; default: break; } } } if (coll_specific_attributes) { statement->append_number(isc_dyn_coll_specific_attributes_charset, coll_specific_attributes->nod_desc.dsc_ttype()); statement->append_cstring(isc_dyn_coll_specific_attributes, ((dsql_str*)coll_specific_attributes->nod_arg[0])->str_data); } statement->append_uchar(isc_dyn_end); } static void define_generator(CompiledStatement* statement) { /************************************** * * d e f i n e _ g e n e r a t o r * ************************************** * * Function * create a generator. * **************************************/ const dsql_str* gen_name = (dsql_str*) statement->req_ddl_node->nod_arg[e_gen_name]; statement->append_cstring(isc_dyn_def_generator, gen_name->str_data); statement->append_uchar(isc_dyn_end); } static void define_index(CompiledStatement* statement) { /************************************** * * d e f i n e _ i n d e x * ************************************** * * Function * Generate ddl to create an index. * **************************************/ statement->append_uchar(isc_dyn_begin); const dsql_nod* ddl_node = statement->req_ddl_node; dsql_nod* relation_node = (dsql_nod*) ddl_node->nod_arg[e_idx_table]; const dsql_str* relation_name = (dsql_str*) relation_node->nod_arg[e_rln_name]; dsql_nod* field_list = ddl_node->nod_arg[e_idx_fields]; const dsql_str* index_name = (dsql_str*) ddl_node->nod_arg[e_idx_name]; statement->append_cstring(isc_dyn_def_idx, index_name->str_data); statement->append_cstring(isc_dyn_rel_name, relation_name->str_data); /* go through the fields list, making an index segment for each field, unless we have a computation, in which case generate an expression index */ if (field_list->nod_type == nod_list) { const dsql_nod* const* ptr = field_list->nod_arg; const dsql_nod* const* const end = ptr + field_list->nod_count; for (; ptr < end; ptr++) { statement->append_cstring(isc_dyn_fld_name, ((dsql_str*) (*ptr)->nod_arg[1])->str_data); } } else if (field_list->nod_type == nod_def_computed) define_computed(statement, relation_node, NULL, field_list); // check for a unique index if (ddl_node->nod_arg[e_idx_unique]) { statement->append_number(isc_dyn_idx_unique, 1); } if (ddl_node->nod_arg[e_idx_asc_dsc]) { statement->append_number(isc_dyn_idx_type, 1); } statement->append_uchar(isc_dyn_end); // of define index statement->append_uchar(isc_dyn_end); // of begin } static void define_procedure(CompiledStatement* statement, NOD_TYPE op) { /************************************** * * d e f i n e _ p r o c e d u r e * ************************************** * * Function * Create DYN to store a procedure * **************************************/ thread_db* tdbb = JRD_get_thread_data(); SSHORT inputs = 0, defaults = 0; SSHORT outputs = 0; SSHORT locals = 0; const dsql_nod* procedure_node = statement->req_ddl_node; const dsql_str* procedure_name = (dsql_str*) procedure_node->nod_arg[e_prc_name]; switch (op) { case nod_replace_procedure: if (METD_get_procedure(statement, procedure_name)) define_procedure(statement, nod_mod_procedure); else define_procedure(statement, nod_def_procedure); return; case nod_def_procedure: case nod_redef_procedure: statement->append_cstring(isc_dyn_def_procedure, procedure_name->str_data); statement->append_number(isc_dyn_rel_sql_protection, 1); break; default: // op == nod_mod_procedure { statement->append_cstring(isc_dyn_mod_procedure, procedure_name->str_data); const dsql_prc* procedure = METD_get_procedure(statement, procedure_name); if (procedure) { const dsql_fld* field; for (field = procedure->prc_inputs; field; field = field->fld_next) { statement->append_string(isc_dyn_delete_parameter, field->fld_name); statement->append_uchar(isc_dyn_end); } for (field = procedure->prc_outputs; field; field = field->fld_next) { statement->append_string(isc_dyn_delete_parameter, field->fld_name); statement->append_uchar(isc_dyn_end); } } } } statement->begin_debug(); const dsql_str* source = (dsql_str*) procedure_node->nod_arg[e_prc_source]; if (source) { fb_assert(source->str_length <= MAX_USHORT); ULONG j = find_start_of_body(source); if (j < source->str_length) { statement->append_string(isc_dyn_prc_source, source->str_data + j, source->str_length - j); } } // fill req_procedure to allow procedure to self reference MemoryPool& pool = *tdbb->getDefaultPool(); dsql_prc* procedure = FB_NEW(pool) dsql_prc(pool); procedure->prc_name = procedure_name->str_data; statement->req_procedure = procedure; // now do the input parameters dsql_fld** field_ptr = &procedure->prc_inputs; dsql_nod* parameters = procedure_node->nod_arg[e_prc_inputs]; if (parameters) { SSHORT position = 0; dsql_nod** ptr = parameters->nod_arg; for (const dsql_nod* const* const end = ptr + parameters->nod_count; ptr < end; ptr++) { dsql_nod* parameter = *ptr; dsql_fld* field = (dsql_fld*) parameter->nod_arg[e_dfl_field]; statement->append_string(isc_dyn_def_parameter, field->fld_name); statement->append_number(isc_dyn_prm_number, position); statement->append_number(isc_dyn_prm_type, 0); DDL_resolve_intl_type(statement, field, reinterpret_cast(parameter->nod_arg[e_dfl_collate])); put_field(statement, field, false); statement->put_debug_argument(fb_dbg_arg_input, position, field->fld_name.c_str()); // check for a parameter default value dsql_nod* node = parameter->nod_arg[e_dfl_default]; if (node) { define_default(statement, node); defaults++; } else if (defaults) { // 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")); } *ptr = MAKE_variable(field, field->fld_name.c_str(), VAR_input, 0, (USHORT) (2 * position), locals); // put the field in a field list which will be stored to allow // procedure self referencing *field_ptr = field; field_ptr = &field->fld_next; position++; statement->append_uchar(isc_dyn_end); statement->append_number(isc_dyn_prc_inputs, position); } inputs = position; } // terminate the input list *field_ptr = NULL; // now do the output parameters field_ptr = &procedure->prc_outputs; if (parameters = procedure_node->nod_arg[e_prc_outputs]) { SSHORT position = 0; dsql_nod** ptr = parameters->nod_arg; for (const dsql_nod* const* const end = ptr + parameters->nod_count; ptr < end; ++ptr) { dsql_nod* parameter = *ptr; dsql_fld* field = (dsql_fld*) parameter->nod_arg[e_dfl_field]; statement->append_string(isc_dyn_def_parameter, field->fld_name); statement->append_number(isc_dyn_prm_number, position); statement->append_number(isc_dyn_prm_type, 1); DDL_resolve_intl_type(statement, field, reinterpret_cast(parameter->nod_arg[e_dfl_collate])); put_field(statement, field, false); statement->put_debug_argument(fb_dbg_arg_output, position, field->fld_name.c_str()); *ptr = MAKE_variable(field, field->fld_name.c_str(), VAR_output, 1, (USHORT) (2 * position), locals); *field_ptr = field; field_ptr = &field->fld_next; position++; locals++; statement->append_uchar(isc_dyn_end); statement->append_number(isc_dyn_prc_outputs, position); } outputs = position; } *field_ptr = NULL; procedure->prc_out_count = outputs; procedure->prc_in_count = inputs; procedure->prc_def_count = defaults; statement->begin_blr(isc_dyn_prc_blr); statement->append_uchar(blr_begin); if (inputs) { statement->append_uchar(blr_message); statement->append_uchar(0); statement->append_ushort(2 * inputs); parameters = procedure_node->nod_arg[e_prc_inputs]; dsql_nod** ptr = parameters->nod_arg; for (const dsql_nod* const* const end = ptr + parameters->nod_count; ptr < end; ptr++) { const dsql_nod* parameter = *ptr; const dsql_var* variable = (dsql_var*) parameter->nod_arg[e_var_variable]; const dsql_fld* field = variable->var_field; put_msg_field(statement, field); } } statement->append_uchar(blr_message); statement->append_uchar(1); statement->append_ushort(2 * outputs + 1); if (outputs) { parameters = procedure_node->nod_arg[e_prc_outputs]; dsql_nod** ptr = parameters->nod_arg; for (const dsql_nod* const* const end = ptr + parameters->nod_count; ptr < end; ptr++) { const dsql_nod* parameter = *ptr; const dsql_var* variable = (dsql_var*) parameter->nod_arg[e_var_variable]; const dsql_fld* field = variable->var_field; put_msg_field(statement, field); } } // add slot for EOS statement->append_uchar(blr_short); statement->append_uchar(0); if (inputs) { statement->append_uchar(blr_receive); statement->append_uchar(0); } statement->append_uchar(blr_begin); if (inputs) { parameters = procedure_node->nod_arg[e_prc_inputs]; const dsql_nod* const* ptr = parameters->nod_arg; for (const dsql_nod* const* const end = ptr + parameters->nod_count; ptr < end; ptr++) { const dsql_nod* parameter = *ptr; const dsql_var* variable = (dsql_var*) parameter->nod_arg[e_var_variable]; const dsql_fld* field = variable->var_field; if (field->fld_full_domain || field->fld_not_nullable) { // ASF: To validate input parameters we need only to read his value. // Assigning it to null is an easy way to do this. statement->append_uchar(blr_assignment); statement->append_uchar(blr_parameter2); statement->append_uchar(variable->var_msg_number); statement->append_ushort(variable->var_msg_item); statement->append_ushort(variable->var_msg_item + 1); statement->append_uchar(blr_null); } } } if (outputs) { parameters = procedure_node->nod_arg[e_prc_outputs]; dsql_nod** ptr = parameters->nod_arg; for (const dsql_nod* const* const end = ptr + parameters->nod_count; ptr < end; ptr++) { dsql_nod* parameter = *ptr; dsql_var* variable = (dsql_var*) parameter->nod_arg[e_var_variable]; put_local_variable(statement, variable, 0, NULL); } } // ASF: This is here to not change the old logic (proc_flag) // of previous calls to PASS1_node and PASS1_statement. statement->setPsql(true); put_local_variables(statement, procedure_node->nod_arg[e_prc_dcls], locals); statement->req_loop_level = 0; statement->req_cursor_number = 0; dsql_nod* stmtNode = PASS1_statement(statement, procedure_node->nod_arg[e_prc_body]); GEN_hidden_variables(statement, false); statement->append_uchar(blr_stall); // put a label before body of procedure, // so that any EXIT statement can get out statement->append_uchar(blr_label); statement->append_uchar(0); GEN_statement(statement, stmtNode); statement->req_type = REQ_DDL; statement->append_uchar(blr_end); GEN_return(statement, procedure_node->nod_arg[e_prc_outputs], true); statement->append_uchar(blr_end); statement->end_blr(); const UCHAR prc_type = (statement->req_flags & REQ_selectable) ? isc_dyn_prc_t_selectable : isc_dyn_prc_t_executable; statement->append_number(isc_dyn_prc_type, prc_type); statement->append_debug_info(); statement->append_uchar(isc_dyn_end); } void DDL_gen_block(CompiledStatement* statement, dsql_nod* node) { /************************************** * * D D L _ g e n _ b l o c k * ************************************** * * Function * Generate BLR for EXECUTE BLOCK statement * **************************************/ SSHORT inputs = 0, outputs = 0, locals = 0; statement->req_blk_node = node; statement->begin_debug(); dsql_nod* parameters; // now do the input parameters if (parameters = node->nod_arg[e_exe_blk_inputs]) { SSHORT position = 0; dsql_nod** ptr = parameters->nod_arg; for (const dsql_nod* const* const end = ptr + parameters->nod_count; ptr < end; ptr++) { dsql_nod* parameter = (*ptr)->nod_arg[e_prm_val_fld]; dsql_fld* field = (dsql_fld*) parameter->nod_arg[e_dfl_field]; // parameter = (*ptr)->nod_arg[e_prm_val_val]; USELESS DDL_resolve_intl_type(statement, field, reinterpret_cast(parameter->nod_arg[e_dfl_collate])); *ptr = MAKE_variable(field, field->fld_name.c_str(), VAR_input, 0, (USHORT) (2 * position), locals++); position++; } inputs = position; } // now do the output parameters if (parameters = node->nod_arg[e_exe_blk_outputs]) { SSHORT position = 0; dsql_nod** ptr = parameters->nod_arg; for (const dsql_nod* const* const end = ptr + parameters->nod_count; ptr < end; ++ptr) { dsql_fld* field = (dsql_fld*) (*ptr)->nod_arg[e_dfl_field]; DDL_resolve_intl_type(statement, field, reinterpret_cast((*ptr)->nod_arg[e_dfl_collate])); *ptr = MAKE_variable(field, field->fld_name.c_str(), VAR_output, 1, (USHORT) (2 * position), locals++); position++; } outputs = position; } statement->append_uchar(blr_begin); if (inputs) { statement->req_send->msg_parameters = parameter_reverse_order(statement->req_send->msg_parameters, NULL); GEN_port(statement, statement->req_send); } else statement->req_send = NULL; if (outputs) { SSHORT position = 0; parameters = node->nod_arg[e_exe_blk_outputs]; dsql_nod** ptr = parameters->nod_arg; for (const dsql_nod* const* const end = ptr + parameters->nod_count; ptr < end; ptr++) { dsql_par* param = MAKE_parameter(statement->req_receive, true, true, ++position, *ptr); param->par_node = *ptr; MAKE_desc(statement, ¶m->par_desc, *ptr, NULL); param->par_desc.dsc_flags |= DSC_nullable; } } // Set up parameter to handle EOF dsql_par* param = MAKE_parameter(statement->req_receive, false, false, 0, NULL); statement->req_eof = param; param->par_desc.dsc_dtype = dtype_short; param->par_desc.dsc_scale = 0; param->par_desc.dsc_length = sizeof(SSHORT); statement->req_receive->msg_parameters = parameter_reverse_order(statement->req_receive->msg_parameters, NULL); GEN_port(statement, statement->req_receive); if (inputs) { statement->append_uchar(blr_receive); statement->append_uchar(0); } statement->append_uchar(blr_begin); if (outputs) { parameters = node->nod_arg[e_exe_blk_outputs]; dsql_nod** ptr = parameters->nod_arg; for (const dsql_nod* const* const end = ptr + parameters->nod_count; ptr < end; ptr++) { dsql_nod* parameter = *ptr; dsql_var* variable = (dsql_var*) parameter->nod_arg[e_var_variable]; put_local_variable(statement, variable, 0, NULL); } } statement->setPsql(true); put_local_variables(statement, node->nod_arg[e_exe_blk_dcls], locals); statement->req_loop_level = 0; dsql_nod* stmtNode = PASS1_statement(statement, node->nod_arg[e_exe_blk_body]); GEN_hidden_variables(statement, false); statement->append_uchar(blr_stall); // Put a label before body of procedure, so that // any exit statement can get out statement->append_uchar(blr_label); statement->append_uchar(0); GEN_statement(statement, stmtNode); if (outputs) statement->req_type = REQ_SELECT_BLOCK; else statement->req_type = REQ_EXEC_BLOCK; statement->append_uchar(blr_end); GEN_return(statement, node->nod_arg[e_exe_blk_outputs], true); statement->append_uchar(blr_end); statement->end_debug(); } // ***************************************** // d e f i n e _ r e l _ c o n s t r a i n t // ***************************************** // Define a constraint, either as part of a create // table or an alter table statement. // static void define_rel_constraint(CompiledStatement* statement, dsql_nod* element) { const dsql_str* string = (dsql_str*) element->nod_arg[e_rct_name]; const char* constraint_name = string ? string->str_data : 0; statement->append_cstring(isc_dyn_rel_constraint, constraint_name); dsql_nod* node = element->nod_arg[e_rct_type]; switch (node->nod_type) { case nod_unique: case nod_primary: make_index(statement, node, node->nod_arg[e_pri_columns], 0, 0, constraint_name); break; case nod_foreign: foreign_key(statement, node, constraint_name); break; case nod_def_constraint: check_constraint(statement, node, false); // false = No delete trigger break; default: // silence compiler break; } } static void define_relation(CompiledStatement* statement) { /************************************** * * d e f i n e _ r e l a t i o n * ************************************** * * Function * Create an SQL table, relying on DYN to generate * global fields for the local fields. * **************************************/ thread_db* tdbb = JRD_get_thread_data(); dsql_nod* ddl_node = statement->req_ddl_node; const dsql_nod* relation_node = ddl_node->nod_arg[e_drl_name]; const dsql_str* relation_name = (dsql_str*) relation_node->nod_arg[e_rln_name]; statement->append_cstring(isc_dyn_def_rel, relation_name->str_data); const dsql_str* external_file = (dsql_str*) ddl_node->nod_arg[e_drl_ext_file]; if (external_file) { statement->append_cstring(isc_dyn_rel_ext_file, external_file->str_data); } save_relation(statement, relation_name); if (external_file) { fb_assert(statement->req_relation); statement->req_relation->rel_flags |= REL_external; } statement->append_number(isc_dyn_rel_sql_protection, 1); switch(ddl_node->nod_flags) { case NOD_GLOBAL_TEMP_TABLE_PRESERVE_ROWS: statement->append_number(isc_dyn_rel_temporary, isc_dyn_rel_temp_global_preserve); break; case NOD_GLOBAL_TEMP_TABLE_DELETE_ROWS: statement->append_number(isc_dyn_rel_temporary, isc_dyn_rel_temp_global_delete); break; } // now do the actual metadata definition dsql_nod* elements = ddl_node->nod_arg[e_drl_elements]; const dsql_nod* pkcols = find_pk_columns(elements); SSHORT position = 0; dsql_nod** ptr = elements->nod_arg; for (const dsql_nod* const* const end = ptr + elements->nod_count; ptr < end; ptr++) { dsql_nod* element = *ptr; switch (element->nod_type) { case nod_def_field: define_field(statement, element, position, relation_name, pkcols); ++position; break; case nod_rel_constraint: define_rel_constraint(statement, element); break; default: break; } } statement->req_relation->rel_flags &= ~REL_creating; statement->append_uchar(isc_dyn_end); } // ****************************** // d e f i n e _ r o l e // ****************************** // Create a SQL role. // static void define_role(CompiledStatement* statement) { const dsql_str* gen_name = (dsql_str*) statement->req_ddl_node->nod_arg[e_gen_name]; statement->append_cstring(isc_dyn_def_sql_role, gen_name->str_data); statement->append_uchar(isc_dyn_end); } static void define_set_null_trg(CompiledStatement* statement, const dsql_nod* element, const dsql_nod* for_columns, const dsql_nod* prim_columns, const char* prim_rel_name, const char* for_rel_name, bool on_upd_trg) { /***************************************************** * * d e f i n e _ s e t _ n u l l _ t r g * ***************************************************** * * Function * define "on delete/update set null" trigger (for referential integrity) * The trigger blr is the same for both the delete and update * cases. Only difference is its TRIGGER_TYPE (ON DELETE or ON UPDATE) * The on_upd_trg parameter == true is an update trigger. * *****************************************************/ if (element->nod_type != nod_foreign) { return; } // count of foreign key columns fb_assert(prim_columns->nod_count == for_columns->nod_count); fb_assert(prim_columns->nod_count != 0); statement->generate_unnamed_trigger_beginning(on_upd_trg, prim_rel_name, prim_columns, for_rel_name, for_columns); USHORT num_fields = 0; const dsql_nod* const* for_key_flds = for_columns->nod_arg; do { const dsql_str* for_key_fld_name_str = (dsql_str*) (*for_key_flds)->nod_arg[1]; statement->append_uchar(blr_assignment); statement->append_uchar(blr_null); statement->append_uchar(blr_field); statement->append_uchar(2); statement->append_cstring(0, for_key_fld_name_str->str_data); num_fields++; for_key_flds++; } while (num_fields < for_columns->nod_count); statement->append_uchar(blr_end); if (on_upd_trg) { statement->append_uchars(blr_end, 3); } statement->end_blr(); // end of the blr statement->append_number(isc_dyn_system_flag, fb_sysflag_referential_constraint); // no trg_source and no trg_description statement->append_uchar(isc_dyn_end); } // // create a shadow for the database // static void define_shadow(CompiledStatement* statement) { const dsql_nod* shadow_node = statement->req_ddl_node; const dsql_nod* const* ptr = shadow_node->nod_arg; if (!ptr[e_shadow_number]) { post_607(Arg::Gds(isc_dsql_shadow_number_err)); } statement->append_number(isc_dyn_def_shadow, (SSHORT)(IPTR) (ptr[e_shadow_number])); statement->append_cstring(isc_dyn_def_file, ((dsql_str*) (ptr[e_shadow_name]))->str_data); statement->append_number(isc_dyn_shadow_man_auto, (SSHORT) ptr[e_shadow_man_auto]->getSlong()); statement->append_number(isc_dyn_shadow_conditional, (SSHORT) ptr[e_shadow_conditional]->getSlong()); statement->append_file_start(0); SLONG length = (IPTR) ptr[e_shadow_length]; statement->append_file_length(length); statement->append_uchar(isc_dyn_end); const dsql_nod* elements = ptr[e_shadow_sec_files]; if (elements) { ptr = elements->nod_arg; for (const dsql_nod* const* const end = ptr + elements->nod_count; ptr < end; ++ptr) { const dsql_nod* element = *ptr; const dsql_fil* file = (dsql_fil*) element->nod_arg[0]; statement->append_cstring(isc_dyn_def_file, file->fil_name->str_data); if (!length && !file->fil_start) { // Preceding file did not specify length, so %s must include starting page number post_607(Arg::Gds(isc_dsql_file_length_err) << Arg::Str(file->fil_name->str_data)); } const SLONG start = file->fil_start; statement->append_file_start(start); length = file->fil_length; statement->append_file_length(length); statement->append_uchar(isc_dyn_end); } } statement->append_uchar(isc_dyn_end); } // // Create the ddl to define or alter a trigger. // static void define_trigger(CompiledStatement* statement, NOD_TYPE op) { thread_db* tdbb = JRD_get_thread_data(); dsql_nod* trigger_node = statement->req_ddl_node; const dsql_str* trigger_name = (dsql_str*) trigger_node->nod_arg[e_trg_name]; USHORT trig_type; dsql_nod* relation_node = NULL; dsql_nod* type_node = trigger_node->nod_arg[e_trg_type]; switch (op) { case nod_replace_trigger: if (METD_get_trigger(statement, trigger_name, NULL, &trig_type)) define_trigger(statement, nod_mod_trigger); else define_trigger(statement, nod_def_trigger); return; case nod_def_trigger: case nod_redef_trigger: fb_assert(trigger_name->str_length <= MAX_USHORT); statement->append_string( isc_dyn_def_trigger, trigger_name->str_data, trigger_name->str_length); relation_node = trigger_node->nod_arg[e_trg_table]; if (relation_node) { if (type_node && (type_node->getSlong() & TRIGGER_TYPE_MASK) != TRIGGER_TYPE_DML) { ERRD_post(Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_incompatible_trigger_type)); } const dsql_str* relation_name = (dsql_str*) relation_node->nod_arg[e_rln_name]; fb_assert(relation_name->str_length <= MAX_USHORT); statement->append_string( isc_dyn_rel_name, relation_name->str_data, relation_name->str_length); } else { if (type_node && (type_node->getSlong() & TRIGGER_TYPE_MASK) != TRIGGER_TYPE_DB) { ERRD_post(Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_incompatible_trigger_type)); } } statement->append_uchar(isc_dyn_sql_object); break; default: // nod_mod_trigger fb_assert(op == nod_mod_trigger); fb_assert(trigger_name->str_length <= MAX_USHORT); statement->append_string( isc_dyn_mod_trigger, trigger_name->str_data, trigger_name->str_length); if (trigger_node->nod_arg[e_trg_actions]) { /* Since we will be updating the body of the trigger, we need to know what relation the trigger relates to. */ dsql_str* relation_name = NULL; bool found = METD_get_trigger(statement, trigger_name, &relation_name, &trig_type); if (found && relation_name) { if (type_node && (type_node->getSlong() & TRIGGER_TYPE_MASK) != TRIGGER_TYPE_DML) { ERRD_post(Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_incompatible_trigger_type)); } relation_node = FB_NEW_RPT(*tdbb->getDefaultPool(), e_rln_count) dsql_nod; trigger_node->nod_arg[e_trg_table] = relation_node; relation_node->nod_type = nod_relation_name; relation_node->nod_count = e_rln_count; // Warning: implicit const cast relation_node->nod_arg[e_rln_name] = (dsql_nod*) relation_name; } else if (found && type_node && (USHORT) type_node->getSlong() != trig_type) { ERRD_post(Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_db_trigger_type_cant_change)); } } } statement->begin_debug(); const dsql_str* source = (dsql_str*) trigger_node->nod_arg[e_trg_source]; dsql_nod* actions = (trigger_node->nod_arg[e_trg_actions]) ? trigger_node->nod_arg[e_trg_actions]->nod_arg[e_trg_act_body] : NULL; if (source && actions) { fb_assert(source->str_length <= MAX_USHORT); ULONG j = find_start_of_body(source); if (j < source->str_length) { statement->append_string( isc_dyn_trg_source, source->str_data + j, source->str_length - j); } } dsql_nod* constant = trigger_node->nod_arg[e_trg_active]; if (constant) statement->append_number(isc_dyn_trg_inactive, (SSHORT) constant->getSlong()); if (constant = trigger_node->nod_arg[e_trg_position]) statement->append_number(isc_dyn_trg_sequence, (SSHORT) constant->getSlong()); if (constant = trigger_node->nod_arg[e_trg_type]) { statement->append_number(isc_dyn_trg_type, (SSHORT) constant->getSlong()); trig_type = (USHORT) constant->getSlong(); } else { fb_assert(op == nod_mod_trigger); } if (actions) { /* 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 */ reset_context_stack(statement); if (relation_node) { dsql_nod* const temp = relation_node->nod_arg[e_rln_alias]; if (hasOldContext(trig_type)) { relation_node->nod_arg[e_rln_alias] = (dsql_nod*) MAKE_cstring(OLD_CONTEXT); dsql_ctx* oldContext = PASS1_make_context(statement, relation_node); oldContext->ctx_flags |= CTX_system; } else { statement->req_context_number++; } if (hasNewContext(trig_type)) { relation_node->nod_arg[e_rln_alias] = (dsql_nod*) MAKE_cstring(NEW_CONTEXT); dsql_ctx* newContext = PASS1_make_context(statement, relation_node); newContext->ctx_flags |= CTX_system; } else { statement->req_context_number++; } relation_node->nod_arg[e_rln_alias] = temp; } // generate the trigger blr statement->begin_blr(isc_dyn_trg_blr); statement->append_uchar(blr_begin); statement->setPsql(true); put_local_variables(statement, trigger_node->nod_arg[e_trg_actions]->nod_arg[e_trg_act_dcls], 0); statement->req_scope_level++; statement->req_loop_level = 0; statement->req_cursor_number = 0; actions = PASS1_statement(statement, actions); // 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. statement->append_uchar(blr_label); statement->append_uchar(0); GEN_hidden_variables(statement, false); GEN_statement(statement, actions); statement->req_scope_level--; statement->append_uchar(blr_end); statement->end_blr(); /* 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->req_type = REQ_DDL; } statement->append_debug_info(); statement->append_uchar(isc_dyn_end); } static void define_udf(CompiledStatement* statement) { /************************************** * * d e f i n e _ u d f * ************************************** * * Function * define a udf to the database. * **************************************/ SSHORT position, blob_position = -1; dsql_nod* udf_node = statement->req_ddl_node; dsql_nod* arguments = udf_node->nod_arg[e_udf_args]; dsql_nod** ptr = udf_node->nod_arg; const char* udf_name = ((dsql_str*) (ptr[e_udf_name]))->str_data; const dsql_str* func_entry_point_name = reinterpret_cast(ptr[e_udf_entry_pt]); const dsql_str* func_module_name = reinterpret_cast(ptr[e_udf_module]); statement->append_cstring(isc_dyn_def_function, udf_name); statement->append_cstring(isc_dyn_func_entry_point, func_entry_point_name->str_data); statement->append_cstring(isc_dyn_func_module_name, func_module_name->str_data); dsql_nod** ret_val_ptr = ptr[e_udf_return_value]->nod_arg; dsql_fld* field = (dsql_fld*) ret_val_ptr[0]; if (field) { // CVC: This is case of "returns [by value|reference]" // Some data types can not be returned as value if (((int) ret_val_ptr[1]->getSlong() == FUN_value) && (field->fld_dtype == dtype_text || field->fld_dtype == dtype_varying || field->fld_dtype == dtype_cstring || field->fld_dtype == dtype_blob || field->fld_dtype == dtype_timestamp)) { // Return mode by value not allowed for this data type post_607(Arg::Gds(isc_return_mode_err)); } /* For functions returning a blob, coerce return argument position to be the last parameter. */ if (field->fld_dtype == dtype_blob) { blob_position = (arguments) ? arguments->nod_count + 1 : 1; if (blob_position > MAX_UDF_ARGUMENTS) { // External functions can not have more than 10 parameters // Or 9 if the function returns a BLOB post_607(Arg::Gds(isc_extern_func_err)); } statement->append_number(isc_dyn_func_return_argument, blob_position); } else { statement->append_number(isc_dyn_func_return_argument, (SSHORT) 0); } position = 0; } else { // CVC: This is case of "returns parameter " position = (SSHORT) ret_val_ptr[1]->getSlong(); // Function modifies an argument whose value is the function return value if (!arguments || position > arguments->nod_count || position < 1) { post_607(Arg::Gds(isc_dsql_udf_return_pos_err) << //gds__extern_func_err, Arg::Num(arguments ? arguments->nod_count : 0)); // CVC: We should devise new msg "position should be between 1 and #params"; // here it is: dsql_udf_return_pos_err // External functions can not have more than 10 parameters // Not strictly correct -- return position error } // We'll verify that SCALAR_ARRAY can't be used as a return type. // The support for SCALAR_ARRAY is only for input parameters. const dsql_nod* ret_arg = arguments->nod_arg[position - 1]; const dsql_nod* const* param_node = ret_arg->nod_arg; if (param_node[e_udf_param_type]) { const SSHORT arg_mechanism = (SSHORT) param_node[e_udf_param_type]->getSlong(); if (arg_mechanism == FUN_scalar_array) post_607(Arg::Gds(isc_random) << Arg::Str("BY SCALAR_ARRAY can't be used as a return parameter")); } statement->append_number(isc_dyn_func_return_argument, position); position = 1; } // Now define all the arguments if (!position) { /* CVC: This is case of "returns [by value|reference]" */ if (field->fld_dtype == dtype_blob) { /* CVC: I need to test returning blobs by descriptor before allowing the change there. For now, I ignore the return type specification. */ const bool free_it = ((SSHORT) ret_val_ptr[1]->getSlong() < 0); statement->append_number(isc_dyn_def_function_arg, blob_position); statement->append_number(isc_dyn_func_mechanism, (SSHORT)(SLONG) ((free_it ? -1 : 1) * FUN_blob_struct)); /* if we have the free_it set then the blob has to be freed on return */ } else { statement->append_number(isc_dyn_def_function_arg, (SSHORT) 0); statement->append_number(isc_dyn_func_mechanism, (SSHORT) ret_val_ptr[1]->getSlong()); } statement->append_cstring(isc_dyn_function_name, udf_name); DDL_resolve_intl_type(statement, field, NULL); put_field(statement, field, true); statement->append_uchar(isc_dyn_end); position = 1; } fb_assert(position == 1); /* CVC: This for all params, including the case of "returns parameter " */ if (arguments) { ptr = arguments->nod_arg; for (const dsql_nod* const* const end = ptr + arguments->nod_count; ptr < end; ptr++, position++) { if (position > MAX_UDF_ARGUMENTS) { // External functions can not have more than 10 parameters post_607(Arg::Gds(isc_extern_func_err)); } /*field = (dsql_fld*) *ptr; */ dsql_nod** param_node = (*ptr)->nod_arg; field = (dsql_fld*) param_node[e_udf_param_field]; statement->append_number(isc_dyn_def_function_arg, position); if (param_node[e_udf_param_type]) { const SSHORT arg_mechanism = (SSHORT) param_node[e_udf_param_type]->getSlong(); statement->append_number(isc_dyn_func_mechanism, arg_mechanism); } else if (field->fld_dtype == dtype_blob) { statement->append_number(isc_dyn_func_mechanism, (SSHORT) FUN_blob_struct); } else { statement->append_number(isc_dyn_func_mechanism, (SSHORT) FUN_reference); } statement->append_cstring(isc_dyn_function_name, udf_name); DDL_resolve_intl_type(statement, field, NULL); put_field(statement, field, true); statement->append_uchar(isc_dyn_end); } } statement->append_uchar(isc_dyn_end); } static void define_update_action(CompiledStatement* statement, dsql_nod** base_and_node, dsql_nod** base_relation, dsql_nod* items) { /************************************** * * d e f i n e _ u p d a t e _ a c t i o n * ************************************** * * Function * Define an action statement which, given a view * definition, will map an update to a record from * a view of a single relation into the * base relation. * **************************************/ dsql_nod* ddl_node = statement->req_ddl_node; // check whether this is an updatable view definition dsql_nod* select_node = NULL; dsql_nod* select_expr = NULL; dsql_nod* from_list = NULL; if ((ddl_node->nod_type != nod_def_view && ddl_node->nod_type != nod_redef_view && ddl_node->nod_type != nod_replace_view && ddl_node->nod_type != nod_mod_view) || !(select_node = ddl_node->nod_arg[e_view_select]) || !(select_expr = select_node->nod_arg[e_sel_query_spec]) || !(from_list = select_expr->nod_arg[e_qry_from]) || from_list->nod_count != 1) { // The caller seems throwing proper errors for all the above conditions. // But just in case it doesn't, here we have the final attempt to prevent the bad things. fb_assert(false); } // use the relation referenced in the select statement for rse dsql_nod* relation_node = MAKE_node(nod_relation_name, (int) e_rln_count); relation_node->nod_arg[e_rln_name] = from_list->nod_arg[0]->nod_arg[e_rln_name]; relation_node->nod_arg[e_rln_alias] = (dsql_nod*) MAKE_cstring(TEMP_CONTEXT); *base_relation = relation_node; /* get the list of values and fields to compare to -- if there is no list of fields, get all fields in the base relation that are not computed */ dsql_nod* values_node = ddl_node->nod_arg[e_view_fields]; dsql_nod* fields_node = select_expr->nod_arg[e_qry_list]; if (!fields_node) { const dsql_str* rel_name = reinterpret_cast(relation_node->nod_arg[e_rln_name]); const dsql_rel* relation = METD_get_relation(statement, rel_name); DsqlNodStack field_stack; for (const dsql_fld* field = relation->rel_fields; field; field = field->fld_next) { if (field->fld_flags & FLD_computed) continue; field_stack.push(MAKE_field_name(field->fld_name.c_str())); } fields_node = MAKE_list(field_stack); } if (!values_node) values_node = fields_node; // generate the list of assignments to fields in the base relation dsql_nod** ptr = fields_node->nod_arg; const dsql_nod* const* const end = ptr + fields_node->nod_count; dsql_nod** ptr2 = values_node->nod_arg; const dsql_nod* const* const end2 = ptr2 + values_node->nod_count; dsql_nod* and_node = MAKE_node(nod_and, (int) 2); int and_arg = 0; for (; (ptr < end) && (ptr2 < end2); ptr++, ptr2++) { dsql_nod* field_node = *ptr; if (field_node->nod_type == nod_alias) field_node = field_node->nod_arg[e_alias_value]; // generate the actual comparisons if (field_node->nod_type == nod_field_name) { field_node->nod_arg[e_fln_context] = (dsql_nod*) MAKE_cstring(TEMP_CONTEXT); // CVC: This code serves no purpose. //dsql_nod* value_node = MAKE_node(nod_field_name, (int) e_fln_count); //value_node->nod_arg[e_fln_name] = (*ptr2)->nod_arg[e_fln_name]; //value_node->nod_arg[e_fln_context] = // (dsql_nod*) MAKE_cstring(NEW_CONTEXT); dsql_nod* old_value_node = MAKE_node(nod_field_name, (int) e_fln_count); old_value_node->nod_arg[e_fln_name] = (*ptr2)->nod_arg[e_fln_name]; old_value_node->nod_arg[e_fln_context] = (dsql_nod*) MAKE_cstring(OLD_CONTEXT); dsql_nod* eql_node = MAKE_node(nod_eql, (int) 2); eql_node->nod_arg[0] = old_value_node; eql_node->nod_arg[1] = field_node; dsql_nod* anull_node = MAKE_node(nod_missing, 1); anull_node->nod_arg[0] = old_value_node; dsql_nod* bnull_node = MAKE_node(nod_missing, 1); bnull_node->nod_arg[0] = field_node; dsql_nod* iand_node = MAKE_node(nod_and, (int) 2); iand_node->nod_arg[0] = anull_node; iand_node->nod_arg[1] = bnull_node; dsql_nod* or_node = MAKE_node(nod_or, (int) 2); or_node->nod_arg[0] = eql_node; or_node->nod_arg[1] = iand_node; if (and_arg <= 1) and_node->nod_arg[and_arg++] = or_node; else { dsql_nod* old_and = and_node; and_node = MAKE_node(nod_and, (int) 2); and_node->nod_arg[0] = old_and; and_node->nod_arg[1] = or_node; } } } if (and_arg <= 1) and_node->nod_arg[and_arg] = replace_field_names(select_expr->nod_arg[e_qry_where], items, NULL, false, TEMP_CONTEXT); else { dsql_nod* old_and = and_node; and_node = MAKE_node(nod_and, (int) 2); and_node->nod_arg[0] = old_and; and_node->nod_arg[1] = replace_field_names(select_expr->nod_arg[e_qry_where], items, NULL, false, TEMP_CONTEXT); } *base_and_node = and_node; } static void define_upd_cascade_trg( CompiledStatement* statement, const dsql_nod* element, const dsql_nod* for_columns, const dsql_nod* prim_columns, const char* prim_rel_name, const char* for_rel_name) { /***************************************************** * * d e f i n e _ u p d _ c a s c a d e _ t r g * ***************************************************** * * Function * define "on update cascade" trigger (for referential integrity) * along with the trigger blr. * *****************************************************/ if (element->nod_type != nod_foreign) { return; } // count of foreign key columns fb_assert(prim_columns->nod_count == for_columns->nod_count); fb_assert(prim_columns->nod_count != 0); statement->generate_unnamed_trigger_beginning(true, prim_rel_name, prim_columns, for_rel_name, for_columns); USHORT num_fields = 0; const dsql_nod* const* for_key_flds = for_columns->nod_arg; const dsql_nod* const* prim_key_flds = prim_columns->nod_arg; do { const dsql_str* for_key_fld_name_str = (dsql_str*) (*for_key_flds)->nod_arg[1]; const dsql_str* prim_key_fld_name_str = (dsql_str*) (*prim_key_flds)->nod_arg[1]; statement->append_uchar(blr_assignment); statement->append_uchar(blr_field); statement->append_uchar(1); statement->append_cstring(0, prim_key_fld_name_str->str_data); statement->append_uchar(blr_field); statement->append_uchar(2); statement->append_cstring(0, for_key_fld_name_str->str_data); num_fields++; prim_key_flds++; for_key_flds++; } while (num_fields < for_columns->nod_count); statement->append_uchars(blr_end, 4); statement->end_blr(); // end of the blr statement->append_number(isc_dyn_system_flag, fb_sysflag_referential_constraint); // no trg_source and no trg_description statement->append_uchar(isc_dyn_end); } static void define_view(CompiledStatement* statement, NOD_TYPE op) { /************************************** * * d e f i n e _ v i e w * ************************************** * * Function * Create the ddl to define a view, using a SELECT * statement as the source of the view. * **************************************/ thread_db* tdbb = JRD_get_thread_data(); dsql_nod* node = statement->req_ddl_node; const dsql_str* view_name = (dsql_str*) node->nod_arg[e_view_name]; const dsql_rel* view_relation = NULL; switch (op) { case nod_replace_view: if (METD_get_relation(statement, view_name)) define_view(statement, nod_mod_view); else define_view(statement, nod_def_view); return; case nod_def_view: case nod_redef_view: statement->append_cstring(isc_dyn_def_view, view_name->str_data); statement->append_number(isc_dyn_rel_sql_protection, 1); save_relation(statement, view_name); break; default: // op == nod_mod_view statement->append_cstring(isc_dyn_mod_view, view_name->str_data); view_relation = METD_get_relation(statement, view_name); if (!view_relation) { post_607(Arg::Gds(isc_dsql_view_not_found) << Arg::Str(view_name->str_data)); } } // compile the SELECT statement into a record selection expression, // making sure to bump the context number since view contexts start // at 1 (except for computed fields) -- note that calling PASS1_rse // directly rather than PASS1_statement saves the context stack reset_context_stack(statement); statement->req_context_number++; dsql_nod* select_expr = node->nod_arg[e_view_select]; select_expr->nod_flags |= NOD_SELECT_VIEW_FIELDS; dsql_nod* rse = PASS1_rse(statement, select_expr, NULL); // store the blr and source string for the view definition statement->begin_blr(isc_dyn_view_blr); // ASF: Call GEN_hidden_variables could be a optimization for views to not have // blr_dcl_variables inside RSE loops, but this is currently not possible because it will // mix the variables from view fields and view body. GEN_expr(statement, rse); statement->end_blr(); // Store source for view. gdef -e cannot cope with it. // We need to add something to rdb$views to indicate source type. // Source will be for documentation purposes. const dsql_str* source = (dsql_str*) node->nod_arg[e_view_source]; fb_assert(source->str_length <= MAX_USHORT); statement->append_string(isc_dyn_view_source, source->str_data, source->str_length); // define the view source relations from the statement contexts & union contexts while (statement->req_dt_context.hasData()) { statement->req_context->push(statement->req_dt_context.pop()); } while (statement->req_union_context.hasData()) { statement->req_context->push(statement->req_union_context.pop()); } for (DsqlContextStack::iterator temp(*statement->req_context); temp.hasData(); ++temp) { const dsql_ctx* context = temp.object(); const dsql_rel* relation = context->ctx_relation; const dsql_prc* procedure = context->ctx_procedure; if (relation || procedure) { // ASF: Check disabled as seems to not be reason to prevent // procedure usage in view. 2007-11-28 /* if (procedure) { // Disallow procedure-based views ERRD_post(Arg::Gds(isc_wish_list)); } */ const MetaName& name = relation ? relation->rel_name : procedure->prc_name; statement->append_string(isc_dyn_view_relation, name); statement->append_number(isc_dyn_view_context, context->ctx_context); const char* str = context->ctx_alias ? context->ctx_alias : name.c_str(); const USHORT len = context->ctx_alias ? strlen(str) : name.length(); statement->append_string(isc_dyn_view_context_name, str, len); statement->append_uchar(isc_dyn_end); } } // if there are field names defined for the view, match them in order // with the items from the SELECT. Otherwise use all the fields from // the rse node that was created from the select expression const dsql_nod* const* ptr = NULL; const dsql_nod* const* end = NULL; const dsql_nod* view_fields = node->nod_arg[e_view_fields]; if (view_fields != NULL) { ptr = view_fields->nod_arg; end = ptr + view_fields->nod_count; } const TEXT* field_string; bool updatable = true; SSHORT position = 0; // go through the fields list, defining or modifying the local fields; // if an expression is specified rather than a field, define // a global field for the computed value as well dsql_nod* items = rse->nod_arg[e_rse_items]; dsql_nod** i_ptr = items->nod_arg; SortedArray modified_fields; for (const dsql_nod* const* const i_end = i_ptr + items->nod_count; i_ptr < i_end; i_ptr++, position++) { dsql_nod* field_node = *i_ptr; const dsql_str* alias_name = NULL; if (field_node->nod_type == nod_alias) { alias_name = (dsql_str*) field_node->nod_arg[e_alias_alias]; field_node = field_node->nod_arg[e_alias_value]; } // check if this is a field or an expression const dsql_fld* field = NULL; const dsql_ctx* context = NULL; if (field_node->nod_type == nod_field) { field = (dsql_fld*) field_node->nod_arg[e_fld_field]; context = (dsql_ctx*) field_node->nod_arg[e_fld_context]; } else updatable = false; // determine the proper field name, replacing the default if necessary if (alias_name) { field_string = (TEXT*) alias_name->str_data; } else if (field) { field_string = field->fld_name.c_str(); } else { field_string = NULL; } // if this is an expression, check to make sure there is a name specified if (!ptr && !field_string) { // must specify field name for view select expression post_607(Arg::Gds(isc_specify_field_err)); } // CVC: Small modification here to catch any mismatch between number of // explicit field names in a view and number of fields in the select expression, // see comment below. This closes Firebird Bug #223059. if (ptr) { if (ptr < end) { const dsql_str* field_name = (dsql_str*) (*ptr)->nod_arg[1]; field_string = (TEXT*) field_name->str_data; } ptr++; } // if not an expression, point to the proper base relation field, // else make up an SQL field with generated global field for calculations dsql_fld* rel_field = NULL; if (view_relation) // if we're modifying a view { for (rel_field = view_relation->rel_fields; rel_field; rel_field = rel_field->fld_next) { if (rel_field->fld_name == field_string) { if (modified_fields.exist(rel_field)) { // column @1 appears more than once in ALTER VIEW ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_col_more_than_once_view) << Arg::Str(field_string)); } modified_fields.add(rel_field); break; } } } if (field) { if (rel_field) // modifying a view { statement->append_cstring(isc_dyn_mod_sql_fld, field_string); statement->append_uchar(isc_dyn_del_computed); } else statement->append_cstring(isc_dyn_def_local_fld, field_string); statement->append_string(isc_dyn_fld_base_fld, field->fld_name); if (field->fld_dtype <= dtype_any_text) { statement->append_number(isc_dyn_fld_collation, field->fld_collation_id); } statement->append_number(isc_dyn_view_context, context->ctx_context); } else { if (rel_field) // modifying a view { statement->append_cstring(isc_dyn_mod_sql_fld, field_string); statement->append_cstring(isc_dyn_fld_base_fld, ""); } else statement->append_cstring(isc_dyn_def_sql_fld, field_string); MAKE_desc(statement, &field_node->nod_desc, field_node, NULL); put_descriptor(statement, &field_node->nod_desc); statement->begin_blr(isc_dyn_fld_computed_blr); GEN_expr(statement, field_node); statement->end_blr(); statement->append_number(isc_dyn_view_context, (SSHORT) 0); } if (field_string) save_field(statement, field_string); statement->append_number(isc_dyn_fld_position, position); statement->append_uchar(isc_dyn_end); } // CVC: This message was not catching the case when // #fields < items in select list, see comment above. if (ptr != end) { // number of fields does not match select list post_607(Arg::Gds(isc_num_field_err)); } if (view_relation) // modifying a view { // delete the old fields not present in the new definition for (dsql_fld* rel_field = view_relation->rel_fields; rel_field; rel_field = rel_field->fld_next) { if (!modified_fields.exist(rel_field)) { statement->append_string(isc_dyn_delete_local_fld, rel_field->fld_name); statement->append_uchar(isc_dyn_end); } } } // setup to define triggers for WITH CHECK OPTION dsql_nod* check = node->nod_arg[e_view_check]; if (check) { if (!updatable) { // Only simple column names permitted for VIEW WITH CHECK OPTION post_607(Arg::Gds(isc_col_name_err)); } select_expr = select_expr->nod_arg[e_sel_query_spec]; if (select_expr->nod_type == nod_list) { // Only one table allowed for VIEW WITH CHECK OPTION post_607(Arg::Gds(isc_table_view_err)); } if (select_expr->nod_arg[e_qry_from]->nod_count != 1) { // Only one table allowed for VIEW WITH CHECK OPTION post_607(Arg::Gds(isc_table_view_err)); } if (!select_expr->nod_arg[e_qry_where]) { // No where clause for VIEW WITH CHECK OPTION post_607(Arg::Gds(isc_where_err)); } if (select_expr->nod_arg[e_qry_distinct] || select_expr->nod_arg[e_qry_group] || select_expr->nod_arg[e_qry_having]) { // DISTINCT, GROUP or HAVING not permitted for VIEW WITH CHECK OPTION post_607(Arg::Gds(isc_distinct_err)); } dsql_nod* relation_node = MAKE_node(nod_relation_name, e_rln_count); // Warning: implicit const_cast relation_node->nod_arg[e_rln_name] = (dsql_nod*) view_name; check->nod_arg[e_cnstr_table] = relation_node; check->nod_arg[e_cnstr_source] = (dsql_nod*) source; // the condition for the trigger is the converse of the selection // criteria for the view, suitably fixed up so that the fields in // the view are referenced check->nod_arg[e_cnstr_condition] = select_expr->nod_arg[e_qry_where]; // Define the triggers create_view_triggers(statement, check, rse->nod_arg[e_rse_items]); } statement->append_uchar(isc_dyn_end); reset_context_stack(statement); } static void define_view_trigger(CompiledStatement* statement, dsql_nod* node, dsql_nod* rse, dsql_nod* items) { // The fields in VIEW actually /************************************** * * d e f i n e _ v i e w _ t r i g g e r * ************************************** * * Function * Create the ddl to define a trigger for a VIEW WITH CHECK OPTION. * **************************************/ thread_db* tdbb = JRD_get_thread_data(); dsql_nod* const saved_ddl_node = statement->req_ddl_node; dsql_nod* select_expr = saved_ddl_node->nod_arg[e_view_select]; select_expr = select_expr->nod_arg[e_sel_query_spec]; dsql_nod* view_fields = saved_ddl_node->nod_arg[e_view_fields]; /* make the "define trigger" node the current statement ddl node so that generating of BLR will be appropriate for trigger */ statement->req_ddl_node = node; dsql_nod* relation_node = NULL; if (node->nod_type == nod_def_constraint) { statement->append_string(isc_dyn_def_trigger, "", 0); relation_node = node->nod_arg[e_cnstr_table]; const dsql_str* relation_name = (dsql_str*) relation_node->nod_arg[e_rln_name]; fb_assert(relation_name->str_length <= MAX_USHORT); statement->append_string( isc_dyn_rel_name, relation_name->str_data, relation_name->str_length); } else { return; } statement->append_number(isc_dyn_trg_sequence, 0); const dsql_nod* constant = node->nod_arg[e_cnstr_type]; USHORT trig_type; if (constant) { trig_type = (USHORT) constant->getSlong(); statement->append_number(isc_dyn_trg_type, trig_type); } else { /* If we don't have a trigger type assigned, then this is just a template definition for use with domains. The real triggers are defined when the domain is used. */ trig_type = 0; } statement->append_uchar(isc_dyn_sql_object); // generate the trigger blr if (node->nod_arg[e_cnstr_condition] && node->nod_arg[e_cnstr_actions]) { statement->begin_blr(isc_dyn_trg_blr); statement->append_uchar(blr_begin); /* create the "OLD" and "NEW" contexts for the trigger -- the new one could be a dummy place holder to avoid resolving fields to that context but prevent relations referenced in the trigger actions from referencing the predefined "1" context */ dsql_ctx* sav_context = 0; dsql_ctx* context = 0; if (statement->req_context_number) { /* If an alias is specified for the single base table involved, save and then add the context */ context = statement->req_context->object(); if (context->ctx_alias) { MemoryPool& pool = *tdbb->getDefaultPool(); sav_context = FB_NEW(pool) dsql_ctx(pool); *sav_context = *context; } } reset_context_stack(statement); dsql_nod* temp_alias = relation_node->nod_arg[e_rln_alias]; relation_node->nod_arg[e_rln_alias] = (dsql_nod*) MAKE_cstring(OLD_CONTEXT); dsql_ctx* oldContext = PASS1_make_context(statement, relation_node); oldContext->ctx_flags |= CTX_system; relation_node->nod_arg[e_rln_alias] = (dsql_nod*) MAKE_cstring(NEW_CONTEXT); dsql_ctx* newContext = PASS1_make_context(statement, relation_node); newContext->ctx_flags |= CTX_system; relation_node->nod_arg[e_rln_alias] = temp_alias; if (sav_context) { sav_context->ctx_context = statement->req_context_number++; context->ctx_scope_level = statement->req_scope_level; statement->req_context->push(sav_context); } // generate the condition for firing the trigger dsql_nod* condition; if (trig_type == PRE_MODIFY_TRIGGER) { statement->append_uchar(blr_for); dsql_nod* temp = rse->nod_arg[e_rse_streams]; temp->nod_arg[0] = PASS1_node(statement, temp->nod_arg[0]); temp = rse->nod_arg[e_rse_boolean]; rse->nod_arg[e_rse_boolean] = PASS1_node(statement, temp); GEN_expr(statement, rse); condition = replace_field_names(select_expr->nod_arg[e_qry_where], items, view_fields, false, NEW_CONTEXT); } else if (trig_type == PRE_STORE_TRIGGER) { condition = replace_field_names(select_expr->nod_arg[e_qry_where], items, view_fields, true, NEW_CONTEXT); } else { fb_assert(false); } statement->append_uchar(blr_if); GEN_expr(statement, PASS1_node(statement, condition)); statement->append_uchar(blr_begin); statement->append_uchar(blr_end); // generate the action statements for the trigger dsql_nod* actions = node->nod_arg[e_cnstr_actions]; dsql_nod** ptr = actions->nod_arg; for (const dsql_nod* const* const end = ptr + actions->nod_count; ptr < end; ptr++) { GEN_statement(statement, PASS1_statement(statement, *ptr)); } statement->append_uchar(blr_end); // of begin statement->end_blr(); } statement->append_number(isc_dyn_system_flag, fb_sysflag_view_check); statement->append_uchar(isc_dyn_end); // 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->req_type = REQ_DDL; statement->req_ddl_node = saved_ddl_node; reset_context_stack(statement); } static void delete_collation(CompiledStatement* statement) { /************************************** * * d e l e t e _ c o l l a t i o n * ************************************** * * Function * Delete a collation. * **************************************/ const dsql_str* coll_name = (dsql_str*) statement->req_ddl_node->nod_arg[e_del_coll_name]; statement->append_cstring(isc_dyn_del_collation, coll_name->str_data); statement->append_uchar(isc_dyn_end); } static void delete_exception (CompiledStatement* statement, dsql_nod* node, bool silent_deletion) { /************************************** * * d e l e t e _ e x c e p t i o n * ************************************** * * Function * Do nothing and don't throw error if the exception doesn't exist * and silent_deletion is true. * **************************************/ const dsql_str* string = (dsql_str*) node->nod_arg[0]; fb_assert(string); if (node->nod_type == nod_redef_exception || silent_deletion) { if (!METD_get_exception(statement, string)) { return; } } statement->append_cstring(isc_dyn_del_exception, string->str_data); statement->append_uchar(isc_dyn_end); } static void delete_procedure (CompiledStatement* statement, dsql_nod* node, bool silent_deletion) { /************************************** * * d e l e t e _ p r o c e d u r e * ************************************** * * Function * Do nothing and don't throw error if the procedure doesn't exist * and silent_deletion is true. * CVC: Created this function to not clutter generate_dyn(). * **************************************/ const dsql_str* string = (dsql_str*) node->nod_arg[e_prc_name]; fb_assert (string); if (node->nod_type == nod_redef_procedure || silent_deletion) { dsql_prc* procedure = METD_get_procedure (statement, string); if (!procedure) { return; } } statement->append_cstring(isc_dyn_delete_procedure, string->str_data); statement->append_uchar(isc_dyn_end); } static void delete_relation_view (CompiledStatement* statement, dsql_nod* node, bool silent_deletion) { /************************************** * * d e l e t e _ r e l a t i o n _ v i e w * ************************************** * * Function * Check that DROP TABLE is dropping a table and that * DROP VIEW is dropping a view. * Do nothing and don't throw error if the table or view doesn't exist * and silent_deletion is true. * CVC: Created this function to not clutter generate_dyn(). * **************************************/ const dsql_str* string = 0; if (node->nod_type == nod_redef_relation) { dsql_nod* relation_node = node->nod_arg[e_alt_name]; fb_assert (relation_node); string = (dsql_str*) relation_node->nod_arg[e_rln_name]; } else { string = (dsql_str*) node->nod_arg[e_alt_name]; } fb_assert (string); const dsql_rel* relation = METD_get_relation (statement, string); if (node->nod_type == nod_del_relation || node->nod_type == nod_redef_relation) { if (!relation && !silent_deletion || relation && (relation->rel_flags & REL_view)) { post_607(Arg::Gds(isc_dsql_table_not_found) << Arg::Str(string->str_data)); } } else { /* node->nod_type == nod_del_view, nod_redef_view */ if (!relation && !silent_deletion || relation && !(relation->rel_flags & REL_view)) { post_607(Arg::Gds(isc_dsql_view_not_found) << Arg::Str(string->str_data)); } } if (relation) { statement->append_cstring(isc_dyn_delete_rel, string->str_data); statement->append_uchar(isc_dyn_end); } } static void delete_trigger(CompiledStatement* statement, dsql_nod* node, bool silent_deletion) { /************************************** * * d e l e t e _ t r i g g e r * ************************************** * * Function * Do nothing and don't throw error if the trigger doesn't exist * and silent_deletion is true. * **************************************/ const dsql_str* string = (dsql_str*) node->nod_arg[e_trg_name]; fb_assert(string); if (silent_deletion) { USHORT trig_type; if (!METD_get_trigger(statement, string, NULL, &trig_type)) return; } statement->append_cstring(isc_dyn_delete_trigger, string->str_data); statement->append_uchar(isc_dyn_end); } // f i n d _ p k _ c o l u m n s // // @brief Starting from the elements in a table definition, locate the PK columns // if given in a separate table constraint declaration. // const dsql_nod* find_pk_columns(const dsql_nod* def_rel_elements) { for (int i = 0; i < def_rel_elements->nod_count; ++i) { const dsql_nod* element = def_rel_elements->nod_arg[i]; if (element->nod_type == nod_rel_constraint) { const dsql_nod* node = element->nod_arg[e_rct_type]; if (node->nod_type == nod_primary) return node->nod_arg[e_pri_columns]; } } return 0; } // f i n d _ s t a r t _ o f _ b o d y // // @brief Find the start of a procedure body. Empty lines are irrelevant. // @param string the source string to be searched. // static ULONG find_start_of_body(const dsql_str* string) { ULONG j = 0; for (ULONG i = 0; i < string->str_length; ++i) { switch (string->str_data[i]) { case '\n': j = i + 1; break; case ' ': case '\r': case '\t': break; default: return j; } } return 0; // Something suspicious happened, better return zero than str_length. } /** f i x _ d e f a u l t _ s o u r c e @brief Get rid of newlines between DEFAULT and the value. @param string the source text to be fixed if necessary. **/ static void fix_default_source(dsql_str* string) { // CVC: I know this is not very brilliant, but some people are annoyed // at this for years. // We assume the first position is used by "default" #ifdef DEV_BUILD // Verify that assumption about "default" const char* token = "default\0DEFAULT"; for (int t = 0; t < 7; ++t) { const char c = string->str_data[t]; if (c != token[t] && c != token[t + 8]) return; // something is screwed, skip town. } #endif for (ULONG i = 7; i < string->str_length; ++i) { switch (string->str_data[i]) { case ' ': case '\n': case '\r': case '\t': string->str_data[i] = ' '; break; default: return; } } } static void foreign_key(CompiledStatement* statement, dsql_nod* element, const char* index_name) { /************************************** * * f o r e i g n _ k e y * ************************************** * * Function * Generate ddl to create a foreign key * constraint. * **************************************/ dsql_nod* columns1 = element->nod_arg[e_for_columns]; dsql_nod* relation2_node = element->nod_arg[e_for_reftable]; const dsql_str* relation2 = (dsql_str*) relation2_node->nod_arg[e_rln_name]; /* If there is a referenced table name but no referenced field names, the primary key of the referenced table designates the referenced fields. */ dsql_nod* columns2 = element->nod_arg[e_for_refcolumns]; if (!columns2) { element->nod_arg[e_for_refcolumns] = columns2 = METD_get_primary_key(statement, relation2); /* If there is NEITHER an explicitly referenced field name, NOR does the referenced table have a primary key to serve as the implicitly referenced field, fail. */ if (!columns2) { // "REFERENCES table" without "(column)" requires PRIMARY // KEY on referenced table post_607(Arg::Gds(isc_reftable_requires_pk)); } } if (columns2 && (columns1->nod_count != columns2->nod_count)) { // foreign key field count does not match primary key post_607(Arg::Gds(isc_key_field_count_err)); } /* define the foreign key index and the triggers that may be needed for referential integrity action. */ make_index_trg_ref_int(statement, element, columns1, element->nod_arg[e_for_refcolumns], relation2->str_data, index_name); } static void generate_dyn(CompiledStatement* statement, dsql_nod* node) { /************************************** * * g e n e r a t e _ d y n * ************************************** * * Functional description * Switch off the type of node to generate a * DYN string. * **************************************/ const dsql_str* string; statement->req_ddl_node = node; switch (node->nod_type) { case nod_def_domain: define_domain(statement); break; case nod_mod_domain: modify_domain(statement); break; case nod_def_index: define_index(statement); break; case nod_def_relation: define_relation(statement); break; case nod_redef_relation: stuff(statement, isc_dyn_begin); delete_relation_view(statement, node, true); // silent. define_relation (statement); stuff(statement, isc_dyn_end); break; case nod_mod_relation: modify_relation(statement); break; case nod_def_view: case nod_mod_view: case nod_replace_view: define_view(statement, node->nod_type); break; case nod_redef_view: stuff(statement, isc_dyn_begin); delete_relation_view(statement, node, true); // silent. define_view(statement, node->nod_type); stuff(statement, isc_dyn_end); break; case nod_def_exception: case nod_mod_exception: case nod_replace_exception: define_exception(statement, node->nod_type); break; case nod_redef_exception: stuff(statement, isc_dyn_begin); delete_exception(statement, node, true); // silent define_exception(statement, node->nod_type); stuff(statement, isc_dyn_end); break; case nod_del_exception: delete_exception(statement, node, false); // no silent break; case nod_def_procedure: case nod_mod_procedure: case nod_replace_procedure: define_procedure(statement, node->nod_type); break; case nod_redef_procedure: stuff(statement, isc_dyn_begin); delete_procedure(statement, node, true); // silent. define_procedure(statement, node->nod_type); stuff(statement, isc_dyn_end); break; case nod_def_constraint: define_constraint_trigger(statement, node); break; case nod_def_trigger: case nod_mod_trigger: case nod_replace_trigger: define_trigger(statement, node->nod_type); break; case nod_redef_trigger: stuff(statement, isc_dyn_begin); delete_trigger(statement, node, true); // silent define_trigger(statement, node->nod_type); stuff(statement, isc_dyn_end); break; case nod_del_domain: string = (dsql_str*) node->nod_arg[0]; statement->append_cstring(isc_dyn_delete_global_fld, string->str_data); statement->append_uchar(isc_dyn_end); break; case nod_del_index: string = (dsql_str*) node->nod_arg[0]; statement->append_cstring(isc_dyn_delete_idx, string->str_data); statement->append_uchar(isc_dyn_end); break; // CVC: Handling drop table and drop view properly. case nod_del_relation: case nod_del_view: delete_relation_view (statement, node, false); // no silent. break; case nod_del_procedure: delete_procedure(statement, node, false); // no silent. break; case nod_del_trigger: delete_trigger(statement, node, false); // no silent break; case nod_del_role: string = (dsql_str*) node->nod_arg[0]; statement->append_cstring(isc_dyn_del_sql_role, string->str_data); statement->append_uchar(isc_dyn_end); break; case nod_grant: case nod_revoke: grant_revoke(statement); break; case nod_def_generator: define_generator(statement); break; case nod_def_role: define_role(statement); break; case nod_def_filter: define_filter(statement); break; case nod_del_generator: string = (dsql_str*) node->nod_arg[0]; statement->append_cstring(isc_dyn_delete_generator, string->str_data); statement->append_uchar(isc_dyn_end); break; case nod_del_filter: string = (dsql_str*) node->nod_arg[0]; statement->append_cstring(isc_dyn_delete_filter, string->str_data); statement->append_uchar(isc_dyn_end); break; case nod_def_udf: define_udf(statement); break; case nod_del_udf: string = (dsql_str*) node->nod_arg[0]; statement->append_cstring(isc_dyn_delete_function, string->str_data); statement->append_uchar(isc_dyn_end); break; case nod_def_shadow: define_shadow(statement); break; case nod_del_shadow: statement->append_number(isc_dyn_delete_shadow, (SSHORT) (IPTR) node->nod_arg[0]); statement->append_uchar(isc_dyn_end); break; case nod_mod_database: modify_database(statement); break; case nod_def_database: define_database(statement); break; case nod_mod_index: modify_index(statement); break; case nod_set_statistics: set_statistics(statement); break; case nod_comment: make_comment(statement); break; case nod_mod_udf: modify_udf(statement); break; case nod_mod_role: modify_map(statement); break; case nod_def_collation: define_collation(statement); break; case nod_del_collation: delete_collation(statement); break; case nod_add_user: define_user(statement, isc_dyn_user_add); break; case nod_mod_user: define_user(statement, isc_dyn_user_mod); break; case nod_del_user: define_user(statement, isc_dyn_user_del); break; default: // CVC: Shouldn't we complain here? break; } } static void grant_revoke(CompiledStatement* statement) { /************************************** * * g r a n t _ r e v o k e * ************************************** * * Functional description * Build DYN string for GRANT/REVOKE statements * **************************************/ const dsql_nod* const* uptr; const dsql_nod* const* uend; SSHORT option = 0; // no grant/admin option dsql_nod* ddl_node = statement->req_ddl_node; const dsql_nod* privs = ddl_node->nod_arg[e_grant_privs]; const dsql_nod* table = ddl_node->nod_arg[e_grant_table]; if ((ddl_node->nod_type == nod_revoke) && !privs && !table) // ALL ON ALL { statement->append_uchar(isc_dyn_begin); const dsql_nod* users = ddl_node->nod_arg[e_grant_users]; uend = users->nod_arg + users->nod_count; for (uptr = users->nod_arg; uptr < uend; ++uptr) { statement->append_uchar(isc_dyn_revoke_all); put_user_grant(statement, *uptr); statement->append_uchar(isc_dyn_end); } statement->append_uchar(isc_dyn_end); return; } bool process_grant_role = false; if (privs->nod_arg[0] != NULL) { if (privs->nod_arg[0]->nod_type == nod_role_name) { process_grant_role = true; } } statement->append_uchar(isc_dyn_begin); if (!process_grant_role) { const dsql_nod* users = ddl_node->nod_arg[e_grant_users]; if (ddl_node->nod_arg[e_grant_grant]) { option = 1; // with grant option } uend = users->nod_arg + users->nod_count; for (uptr = users->nod_arg; uptr < uend; ++uptr) { modify_privileges(statement, ddl_node->nod_type, option, privs, table, *uptr, ddl_node->nod_arg[e_grant_grantor]); } } else { const dsql_nod* role_list = ddl_node->nod_arg[0]; const dsql_nod* users = ddl_node->nod_arg[1]; if (ddl_node->nod_arg[3]) { option = 2; // with admin option } const dsql_nod* const* role_end = role_list->nod_arg + role_list->nod_count; for (const dsql_nod* const* role_ptr = role_list->nod_arg; role_ptr < role_end; ++role_ptr) { uend = users->nod_arg + users->nod_count; for (uptr = users->nod_arg; uptr < uend; ++uptr) { process_role_nm_list(statement, option, *uptr, *role_ptr, ddl_node->nod_type, ddl_node->nod_arg[e_grant_grantor]); } } } statement->append_uchar(isc_dyn_end); } // *********************** // m a k e _ c o m m e n t // *********************** // Set the description blob for objects' self documentation. // This query // 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. static void make_comment(CompiledStatement* statement) { const dsql_nod* node = statement->req_ddl_node; fb_assert(node->nod_type == nod_comment); const bool have_subobj = node->nod_arg[e_comment_part] != 0; const dsql_nod* obj_type_node = node->nod_arg[e_comment_obj_type]; fb_assert(obj_type_node->nod_type == nod_constant && obj_type_node->nod_desc.dsc_dtype == dtype_long); const int obj_type = obj_type_node->getSlong(); UCHAR dyn_verb = 0; switch (obj_type) { case ddl_database: dyn_verb = isc_dyn_mod_database; break; case ddl_domain: dyn_verb = isc_dyn_mod_global_fld; break; case ddl_relation: dyn_verb = isc_dyn_mod_rel; break; case ddl_view: dyn_verb = isc_dyn_mod_view; break; case ddl_procedure: dyn_verb = isc_dyn_mod_procedure; break; case ddl_trigger: dyn_verb = isc_dyn_mod_trigger; break; case ddl_udf: dyn_verb = isc_dyn_mod_function; // missing break; case ddl_blob_filter: dyn_verb = isc_dyn_mod_filter; // missing break; case ddl_exception: dyn_verb = isc_dyn_mod_exception; break; case ddl_generator: dyn_verb = isc_dyn_mod_generator; // missing break; case ddl_index: dyn_verb = isc_dyn_mod_idx; break; case ddl_role: dyn_verb = isc_dyn_mod_sql_role; // missing break; case ddl_charset: dyn_verb = isc_dyn_mod_charset; // missing break; case ddl_collation: dyn_verb = isc_dyn_mod_collation; // missing break; // case ddl_sec_class: // dyn_verb = isc_dyn_mod_security_class; // break; default: // Complain. break; } if (have_subobj) { const dsql_str* field_or_param = (dsql_str*) node->nod_arg[e_comment_part]; UCHAR dyn_verb2 = 0; switch (obj_type) { case ddl_relation: case ddl_view: dyn_verb2 = isc_dyn_mod_local_fld; dyn_verb = isc_dyn_rel_name; break; case ddl_procedure: dyn_verb2 = isc_dyn_mod_prc_parameter; // missing dyn_verb = isc_dyn_prc_name; break; default: // Complain. break; } statement->append_string(dyn_verb2, field_or_param->str_data, field_or_param->str_length); } if (obj_type == ddl_database) statement->append_uchar(dyn_verb); else { const dsql_str* obj_name = (dsql_str*) node->nod_arg[e_comment_object]; statement->append_cstring(dyn_verb, obj_name->str_data); } const dsql_str* obj_desc = (dsql_str*) node->nod_arg[e_comment_string]; if (obj_desc) statement->append_string(isc_dyn_description, obj_desc->str_data, obj_desc->str_length); else statement->append_string(isc_dyn_description, NULL, 0); statement->append_uchar(isc_dyn_end); } static void make_index( CompiledStatement* statement, const dsql_nod* element, const dsql_nod* columns, const dsql_nod* referenced_columns, // unused const char* relation_name, const char* index_name) { /************************************** * * m a k e _ i n d e x * ************************************** * * Function * Generate ddl to create an index for a unique * or primary key constraint. * This is not called for a foreign key constraint. * The func. make_index_trf_ref_int handles foreign key constraint * **************************************/ /* stuff either user-defined name or zero-length name, indicating that an index name should be generated */ fb_assert(element->nod_type != nod_foreign); const dsql_nod* index = element->nod_arg[e_pri_index]; fb_assert(index); const dsql_str* string = (dsql_str*) index->nod_arg[e_idx_name]; if (string) { index_name = string->str_data; } if (element->nod_type == nod_primary) { statement->append_cstring(isc_dyn_def_primary_key, index_name); } else if (element->nod_type == nod_unique) { statement->append_cstring(isc_dyn_def_unique, index_name); } statement->append_number(isc_dyn_idx_unique, 1); if (index->nod_arg[e_idx_asc_dsc]) { statement->append_number(isc_dyn_idx_type, 1); } const dsql_nod* const* ptr = columns->nod_arg; for (const dsql_nod* const* const end = ptr + columns->nod_count; ptr < end; ++ptr) { const dsql_str* field_name = (dsql_str*) (*ptr)->nod_arg[e_fln_name]; statement->append_cstring(isc_dyn_fld_name, field_name->str_data); } statement->append_uchar(isc_dyn_end); } static void make_index_trg_ref_int( CompiledStatement* statement, dsql_nod* element, dsql_nod* columns, dsql_nod* referenced_columns, const char* relation_name, const char* index_name) { /****************************************************** * * m a k e _ i n d e x _ t r g _ r e f _ i n t * ****************************************************** * * Function * This is called only when the element->nod_type == nod_foreign_key * * o Generate ddl to create an index for a unique * or primary key constraint. * o Also make an index for the foreign key constraint * o in the caase of foreign key, also generate an appropriate trigger for * cascading referential integrity. * * *****************************************************/ fb_assert(element->nod_type == nod_foreign) /* for_rel_name_str is the name of the relation on which the ddl operation is being done, in this case the foreign key table */ dsql_nod* ddl_node = statement->req_ddl_node; dsql_nod* for_rel_node = ddl_node->nod_arg[e_drl_name]; const dsql_str* for_rel_name_str = (dsql_str*) for_rel_node->nod_arg[e_rln_name]; /* stuff either user-defined name or zero-length name, indicating that an index name should be generated */ dsql_nod* index = element->nod_arg[e_for_index]; fb_assert(index); const dsql_str* string = (dsql_str*) index->nod_arg[e_idx_name]; if (string) { index_name = string->str_data; } statement->append_cstring(isc_dyn_def_foreign_key, index_name); if (index->nod_arg[e_idx_asc_dsc]) { statement->append_number(isc_dyn_idx_type, 1); } if (element->nod_arg[e_for_action]) { dsql_nod* nod_for_action = element->nod_arg[e_for_action]; fb_assert(nod_for_action->nod_type == nod_ref_upd_del); dsql_nod* nod_ref_upd_action = nod_for_action->nod_arg[e_ref_upd]; if (nod_ref_upd_action) { fb_assert(nod_ref_upd_action->nod_type == nod_ref_trig_action); statement->append_uchar(isc_dyn_foreign_key_update); switch (nod_ref_upd_action->nod_flags) { case REF_ACTION_CASCADE: statement->append_uchar(isc_dyn_foreign_key_cascade); define_upd_cascade_trg(statement, element, columns, referenced_columns, relation_name, for_rel_name_str->str_data); break; case REF_ACTION_SET_DEFAULT: statement->append_uchar(isc_dyn_foreign_key_default); define_set_default_trg(statement, element, columns, referenced_columns, relation_name, for_rel_name_str->str_data, true); break; case REF_ACTION_SET_NULL: statement->append_uchar(isc_dyn_foreign_key_null); define_set_null_trg(statement, element, columns, referenced_columns, relation_name, for_rel_name_str->str_data, true); break; case REF_ACTION_NONE: statement->append_uchar(isc_dyn_foreign_key_none); break; default: fb_assert(0); statement->append_uchar(isc_dyn_foreign_key_none); // just in case break; } } dsql_nod* nod_ref_del_action = nod_for_action->nod_arg[e_ref_del]; if (nod_ref_del_action) { fb_assert(nod_ref_del_action->nod_type == nod_ref_trig_action); statement->append_uchar(isc_dyn_foreign_key_delete); switch (nod_ref_del_action->nod_flags) { case REF_ACTION_CASCADE: statement->append_uchar(isc_dyn_foreign_key_cascade); define_del_cascade_trg(statement, element, columns, referenced_columns, relation_name, for_rel_name_str->str_data); break; case REF_ACTION_SET_DEFAULT: statement->append_uchar(isc_dyn_foreign_key_default); define_set_default_trg(statement, element, columns, referenced_columns, relation_name, for_rel_name_str->str_data, false); break; case REF_ACTION_SET_NULL: statement->append_uchar(isc_dyn_foreign_key_null); define_set_null_trg(statement, element, columns, referenced_columns, relation_name, for_rel_name_str->str_data, false); break; case REF_ACTION_NONE: statement->append_uchar(isc_dyn_foreign_key_none); break; default: fb_assert(0); statement->append_uchar(isc_dyn_foreign_key_none); // just in case break; // Error } } } const dsql_nod* const* ptr = columns->nod_arg; for (const dsql_nod* const* const end = ptr + columns->nod_count; ptr < end; ++ptr) { const dsql_str* field_name = (dsql_str*) (*ptr)->nod_arg[1]; statement->append_cstring(isc_dyn_fld_name, field_name->str_data); } statement->append_cstring(isc_dyn_idx_foreign_key, relation_name); if (referenced_columns) { ptr = referenced_columns->nod_arg; for (const dsql_nod* const* const end = ptr + referenced_columns->nod_count; ptr < end; ++ptr) { const dsql_str* field_name = (dsql_str*) (*ptr)->nod_arg[1]; statement->append_cstring(isc_dyn_idx_ref_column, field_name->str_data); } } statement->append_uchar(isc_dyn_end); } static void modify_database( CompiledStatement* statement) { /************************************** * * m o d i f y _ d a t a b a s e * ************************************** * * Function * Modify a database. * **************************************/ const dsql_nod* ddl_node = statement->req_ddl_node; statement->append_uchar(isc_dyn_mod_database); // statement->append_number(isc_dyn_rel_sql_protection, 1); bool drop_difference = false; const dsql_nod* elements = ddl_node->nod_arg[e_adb_all]; const dsql_nod* const* end = elements->nod_arg + elements->nod_count; const dsql_nod* const* ptr; for (ptr = elements->nod_arg; ptr < end; ptr++) { const dsql_nod* element = *ptr; if (element->nod_type == nod_drop_difference) drop_difference = true; } if (drop_difference) { statement->append_uchar(isc_dyn_drop_difference); } SLONG start = 0; elements = ddl_node->nod_arg[e_adb_all]; end = elements->nod_arg + elements->nod_count; for (ptr = elements->nod_arg; ptr < end; ptr++) { const dsql_fil* file; const dsql_nod* element = *ptr; switch (element->nod_type) { case nod_file_desc: file = (dsql_fil*) element->nod_arg[0]; statement->append_cstring(isc_dyn_def_file, file->fil_name->str_data); start = MAX(start, file->fil_start); statement->append_file_start(start); statement->append_file_length(file->fil_length); statement->append_uchar(isc_dyn_end); start += file->fil_length; break; case nod_difference_file: statement->append_cstring(isc_dyn_def_difference, ((dsql_str*)element->nod_arg[0])->str_data); break; case nod_begin_backup: statement->append_uchar(isc_dyn_begin_backup); break; case nod_end_backup: statement->append_uchar(isc_dyn_end_backup); break; default: break; } } statement->append_uchar(isc_dyn_end); } static void modify_domain( CompiledStatement* statement) { /************************************** * * m o d i f y _ d o m a i n * ************************************** * * Function * Alter an SQL domain. * **************************************/ dsql_str* string; dsql_fld* field; dsql_fld local_field(statement->req_pool); /* CVC: This array used with check_one_call to ensure each modification option is called only once. Enlarge it if the switch() below gets more cases. */ USHORT repetition_count[6]; dsql_nod* ddl_node = statement->req_ddl_node; dsql_nod* domain_node = ddl_node->nod_arg[e_alt_dom_name]; const dsql_str* domain_name = (dsql_str*) domain_node->nod_arg[e_fln_name]; statement->append_cstring(isc_dyn_mod_global_fld, domain_name->str_data); /* Is MOVE_CLEAR enough for all platforms? MOVE_CLEAR (repetition_count, sizeof (repetition_count)); */ const USHORT rtop = FB_NELEM(repetition_count); USHORT* p = repetition_count; while (p < repetition_count + rtop) { *p++ = 0; } dsql_nod* ops = ddl_node->nod_arg[e_alt_dom_ops]; dsql_nod** ptr = ops->nod_arg; for (const dsql_nod* const* const end = ptr + ops->nod_count; ptr < end; ptr++) { dsql_nod* element = *ptr; switch (element->nod_type) { case nod_def_default: check_one_call(repetition_count, 0, "DOMAIN DEFAULT"); define_default(statement, element); break; case nod_def_constraint: check_one_call(repetition_count, 1, "DOMAIN CONSTRAINT"); statement->append_uchar(isc_dyn_single_validation); statement->begin_blr(isc_dyn_fld_validation_blr); /* Get the attributes of the domain, and set any occurances of nod_dom_value (corresponding to the keyword VALUE) to the correct type, length, scale, etc. */ if (!METD_get_domain(statement, &local_field, domain_name->str_data)) { // Specified domain or source field does not exist post_607(Arg::Gds(isc_dsql_domain_not_found) << Arg::Str(domain_name->str_data)); } if (element->nod_arg[e_cnstr_condition]) set_nod_value_attributes(element->nod_arg[e_cnstr_condition], &local_field); /* 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 */ statement->req_context_number++; { dsql_nod* node = PASS1_node(statement, element->nod_arg[e_cnstr_condition]); GEN_hidden_variables(statement, true); GEN_expr(statement, node); } statement->end_blr(); if ((string = (dsql_str*) element->nod_arg[e_cnstr_source]) != NULL) { fb_assert(string->str_length <= MAX_USHORT); statement->append_string(isc_dyn_fld_validation_source, string->str_data, string->str_length); } break; case nod_mod_domain_type: field = (dsql_fld*) element->nod_arg[e_mod_dom_new_dom_type]; DDL_resolve_intl_type(statement, field, NULL); put_field(statement, field, false); break; case nod_field_name: { check_one_call(repetition_count, 3, "DOMAIN NAME"); const dsql_str* new_dom_name = (dsql_str*) element->nod_arg[e_fln_name]; statement->append_cstring(isc_dyn_fld_name, new_dom_name->str_data); break; } case nod_delete_rel_constraint: check_one_call(repetition_count, 4, "DOMAIN DROP CONSTRAINT"); statement->append_uchar(isc_dyn_del_validation); break; case nod_del_default: check_one_call(repetition_count, 5, "DOMAIN DROP DEFAULT"); statement->append_uchar(isc_dyn_del_default); break; default: break; } } statement->append_uchar(isc_dyn_end); } static void modify_index( CompiledStatement* statement) { /************************************** * * m o d i f y _ i n d e x * ************************************** * * Function * Alter an index (only active or inactive for now) * **************************************/ dsql_nod* ddl_node = statement->req_ddl_node; dsql_nod* index_node = ddl_node->nod_arg[e_alt_index]; const dsql_str* index_name = (dsql_str*) index_node->nod_arg[e_alt_idx_name]; statement->append_cstring(isc_dyn_mod_idx, index_name->str_data); if (index_node->nod_type == nod_idx_active) { statement->append_number(isc_dyn_idx_inactive, 0); } else if (index_node->nod_type == nod_idx_inactive) { statement->append_number(isc_dyn_idx_inactive, 1); } statement->append_uchar(isc_dyn_end); } static void put_user_grant(CompiledStatement* statement, const dsql_nod* user) { /************************************** * * p u t _ u s e r _ g r a n t * ************************************** * * Functional description * Stuff a user/role/obj option in grant/revoke * **************************************/ const dsql_str* name = (dsql_str*) user->nod_arg[0]; switch (user->nod_type) { case nod_user_group: // GRANT priv ON tbl TO GROUP unix_group statement->append_cstring(isc_dyn_grant_user_group, name->str_data); break; case nod_user_name: if (user->nod_count == 2) { statement->append_cstring(isc_dyn_grant_user_explicit, name->str_data); } else { statement->append_cstring(isc_dyn_grant_user, name->str_data); } break; case nod_proc_obj: statement->append_cstring(isc_dyn_grant_proc, name->str_data); break; case nod_trig_obj: statement->append_cstring(isc_dyn_grant_trig, name->str_data); break; case nod_view_obj: statement->append_cstring(isc_dyn_grant_view, name->str_data); break; case nod_role_name: statement->append_cstring(isc_dyn_grant_role, name->str_data); break; default: /* CVC: Here we should complain: DYN doesn't check parameters and it will write trash in rdb$user_privileges. We probably should complain in most cases when "name" is blank, too. */ break; } } static void modify_privilege(CompiledStatement* statement, NOD_TYPE type, SSHORT option, const UCHAR* privs, const dsql_nod* table, const dsql_nod* user, const dsql_nod* grantor, const dsql_str* field_name) { /************************************** * * m o d i f y _ p r i v i l e g e * ************************************** * * Functional description * Stuff a single grant/revoke verb and all its options. * **************************************/ if (type == nod_grant) { statement->append_uchar(isc_dyn_grant); } else { statement->append_uchar(isc_dyn_revoke); } // stuff the privileges string SSHORT priv_count = 0; statement->append_ushort(0); for (; *privs; privs++) { priv_count++; statement->append_uchar(*privs); } UCHAR* dynsave = statement->req_blr_data.end(); for (SSHORT i = priv_count + 2; i; i--) { --dynsave; } *dynsave++ = (UCHAR) priv_count; *dynsave = (UCHAR) (priv_count >> 8); const dsql_str* name = (dsql_str*) table->nod_arg[0]; if (table->nod_type == nod_procedure_name) { statement->append_cstring(isc_dyn_prc_name, name->str_data); } else { statement->append_cstring(isc_dyn_rel_name, name->str_data); } put_user_grant(statement, user); if (field_name) { statement->append_cstring(isc_dyn_fld_name, field_name->str_data); } if (option) { statement->append_number(isc_dyn_grant_options, option); } put_grantor(statement, grantor); statement->append_uchar(isc_dyn_end); } static char modify_privileges(CompiledStatement* statement, NOD_TYPE type, SSHORT option, const dsql_nod* privs, const dsql_nod* table, const dsql_nod* user, const dsql_nod* grantor) { /************************************** * * m o d i f y _ p r i v i l e g e s * ************************************** * * Functional description * Return a char indicating the privilege to be modified * **************************************/ char privileges[10]; const char* p = 0; char* q; const dsql_nod* fields; const dsql_nod* const* ptr; const dsql_nod* const* end; switch (privs->nod_type) { case nod_all: p = "A"; break; case nod_select: return 'S'; case nod_execute: return 'X'; case nod_insert: return 'I'; case nod_references: case nod_update: p = (privs->nod_type == nod_references) ? "R" : "U"; fields = privs->nod_arg[0]; if (!fields) { return *p; } for (ptr = fields->nod_arg, end = ptr + fields->nod_count; ptr < end; ptr++) { modify_privilege(statement, type, option, reinterpret_cast(p), table, user, grantor, reinterpret_cast((*ptr)->nod_arg[1])); } return 0; case nod_delete: return 'D'; case nod_list: p = q = privileges; for (ptr = privs->nod_arg, end = ptr + privs->nod_count; ptr < end; ptr++) { *q = modify_privileges(statement, type, option, *ptr, table, user, grantor); if (*q) { q++; } } *q = 0; break; default: break; } if (*p) { modify_privilege(statement, type, option, reinterpret_cast(p), table, user, grantor, 0); } return 0; } static void modify_relation(CompiledStatement* statement) { /************************************** * * m o d i f y _ r e l a t i o n * ************************************** * * Function * Alter an SQL table, relying on DYN to generate * global fields for the local fields. * **************************************/ thread_db* tdbb = JRD_get_thread_data(); dsql_nod* ddl_node = statement->req_ddl_node; dsql_nod* relation_node = ddl_node->nod_arg[e_alt_name]; const dsql_str* relation_name = (dsql_str*) relation_node->nod_arg[e_rln_name]; statement->append_cstring(isc_dyn_mod_rel, relation_name->str_data); save_relation(statement, relation_name); if (!statement->req_relation) { TEXT linecol[64]; sprintf (linecol, "At line %d, column %d.", (int) relation_node->nod_line, (int) relation_node->nod_column); ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) << Arg::Gds(isc_dsql_relation_err) << Arg::Gds(isc_random) << Arg::Str(relation_name->str_data) << Arg::Gds(isc_random) << Arg::Str(linecol)); } /* need to handle error that occur in generating dyn string. * If there is an error, get rid of the cached data */ try { dsql_nod* ops = ddl_node->nod_arg[e_alt_ops]; dsql_nod** ptr = ops->nod_arg; for (const dsql_nod* const* const end = ptr + ops->nod_count; ptr < end; ptr++) { const dsql_nod* field_node; const dsql_str* field_name; dsql_nod* element = *ptr; switch (element->nod_type) { case nod_mod_field_name: { const dsql_nod* old_field = element->nod_arg[e_mod_fld_name_orig_name]; const dsql_str* old_field_name = (dsql_str*) old_field->nod_arg[e_fln_name]; statement->append_cstring(isc_dyn_mod_local_fld, old_field_name->str_data); dsql_nod* new_field = element->nod_arg[e_mod_fld_name_new_name]; const dsql_str* new_field_name = (dsql_str*) new_field->nod_arg[e_fln_name]; statement->append_cstring(isc_dyn_rel_name, relation_name->str_data); statement->append_cstring(isc_dyn_new_fld_name, new_field_name->str_data); statement->append_uchar(isc_dyn_end); break; } case nod_mod_field_pos: { field_node = element->nod_arg[e_mod_fld_pos_orig_name]; field_name = (dsql_str*) field_node->nod_arg[e_fln_name]; statement->append_cstring(isc_dyn_mod_local_fld, field_name->str_data); const dsql_nod* const_node = element->nod_arg[e_mod_fld_pos_new_position]; // CVC: Since now the parser accepts pos=1..N, let's subtract one here. const SSHORT constant = (SSHORT) const_node->getSlong() - 1; statement->append_cstring(isc_dyn_rel_name, relation_name->str_data); statement->append_number(isc_dyn_fld_position, constant); statement->append_uchar(isc_dyn_end); break; } case nod_mod_field_type: modify_field(statement, element, (SSHORT) -1, relation_name); break; case nod_def_field: define_field(statement, element, (SSHORT) -1, relation_name, 0); break; case nod_del_field: /* Fix for bug 8054: [CASCADE | RESTRICT] syntax is available in IB4.5, but not required until v5.0. Option CASCADE causes an error : unsupported DSQL construct Option RESTRICT is default behaviour. */ field_node = element->nod_arg[0]; field_name = (dsql_str*) field_node->nod_arg[e_fln_name]; if ((element->nod_arg[1])->nod_type == nod_cascade) { // Unsupported DSQL construct ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_construct_err)); } fb_assert((element->nod_arg[1])->nod_type == nod_restrict); statement->append_cstring(isc_dyn_delete_local_fld, field_name->str_data); statement->append_uchar(isc_dyn_end); break; case nod_delete_rel_constraint: field_name = (dsql_str*) element->nod_arg[0]; statement->append_cstring(isc_dyn_delete_rel_constraint, field_name->str_data); break; case nod_rel_constraint: define_rel_constraint(statement, element); break; default: break; } } statement->append_uchar(isc_dyn_end); } // try catch (const Exception&) { METD_drop_relation(statement, relation_name); statement->req_relation = 0; throw; } } // ******************* // m o d i f y _ u d f // ******************* // Allow the user to change the entry point or module name. // Useful when there are dependencies on the udf, so it cannot be dropped. static void modify_udf(CompiledStatement* statement) { const dsql_nod* node = statement->req_ddl_node; fb_assert(node->nod_type == nod_mod_udf); const dsql_str* obj_name = (dsql_str*) node->nod_arg[e_mod_udf_name]; if (!node->nod_arg[e_mod_udf_entry_pt] && !node->nod_arg[e_mod_udf_module]) ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << // Unexpected end of command Arg::Gds(isc_command_end_err2) << Arg::Num(node->nod_line) << Arg::Num(node->nod_column + obj_name->str_length)); // + strlen("FUNCTION") statement->append_cstring(isc_dyn_mod_function, obj_name->str_data); const dsql_str* entry_point_name = (dsql_str*) node->nod_arg[e_mod_udf_entry_pt]; if (entry_point_name) statement->append_cstring(isc_dyn_func_entry_point, entry_point_name->str_data); const dsql_str* module_name = (dsql_str*) node->nod_arg[e_mod_udf_module]; if (module_name) statement->append_cstring(isc_dyn_func_module_name, module_name->str_data); statement->append_uchar(isc_dyn_end); } // ******************* // m o d i f y _ m a p // ******************* // Allow the user to establish/drop the mapping between OS security object and the role static void modify_map(CompiledStatement* statement) { const dsql_nod* node = statement->req_ddl_node; fb_assert(node->nod_type == nod_mod_role); const dsql_str* ds = (dsql_str*) node->nod_arg[e_mod_role_os_name]; fb_assert(ds || node->nod_arg[e_mod_role_action]->getSlong() == isc_dyn_automap_role || node->nod_arg[e_mod_role_action]->getSlong() == isc_dyn_autounmap_role); statement->append_cstring(isc_dyn_mapping, ds ? ds->str_data : ""); ds = (dsql_str*) node->nod_arg[e_mod_role_db_name]; fb_assert(ds); statement->append_cstring(node->nod_arg[e_mod_role_action]->getSlong(), ds->str_data); statement->append_uchar(isc_dyn_end); } // ********************* // d e f i n e _ u s e r // ********************* // Support SQL operator create/alter/drop user static void define_user(CompiledStatement* statement, UCHAR op) { statement->append_uchar(isc_dyn_user); const dsql_nod* node = statement->req_ddl_node; int argCount = 0; for (int i = 0; i < node->nod_count; ++i) { const dsql_str* ds = (dsql_str*) node->nod_arg[i]; if (! ds) { if (i == e_user_name || (i == e_user_passwd && op == isc_dyn_user_add)) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << // Unexpected end of command Arg::Gds(isc_command_end_err2) << Arg::Num(node->nod_line) << Arg::Num(node->nod_column)); } continue; } ++argCount; switch(i) { case e_user_name: statement->append_cstring(op, ds->str_data); break; case e_user_passwd: statement->append_cstring(isc_dyn_user_passwd, ds->str_data); break; case e_user_first: statement->append_cstring(isc_dyn_user_first, ds->str_data); break; case e_user_middle: statement->append_cstring(isc_dyn_user_middle, ds->str_data); break; case e_user_last: statement->append_cstring(isc_dyn_user_last, ds->str_data); break; } } if (argCount < 2 && op != isc_dyn_user_del) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << // Unexpected end of command Arg::Gds(isc_command_end_err2) << Arg::Num(node->nod_line) << Arg::Num(node->nod_column)); } statement->append_uchar(isc_user_end); statement->append_uchar(isc_dyn_end); } static dsql_par* parameter_reverse_order(dsql_par* parameter, dsql_par* prev) { /************************************** * * p a r a m e t e r _ r e v e r s e _ o r d e r * ************************************** * * Function * Reverse parameters order for EXECUTE BLOCK statement * **************************************/ dsql_par* result; if (parameter->par_next) result = parameter_reverse_order(parameter->par_next, parameter); else result = parameter; parameter->par_next = prev; return result; } static void process_role_nm_list(CompiledStatement* statement, SSHORT option, const dsql_nod* user_ptr, const dsql_nod* role_ptr, NOD_TYPE type, const dsql_nod* grantor) { /************************************** * * p r o c e s s _ r o l e _ n m _ l i s t * ************************************** * * Functional description * Build req_blr for grant & revoke role stmt * **************************************/ if (type == nod_grant) { statement->append_uchar(isc_dyn_grant); } else { statement->append_uchar(isc_dyn_revoke); } statement->append_ushort(1); statement->append_uchar('M'); const dsql_str* role_nm = (dsql_str*) role_ptr->nod_arg[0]; statement->append_cstring(isc_dyn_sql_role_name, role_nm->str_data); const dsql_str* user_nm = (dsql_str*) user_ptr->nod_arg[0]; statement->append_cstring(isc_dyn_grant_user, user_nm->str_data); if (option) { statement->append_number(isc_dyn_grant_admin_options, option); } put_grantor(statement, grantor); statement->append_uchar(isc_dyn_end); } static void put_grantor(CompiledStatement* statement, const dsql_nod* grantor) { /************************************** * * p u t _ g r a n t o r * ************************************** * * Function * Write out grantor for grant / revoke. * **************************************/ if (grantor) { fb_assert(grantor->nod_type == nod_user_name); const dsql_str* name = (const dsql_str*) grantor->nod_arg[0]; statement->append_cstring(isc_dyn_grant_grantor, name->str_data); } } static void put_descriptor(CompiledStatement* statement, const dsc* desc) { /************************************** * * p u t _ d e s c r i p t o r * ************************************** * * Function * Write out field description in ddl, given the * input descriptor. * **************************************/ statement->append_number(isc_dyn_fld_type, blr_dtypes[desc->dsc_dtype]); if (desc->dsc_dtype == dtype_varying) { statement->append_number(isc_dyn_fld_length, (SSHORT) (desc->dsc_length - sizeof(USHORT))); } else { statement->append_number(isc_dyn_fld_length, desc->dsc_length); } if (desc->dsc_dtype <= dtype_any_text) { statement->append_number(isc_dyn_fld_character_set, DSC_GET_CHARSET(desc)); statement->append_number(isc_dyn_fld_collation, DSC_GET_COLLATE(desc)); } else if (desc->dsc_dtype == dtype_blob) { statement->append_number(isc_dyn_fld_sub_type, desc->dsc_sub_type); if (desc->dsc_sub_type == isc_blob_text) { statement->append_number(isc_dyn_fld_character_set, desc->dsc_scale); statement->append_number(isc_dyn_fld_collation, desc->dsc_flags >> 8); // BLOB collation } } else { statement->append_number(isc_dyn_fld_sub_type, desc->dsc_sub_type); statement->append_number(isc_dyn_fld_scale, desc->dsc_scale); } } // // Write out field data type // Taking special care to declare international text. // static void put_dtype(CompiledStatement* statement, const dsql_fld* field, bool use_subtype) { #ifdef DEV_BUILD // Check if the field describes a known datatype if (field->fld_dtype > FB_NELEM(blr_dtypes) || !blr_dtypes[field->fld_dtype]) { SCHAR buffer[100]; sprintf(buffer, "Invalid dtype %d in put_dtype", field->fld_dtype); ERRD_bugcheck(buffer); } #endif if (field->fld_not_nullable) statement->append_uchar(blr_not_nullable); if (field->fld_type_of_name.hasData()) { if (field->fld_type_of_table) { if (field->fld_explicit_collation) { statement->append_uchar(blr_column_name2); statement->append_uchar(field->fld_full_domain ? blr_domain_full : blr_domain_type_of); statement->append_meta_string(field->fld_type_of_table->str_data); statement->append_meta_string(field->fld_type_of_name.c_str()); statement->append_ushort(field->fld_ttype); } else { statement->append_uchar(blr_column_name); statement->append_uchar(field->fld_full_domain ? blr_domain_full : blr_domain_type_of); statement->append_meta_string(field->fld_type_of_table->str_data); statement->append_meta_string(field->fld_type_of_name.c_str()); } } else { if (field->fld_explicit_collation) { statement->append_uchar(blr_domain_name2); statement->append_uchar(field->fld_full_domain ? blr_domain_full : blr_domain_type_of); statement->append_meta_string(field->fld_type_of_name.c_str()); statement->append_ushort(field->fld_ttype); } else { statement->append_uchar(blr_domain_name); statement->append_uchar(field->fld_full_domain ? blr_domain_full : blr_domain_type_of); statement->append_meta_string(field->fld_type_of_name.c_str()); } } return; } switch (field->fld_dtype) { case dtype_cstring: case dtype_text: case dtype_varying: case dtype_blob: if (!use_subtype) { statement->append_uchar(blr_dtypes[field->fld_dtype]); } else if (field->fld_dtype == dtype_varying) { statement->append_uchar(blr_varying2); statement->append_ushort(field->fld_ttype); } else if (field->fld_dtype == dtype_cstring) { statement->append_uchar(blr_cstring2); statement->append_ushort(field->fld_ttype); } else if (field->fld_dtype == dtype_blob) { statement->append_uchar(blr_blob2); statement->append_ushort(field->fld_sub_type); statement->append_ushort(field->fld_ttype); } else { statement->append_uchar(blr_text2); statement->append_ushort(field->fld_ttype); } if (field->fld_dtype == dtype_varying) { statement->append_ushort(field->fld_length - sizeof(USHORT)); } else if (field->fld_dtype != dtype_blob) { statement->append_ushort(field->fld_length); } break; default: statement->append_uchar(blr_dtypes[field->fld_dtype]); if (DTYPE_IS_EXACT(field->fld_dtype) || (dtype_quad == field->fld_dtype)) { statement->append_uchar(field->fld_scale); } } } static void put_field( CompiledStatement* statement, dsql_fld* field, bool udf_flag) { /************************************** * * p u t _ f i e l d * ************************************** * * Function * Emit dyn which describes a field data type. * This field could be a column, a procedure input, * or a procedure output. * **************************************/ if (field->fld_not_nullable) statement->append_uchar(isc_dyn_fld_not_null); if (field->fld_type_of_name.hasData()) { if (field->fld_source.hasData()) { statement->append_string(isc_dyn_fld_source, field->fld_source); statement->append_string(isc_dyn_fld_name, field->fld_type_of_name); statement->append_cstring(isc_dyn_rel_name, field->fld_type_of_table->str_data); } else statement->append_string(isc_dyn_fld_source, field->fld_type_of_name); if (field->fld_explicit_collation) statement->append_number(isc_dyn_fld_collation, field->fld_collation_id); if (!field->fld_full_domain) statement->append_number(isc_dyn_prm_mechanism, prm_mech_type_of); return; } statement->append_number(isc_dyn_fld_type, blr_dtypes[field->fld_dtype]); if (field->fld_dtype == dtype_blob) { statement->append_number(isc_dyn_fld_sub_type, field->fld_sub_type); statement->append_number(isc_dyn_fld_scale, 0); if (!udf_flag) { if (!field->fld_seg_length) { field->fld_seg_length = DEFAULT_BLOB_SEGMENT_SIZE; } statement->append_number(isc_dyn_fld_segment_length, field->fld_seg_length); } if (field->fld_sub_type == isc_blob_text) { statement->append_number(isc_dyn_fld_character_set, field->fld_character_set_id); statement->append_number(isc_dyn_fld_collation, field->fld_collation_id); } } else if (field->fld_dtype <= dtype_any_text) { statement->append_number(isc_dyn_fld_sub_type, field->fld_sub_type); statement->append_number(isc_dyn_fld_scale, 0); if (field->fld_dtype == dtype_varying) { // CVC: Fix the assertion fb_assert((field->fld_length) <= MAX_SSHORT); statement->append_number(isc_dyn_fld_length, (SSHORT) (field->fld_length - sizeof(USHORT))); } else { statement->append_number(isc_dyn_fld_length, field->fld_length); } statement->append_number(isc_dyn_fld_char_length, field->fld_character_length); statement->append_number(isc_dyn_fld_character_set, field->fld_character_set_id); if (!udf_flag) statement->append_number(isc_dyn_fld_collation, field->fld_collation_id); } else { statement->append_number(isc_dyn_fld_scale, field->fld_scale); statement->append_number(isc_dyn_fld_length, field->fld_length); if (DTYPE_IS_EXACT(field->fld_dtype)) { statement->append_number(isc_dyn_fld_precision, field->fld_precision); statement->append_number(isc_dyn_fld_sub_type, field->fld_sub_type); } } } static void put_local_variable( CompiledStatement* statement, dsql_var* variable, dsql_nod* host_param, const dsql_str* collation_name) { /************************************** * * p u t _ l o c a l _ v a r i a b l e * ************************************** * * Function * Write out local variable field data type * **************************************/ dsql_fld* field = variable->var_field; statement->append_uchar(blr_dcl_variable); statement->append_ushort(variable->var_variable_number); DDL_resolve_intl_type(statement, field, collation_name); //const USHORT dtype = field->fld_dtype; put_dtype(statement, field, true); //field->fld_dtype = dtype; // Check for a default value, borrowed from define_domain dsql_nod* node = (host_param) ? host_param->nod_arg[e_dfl_default] : 0; if (node || (!field->fld_full_domain && !field->fld_not_nullable)) { statement->append_uchar(blr_assignment); if (node) { fb_assert(node->nod_type == nod_def_default); PsqlChanger psqlChanger(statement, false); node = PASS1_node(statement, node->nod_arg[e_dft_default]); GEN_expr(statement, node); } else { // Initialize variable to NULL statement->append_uchar(blr_null); } statement->append_uchar(blr_variable); statement->append_ushort(variable->var_variable_number); } else { statement->append_uchar(blr_init_variable); statement->append_ushort(variable->var_variable_number); } statement->put_debug_variable(variable->var_variable_number, variable->var_name); ++statement->req_hidden_vars_number; } static void put_local_variables(CompiledStatement* statement, dsql_nod* parameters, SSHORT locals) { /************************************** * * p u t _ l o c a l _ v a r i a b l e s * ************************************** * * Function * Emit dyn for the local variables declared * in a procedure or trigger. * **************************************/ if (parameters) { dsql_nod** ptr = parameters->nod_arg; for (const dsql_nod* const* const end = ptr + parameters->nod_count; ptr < end; ptr++) { dsql_nod* parameter = *ptr; statement->put_debug_src_info(parameter->nod_line, parameter->nod_column); if (parameter->nod_type == nod_def_field) { dsql_fld* field = (dsql_fld*) parameter->nod_arg[e_dfl_field]; const dsql_nod* const* rest = ptr; while (++rest != end) { if ((*rest)->nod_type == nod_def_field) { const dsql_fld* rest_field = (dsql_fld*) (*rest)->nod_arg[e_dfl_field]; if (field->fld_name == rest_field->fld_name) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-637) << Arg::Gds(isc_dsql_duplicate_spec) << Arg::Str(field->fld_name)); } } } dsql_nod* var_node = MAKE_variable(field, field->fld_name.c_str(), VAR_local, 0, 0, locals); *ptr = var_node; dsql_var* variable = (dsql_var*) var_node->nod_arg[e_var_variable]; put_local_variable(statement, variable, parameter, reinterpret_cast(parameter->nod_arg[e_dfl_collate])); // Some field attributes are calculated inside // put_local_variable(), so we reinitialize the // descriptor MAKE_desc_from_field(&var_node->nod_desc, field); locals++; } else if (parameter->nod_type == nod_cursor) { PASS1_statement(statement, parameter); GEN_statement(statement, parameter); } } } } static void put_msg_field( CompiledStatement* statement, const dsql_fld* field) { /************************************** * * p u t _ m s g _ f i e l d * ************************************** * * Function * Write out message field data type * **************************************/ //const USHORT dtype = field->fld_dtype; put_dtype(statement, field, true); //field->fld_dtype = dtype; // add slot for null flag (parameter2) statement->append_uchar(blr_short); statement->append_uchar(0); } static dsql_nod* replace_field_names(dsql_nod* input, dsql_nod* search_fields, dsql_nod* replace_fields, bool null_them, const char* context_name) { /************************************** * * r e p l a c e _ f i e l d _ n a m e s * ************************************** * * Function * Given an input node tree, find any field name nodes * and replace them according to the mapping provided. * Used to create view WITH CHECK OPTION. * **************************************/ if (!input || input->getType() != dsql_type_nod) { return input; } const dsql_nod* const* const endo = input->nod_arg + input->nod_count; for (dsql_nod** ptr = input->nod_arg; ptr < endo; ++ptr) { if ((*ptr)->nod_type == nod_select_expr) { // No subqueries permitted for VIEW WITH CHECK OPTION post_607(Arg::Gds(isc_subquery_err)); } if ((*ptr)->nod_type == nod_field_name) { // found a field node, check if it needs to be replaced const dsql_str* field_name = (dsql_str*) (*ptr)->nod_arg[e_fln_name]; dsql_nod** search = search_fields->nod_arg; const dsql_nod* const* const end = search + search_fields->nod_count; dsql_nod** replace = NULL; if (replace_fields) { replace = replace_fields->nod_arg; } bool found = false; for (; search < end; search++, (replace_fields) ? replace++ : NULL) { const dsql_str* replace_name = 0; if (replace_fields) { replace_name = (dsql_str*) (*replace)->nod_arg[e_fln_name]; } const dsql_nod* field_node = *search; const dsql_fld* field = (dsql_fld*) field_node->nod_arg[e_fld_field]; if (field->fld_name == field_name->str_data) { found = true; if (replace_fields) { (*ptr)->nod_arg[e_fln_name] = (*replace)->nod_arg[e_fln_name]; } (*ptr)->nod_arg[e_fln_context] = (dsql_nod*) MAKE_cstring(context_name); } if (null_them && replace_fields && !strcmp((SCHAR *) field_name->str_data, (SCHAR *) replace_name->str_data)) { found = true; } } if (null_them && !found) { (*ptr) = MAKE_node(nod_null, (int) 0); } } else { // recursively go through the input tree // looking for field name nodes replace_field_names(*ptr, search_fields, replace_fields, null_them, context_name); } } return input; } static void reset_context_stack(CompiledStatement* statement) { /************************************** * * r e s e t _ c o n t e x t _ s t a c k * ************************************** * * Function * Get rid of any predefined contexts created * for a view or trigger definition. * Also reset hidden variables. * **************************************/ statement->req_context->clear(); statement->req_context_number = 0; statement->req_derived_context_number = 0; statement->req_hidden_vars_number = 0; statement->req_hidden_vars.clear(); } static void save_field(CompiledStatement* statement, const TEXT* field_name) { /************************************** * * s a v e _ f i e l d * ************************************** * * Function * Save the name of a field in the relation or view currently * being defined. This is done to support definition * of triggers which will depend on the metadata created * in this statement. * **************************************/ thread_db* tdbb = JRD_get_thread_data(); dsql_rel* relation = statement->req_relation; if (!relation) { return; } MemoryPool& p = relation->rel_flags & REL_new_relation ? *tdbb->getDefaultPool() : statement->req_dbb->dbb_pool; dsql_fld* field = FB_NEW(p) dsql_fld(p); field->fld_name = field_name; field->fld_next = relation->rel_fields; relation->rel_fields = field; } static void save_relation(CompiledStatement* statement, const dsql_str* relation_name) { /************************************** * * s a v e _ r e l a t i o n * ************************************** * * Function * Save the name of the relation or view currently * being defined. This is done to support definition * of triggers which will depend on the metadata created * in this statement. * **************************************/ thread_db* tdbb = JRD_get_thread_data(); if (statement->req_flags & REQ_save_metadata) { return; } statement->req_flags |= REQ_save_metadata; const dsql_nod* ddl_node = statement->req_ddl_node; dsql_rel* relation; if (ddl_node->nod_type == nod_mod_relation) { relation = METD_get_relation(statement, relation_name); } else { MemoryPool& pool = *tdbb->getDefaultPool(); relation = FB_NEW(pool) dsql_rel(pool); relation->rel_name = relation_name->str_data; if (ddl_node->nod_type == nod_def_relation || ddl_node->nod_type == nod_redef_relation) relation->rel_flags = REL_creating; } statement->req_relation = relation; } static void set_statistics( CompiledStatement* statement) { /************************************** * * s e t _ s t a t i s t i c s * ************************************** * * Function * Alter an index/.. statistics * **************************************/ const dsql_nod* ddl_node = statement->req_ddl_node; const dsql_str* index_name = (dsql_str*) ddl_node->nod_arg[e_stat_name]; statement->append_cstring(isc_dyn_mod_idx, index_name->str_data); statement->append_uchar(isc_dyn_idx_statistic); statement->append_uchar(isc_dyn_end); } static void stuff_default_blr( CompiledStatement* statement, const UCHAR* default_buff, USHORT buff_size) { /******************************************** * * s t u f f _ d e f a u l t _ b l r * ******************************************** * Function: * The default_blr is passed in default_buffer. It is of the form: * blr_version4 blr_literal ..... blr_eoc. * strip the blr_version4 and blr_eoc verbs and stuff the remaining * blr in the blr stream in the statement. * *********************************************/ fb_assert((*default_buff == blr_version4) || (*default_buff == blr_version5)); USHORT i; for (i = 1; i < buff_size - 1; ++i) { statement->append_uchar(default_buff[i]); } fb_assert(default_buff[i] == blr_eoc); } static void stuff_matching_blr(CompiledStatement* statement, const dsql_nod* for_columns, const dsql_nod* prim_columns) { /******************************************** * * s t u f f _ m a t c h i n g _ b l r * ******************************************** * * Function * Generate blr to express: foreign_key == primary_key * ie., for_key.column_1 = prim_key.column_1 and * for_key.column_2 = prim_key.column_2 and .... so on.. * **************************************/ // count of foreign key columns fb_assert(prim_columns->nod_count == for_columns->nod_count); fb_assert(prim_columns->nod_count != 0); statement->append_uchar(blr_boolean); if (prim_columns->nod_count > 1) { statement->append_uchar(blr_and); } USHORT num_fields = 0; const dsql_nod* const* for_key_flds = for_columns->nod_arg; const dsql_nod* const* prim_key_flds = prim_columns->nod_arg; do { statement->append_uchar(blr_eql); const dsql_str* for_key_fld_name_str = (dsql_str*) (*for_key_flds)->nod_arg[1]; const dsql_str* prim_key_fld_name_str = (dsql_str*) (*prim_key_flds)->nod_arg[1]; statement->append_uchar(blr_field); statement->append_uchar(2); statement->append_cstring(0, for_key_fld_name_str->str_data); statement->append_uchar(blr_field); statement->append_uchar(0); statement->append_cstring(0, prim_key_fld_name_str->str_data); num_fields++; if (prim_columns->nod_count - num_fields >= 2) { statement->append_uchar(blr_and); } for_key_flds++; prim_key_flds++; } while (num_fields < for_columns->nod_count); statement->append_uchar(blr_end); } static void stuff_trg_firing_cond(CompiledStatement* statement, const dsql_nod* prim_columns) { /******************************************** * * s t u f f _ t r g _ f i r i n g _ c o n d * ******************************************** * * Function * Generate blr to express: if (old.primary_key != new.primary_key). * do a column by column comparison. * **************************************/ statement->append_uchar(blr_if); if (prim_columns->nod_count > 1) { statement->append_uchar(blr_or); } USHORT num_fields = 0; const dsql_nod* const* prim_key_flds = prim_columns->nod_arg; do { statement->append_uchar(blr_neq); const dsql_str* prim_key_fld_name_str = (dsql_str*) (*prim_key_flds)->nod_arg[1]; statement->append_uchar(blr_field); statement->append_uchar(0); statement->append_cstring(0, prim_key_fld_name_str->str_data); statement->append_uchar(blr_field); statement->append_uchar(1); statement->append_cstring(0, prim_key_fld_name_str->str_data); num_fields++; if (prim_columns->nod_count - num_fields >= 2) statement->append_uchar(blr_or); prim_key_flds++; } while (num_fields < prim_columns->nod_count); } static void modify_field(CompiledStatement* statement, dsql_nod* element, SSHORT position, const dsql_str* relation_name) { /************************************** * * m o d i f y _ f i e l d * ************************************** * * Function * Modify a field, as part of an alter table statement. * Alter domain is handled in modify_domain. * **************************************/ dsql_fld* field = (dsql_fld*) element->nod_arg[e_mod_fld_type_field]; statement->append_string(isc_dyn_mod_sql_fld, field->fld_name); // add the field to the relation being defined for parsing purposes bool permanent = false; dsql_rel* relation = statement->req_relation; if (relation != NULL) { if (! (relation->rel_flags & REL_new_relation)) { dsql_fld* perm_field = FB_NEW(statement->req_dbb->dbb_pool) dsql_fld(statement->req_dbb->dbb_pool); *perm_field = *field; field = perm_field; permanent = true; } field->fld_next = relation->rel_fields; relation->rel_fields = field; } try { dsql_nod* computedNod = element->nod_arg[e_mod_fld_type_computed]; if (computedNod) { reset_context_stack(statement); PASS1_make_context(statement, statement->req_ddl_node->nod_arg[e_alt_name]); dsql_str* computedSrc = (dsql_str*) computedNod->nod_arg[e_cmp_text]; fb_assert(computedSrc->str_length <= MAX_USHORT); computedNod = PASS1_node(statement, computedNod->nod_arg[e_cmp_expr]); if (is_array_or_blob(statement, computedNod)) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-607) << Arg::Gds(isc_dsql_no_array_computed)); } statement->begin_blr(isc_dyn_fld_computed_blr); GEN_hidden_variables(statement, true); GEN_expr(statement, computedNod); statement->end_blr(); statement->append_string(isc_dyn_fld_computed_source, computedSrc->str_data, (USHORT) computedSrc->str_length); reset_context_stack(statement); } const dsql_nod* defNod = element->nod_arg[e_mod_fld_type_default]; if (defNod) { // We have the default or want to get rid of it. if (defNod->nod_type == nod_def_default) define_default(statement, defNod); else if (defNod->nod_type == nod_del_default) statement->append_uchar(isc_dyn_del_default); else { fb_assert(false); } } else { // We have the type. Default and type/domain are exclusive for now. dsql_nod* domain_node = element->nod_arg[e_mod_fld_type_dom_name]; if (domain_node) { const dsql_nod* node1 = domain_node->nod_arg[e_dom_name]; const dsql_str* domain_name = (dsql_str*) node1->nod_arg[e_fln_name]; statement->append_cstring(isc_dyn_fld_source, domain_name->str_data); // Get the domain information if (!METD_get_domain(statement, field, domain_name->str_data)) { // Specified domain or source field does not exist post_607(Arg::Gds(isc_dsql_domain_not_found) << Arg::Str(domain_name->str_data)); } DDL_resolve_intl_type(statement, field, NULL); } else { if (relation_name) { statement->append_cstring(isc_dyn_rel_name, relation_name->str_data); } // If COMPUTED was specified but the type wasn't, we use the type of // the computed expression. if (computedNod && field->fld_dtype == dtype_unknown) { dsc desc; MAKE_desc(statement, &desc, computedNod, NULL); field->fld_dtype = desc.dsc_dtype; field->fld_length = desc.dsc_length; field->fld_scale = desc.dsc_scale; if (field->fld_dtype <= dtype_any_text) { field->fld_character_set_id = DSC_GET_CHARSET(&desc); field->fld_collation_id= DSC_GET_COLLATE(&desc); } else field->fld_sub_type = desc.dsc_sub_type; } else DDL_resolve_intl_type2(statement, field, NULL, true); put_field(statement, field, false); } } statement->append_uchar(isc_dyn_end); } // try catch (const Exception&) { clearPermanentField(relation, permanent); throw; } clearPermanentField(relation, permanent); } static void set_nod_value_attributes( dsql_nod* node, const dsql_fld* field) { /************************************** * * s e t _ n o d _ v a l u e _ a t t r i b u t e s * ************************************** * * Function * Examine all the children of the argument node: * if any is a nod_dom_value, set its dtype, size, and scale * to those of the field argument * **************************************/ for (ULONG child_number = 0; child_number < node->nod_count; ++child_number) { dsql_nod* child = node->nod_arg[child_number]; if (child && child->getType() == dsql_type_nod) { if (nod_dom_value == child->nod_type) { fb_assert(field->fld_dtype <= MAX_UCHAR); child->nod_desc.dsc_dtype = (UCHAR) field->fld_dtype; child->nod_desc.dsc_length = field->fld_length; fb_assert(field->fld_scale <= MAX_SCHAR); child->nod_desc.dsc_scale = (SCHAR) field->fld_scale; } else if ((nod_constant != child->nod_type) && (child->nod_count > 0)) { /* A nod_constant can have nod_arg entries which are not really pointers to other nodes, but rather integer values, so it is not safe to scan through its children. Fortunately, it cannot have a nod_dom_value as a child in any case, so we lose nothing by skipping it. */ set_nod_value_attributes(child, field); } } // if it's a node } // for (child_number ... return; } // BEGIN dsql_req METHODS. // Write out a string of blr as part of a ddl string, // as in a view or computed field definition. // void CompiledStatement::begin_blr(UCHAR verb) { if (verb) { append_uchar(verb); } req_base_offset = req_blr_data.getCount(); // put in a place marker for the size of the blr, since it is unknown append_ushort(0); append_uchar((req_flags & REQ_blr_version4) ? blr_version4 : blr_version5); } // Complete the stuffing of a piece of // blr by going back and inserting the length. // void CompiledStatement::end_blr() { append_uchar(blr_eoc); // go back and stuff in the proper length UCHAR* blr_base = &req_blr_data[req_base_offset]; const ULONG length = (req_blr_data.getCount() - req_base_offset) - 2; if (length > 0xFFFF) { ERRD_post(Arg::Gds(isc_too_big_blr) << Arg::Num(length) << Arg::Num(0xFFFF)); } *blr_base++ = (UCHAR) length; *blr_base = (UCHAR) (length >> 8); } void CompiledStatement::append_number(UCHAR verb, SSHORT number) { /************************************** * * Input * blr_ptr: current position of blr being generated * verb: blr byte of which number is an argument * number: value to be written to blr * Function * Write out a numeric valued attribute. * **************************************/ if (verb) { append_uchar(verb); } append_ushort_with_length(number); } // // Write out a string valued attribute. // void CompiledStatement::append_cstring(UCHAR verb, const char* string) { const USHORT length = string ? strlen(string) : 0; append_string(verb, string, length); } // // Write out a string in metadata charset with one byte of length. // void CompiledStatement::append_meta_string(const char* string) { thread_db* tdbb = JRD_get_thread_data(); ISC_STATUS_ARRAY status_vector = {0}; UCharBuffer nameBuffer; CsConvert cv(INTL_charset_lookup(tdbb, CS_dynamic)->getStruct(), INTL_charset_lookup(tdbb, CS_METADATA)->getStruct()); cv.convert(strlen(string), (const UCHAR*) string, nameBuffer); append_string(0, (const TEXT*) nameBuffer.begin(), nameBuffer.getCount()); } // // Write out a string valued attribute. (Overload 1.) // void CompiledStatement::append_string(UCHAR verb, const char* string, USHORT length) { // TMN: Doesn't this look pretty awkward? If we are given // a verb, the length is a ushort, else it's uchar. if (verb) { append_uchar(verb); append_ushort(length); } else { fb_assert(length <= MAX_UCHAR); append_uchar(length); } /* CVC: I preserve this code but it's inconsistent: we first log the length then we check the null terminator. If we want this, we should recalculate the length and log the correct length instead. if (string) { for (; length-- && *string; string++) { append_uchar(*string); } } */ if (string) append_raw_string(string, length); } void CompiledStatement::append_uchars(UCHAR byte, int count) { for (int i = 0; i < count; ++i) { append_uchar(byte); } } void CompiledStatement::append_ushort_with_length(USHORT val) { // append an USHORT value, prepended with the USHORT length of an USHORT append_ushort(2); append_ushort(val); } void CompiledStatement::append_ulong_with_length(ULONG val) { // append an ULONG value, prepended with the USHORT length of an ULONG append_ushort(4); append_ulong(val); } void CompiledStatement::append_file_length(ULONG length) { append_uchar(isc_dyn_file_length); append_ulong_with_length(length); } void CompiledStatement::append_file_start(ULONG start) { append_uchar(isc_dyn_file_start); append_ulong_with_length(start); } // // common code factored out // void CompiledStatement::generate_unnamed_trigger_beginning(bool on_update_trigger, const char* prim_rel_name, const dsql_nod* prim_columns, const char* for_rel_name, const dsql_nod* for_columns) { // no trigger name. It is generated by the engine append_string(isc_dyn_def_trigger, "", 0); append_number(isc_dyn_trg_type, (SSHORT) (on_update_trigger ? POST_MODIFY_TRIGGER : POST_ERASE_TRIGGER)); append_uchar(isc_dyn_sql_object); append_number(isc_dyn_trg_sequence, 1); append_number(isc_dyn_trg_inactive, 0); append_cstring(isc_dyn_rel_name, prim_rel_name); // the trigger blr begin_blr(isc_dyn_trg_blr); /* for ON UPDATE TRIGGER only: generate the trigger firing condition: if prim_key.old_value != prim_key.new value. Note that the key could consist of multiple columns */ if (on_update_trigger) { stuff_trg_firing_cond(this, prim_columns); append_uchars(blr_begin, 2); } append_uchar(blr_for); append_uchar(blr_rse); // the context for the prim. key relation append_uchar(1); append_uchar(blr_relation); append_cstring(0, for_rel_name); // the context for the foreign key relation append_uchar(2); // generate the blr for: foreign_key == primary_key stuff_matching_blr(this, for_columns, prim_columns); append_uchar(blr_modify); append_uchar(2); append_uchar(2); append_uchar(blr_begin); } void CompiledStatement::begin_debug() { fb_assert(!req_debug_data.getCount()); req_debug_data.add(fb_dbg_version); req_debug_data.add(1); } void CompiledStatement::end_debug() { req_debug_data.add(fb_dbg_end); } void CompiledStatement::put_debug_src_info(USHORT line, USHORT col) { req_debug_data.add(fb_dbg_map_src2blr); req_debug_data.add(line); req_debug_data.add(line >> 8); req_debug_data.add(col); req_debug_data.add(col >> 8); ULONG offset = (req_blr_data.getCount() - req_base_offset); // for DDL statements we store BLR's length at the first 2 bytes if (req_type == REQ_DDL || req_ddl_node) { offset -= 2; } req_debug_data.add(offset); req_debug_data.add(offset >> 8); } void CompiledStatement::put_debug_variable(USHORT number, const TEXT* name) { fb_assert(name); req_debug_data.add(fb_dbg_map_varname); req_debug_data.add(number); req_debug_data.add(number >> 8); USHORT len = strlen(name); if (len > MAX_UCHAR) len = MAX_UCHAR; req_debug_data.add(len); req_debug_data.add(reinterpret_cast(name), len); } void CompiledStatement::put_debug_argument(UCHAR type, USHORT number, const TEXT* name) { fb_assert(name); req_debug_data.add(fb_dbg_map_argument); req_debug_data.add(type); req_debug_data.add(number); req_debug_data.add(number >> 8); USHORT len = strlen(name); if (len > MAX_UCHAR) len = MAX_UCHAR; req_debug_data.add(len); req_debug_data.add(reinterpret_cast(name), len); } void CompiledStatement::append_debug_info() { end_debug(); const size_t len = req_blr_data.getCount() + req_debug_data.getCount(); if (len + 4 < MAX_USHORT) { append_uchar(isc_dyn_debug_info); append_ushort(req_debug_data.getCount()); append_raw_string(req_debug_data.begin(), req_debug_data.getCount()); } } // // removes temporary pool pointers from field, stored in permanent cache // void clearPermanentField (dsql_rel* relation, bool perm) { if (relation && relation->rel_fields && perm) { relation->rel_fields->fld_procedure = 0; relation->rel_fields->fld_ranges = 0; relation->rel_fields->fld_character_set = 0; relation->rel_fields->fld_sub_type_name = 0; relation->rel_fields->fld_relation = relation; } } // // post very often used error - avoid code duplication // static void post_607(const Arg::StatusVector& v) { Arg::Gds err(isc_sqlerr); err << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err); err.append(v); ERRD_post(err); }