8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-24 21:23:04 +01:00
firebird-mirror/src/jrd/dfw.epp

5498 lines
136 KiB
Plaintext

/*
* PROGRAM: JRD Access Method
* MODULE: dfw.epp
* DESCRIPTION: Deferred Work handler
*
* 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.6.25 Claudio Valderrama: Implement deferred check for udf usage
* inside a procedure before dropping the udf and creating stub for future
* processing of dependencies from dropped generators.
*
* 2001.8.12 Claudio Valderrama: find_depend_in_dfw() and other functions
* should respect identifiers with embedded blanks instead of chopping them
*.
* 2001.10.01 Claudio Valderrama: check constraints should fire AFTER the
* BEFORE <action> triggers; otherwise they allow invalid data to be stored.
* This is a quick fix for SF Bug #444463 until a more robust one is devised
* using trigger's rdb$flags or another mechanism.
*
* 2001.10.10 Ann Harrison: Don't increment the format version unless the
* table is actually reformatted. At the same time, break out some of
* the parts of make_version making some new subroutines with the goal
* of making make_version readable.
*
* 2001.10.18 Ann Harrison: some cleanup of trigger & constraint handling.
* it now appears to work correctly on new Firebird databases with lots
* of system types and on InterBase databases, without checking for
* missing source.
*
* 23-Feb-2002 Dmitry Yemanov - Events wildcarding
*
* 2002-02-24 Sean Leyne - Code Cleanup of old Win 3.1 port (WINDOWS_ONLY)
*
* Adriano dos Santos Fernandes
*
*/
#include "firebird.h"
#include "../common/classes/fb_string.h"
#include <stdio.h>
#include <string.h>
#include "../jrd/common.h"
#include <stdarg.h>
#include "../jrd/ibase.h"
#include "../jrd/jrd.h"
#include "../jrd/val.h"
#include "../jrd/irq.h"
#include "../jrd/tra.h"
#include "../jrd/os/pio.h"
#include "../jrd/ods.h"
#include "../jrd/btr.h"
#include "../jrd/req.h"
#include "../jrd/exe.h"
#include "../jrd/scl.h"
#include "../jrd/blb.h"
#include "../jrd/met.h"
#include "../jrd/lck.h"
#include "../jrd/sdw.h"
#include "../jrd/flags.h"
#include "../jrd/all.h"
#include "../jrd/intl.h"
#include "../intl/charsets.h"
#include "../jrd/align.h"
#include "../jrd/gdsassert.h"
#include "../jrd/blb_proto.h"
#include "../jrd/btr_proto.h"
#include "../jrd/cch_proto.h"
#include "../jrd/cmp_proto.h"
#include "../jrd/dfw_proto.h"
#include "../jrd/dpm_proto.h"
#include "../jrd/dsc_proto.h"
#include "../jrd/err_proto.h"
#include "../jrd/exe_proto.h"
#include "../jrd/ext_proto.h"
#include "../jrd/gds_proto.h"
#include "../jrd/grant_proto.h"
#include "../jrd/idx_proto.h"
#include "../jrd/intl_proto.h"
#include "../jrd/isc_f_proto.h"
#include "../jrd/lck_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/mov_proto.h"
#include "../jrd/pag_proto.h"
#include "../jrd/pcmet_proto.h"
#include "../jrd/os/pio_proto.h"
#include "../jrd/rlck_proto.h"
#include "../jrd/sch_proto.h"
#include "../jrd/scl_proto.h"
#include "../jrd/sdw_proto.h"
#include "../jrd/thd.h"
#include "../jrd/thread_proto.h"
#include "../jrd/event_proto.h"
#include "../jrd/nbak.h"
#include "../jrd/trig.h"
#include "../jrd/IntlManager.h"
#include "../common/utils_proto.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "gen/iberror.h"
/* Pick up system relation ids */
#include "../jrd/ini.h"
/* Define range of user relation ids */
const int MIN_RELATION_ID = rel_MAX;
const int MAX_RELATION_ID = 32767;
const int COMPUTED_FLAG = 128;
const int WAIT_PERIOD = -1;
DATABASE DB = FILENAME "ODS.RDB";
using namespace Jrd;
/*==================================================================
**
** NOTE:
**
** The following functions required the same number of
** parameters to be passed.
**
**==================================================================
*/
static bool add_file(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool add_shadow(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_shadow(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool compute_security(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool modify_index(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool create_index(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_index(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool create_procedure(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_procedure(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool modify_procedure(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool create_relation(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_relation(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool scan_relation(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool create_trigger(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_trigger(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool modify_trigger(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool create_collation(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_collation(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_exception(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_generator(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool modify_generator(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_udf(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool create_field(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_field(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool modify_field(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_global(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_parameter(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_rfr(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool make_version(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool add_difference(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_difference(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool begin_backup(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool end_backup(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
/* ---------------------------------------------------------------- */
static void check_dependencies(thread_db*, const TEXT*, const TEXT*, int, jrd_tra*);
static void check_filename(const Firebird::string&, bool);
static void check_system_generator(const TEXT*, const dfw_t);
static bool formatsAreEqual(const Format*, const Format*);
static bool find_depend_in_dfw(thread_db*, TEXT*, USHORT, USHORT, jrd_tra*);
static void get_array_desc(thread_db*, const TEXT*, Ods::InternalArrayDesc*);
static void get_procedure_dependencies(DeferredWork*, bool);
static void get_trigger_dependencies(DeferredWork*, bool);
static void load_trigs(thread_db*, jrd_rel*, trig_vec**);
static Format* make_format(thread_db*, jrd_rel*, USHORT *, TemporaryField*);
static DeferredWork* post_work(jrd_tra*, SLONG, DeferredWork**, enum dfw_t,
const dsc*, USHORT);
static void put_summary_blob(blb*, enum rsr_t, bid*);
static void put_summary_record(blb*, enum rsr_t, const UCHAR*, USHORT);
static void setup_array(thread_db*, blb*, const TEXT*, USHORT, TemporaryField*);
static blb* setup_triggers(thread_db*, jrd_rel*, bool, trig_vec**, blb*);
static void setup_trigger_details(thread_db*, jrd_rel*, blb*, trig_vec**, const TEXT*,
const TEXT*, bool);
static bool validate_text_type (thread_db*, const TemporaryField*);
static Lock* protect_relation(thread_db*, jrd_tra*, jrd_rel*, bool&);
static void release_protect_lock(thread_db*, jrd_tra*, Lock*);
static void check_partners(thread_db*, const USHORT);
static const UCHAR nonnull_validation_blr[] =
{
blr_version5,
blr_not,
blr_missing,
blr_fid, 0, 0, 0,
blr_eoc
};
typedef bool (*dfw_task_routine) (thread_db*, SSHORT, DeferredWork*, jrd_tra*);
struct deferred_task
{
enum dfw_t task_type;
dfw_task_routine task_routine;
};
static const deferred_task task_table[] =
{
{ dfw_add_file, add_file },
{ dfw_add_shadow, add_shadow },
{ dfw_delete_index, modify_index },
{ dfw_delete_expression_index, modify_index },
{ dfw_delete_rfr, delete_rfr },
{ dfw_delete_relation, delete_relation },
{ dfw_delete_shadow, delete_shadow },
{ dfw_create_field, create_field },
{ dfw_delete_field, delete_field },
{ dfw_modify_field, modify_field },
{ dfw_delete_global, delete_global },
{ dfw_create_relation, create_relation },
{ dfw_update_format, make_version },
{ dfw_scan_relation, scan_relation },
{ dfw_compute_security, compute_security },
{ dfw_create_index, modify_index },
{ dfw_create_expression_index, modify_index },
{ dfw_grant, GRANT_privileges },
{ dfw_create_trigger, create_trigger },
{ dfw_delete_trigger, delete_trigger },
{ dfw_modify_trigger, modify_trigger },
{ dfw_create_procedure, create_procedure },
{ dfw_delete_procedure, delete_procedure },
{ dfw_modify_procedure, modify_procedure },
{ dfw_delete_prm, delete_parameter },
{ dfw_create_collation, create_collation },
{ dfw_delete_collation, delete_collation },
{ dfw_delete_exception, delete_exception },
{ dfw_delete_generator, delete_generator },
{ dfw_modify_generator, modify_generator },
{ dfw_delete_udf, delete_udf },
{ dfw_add_difference, add_difference },
{ dfw_delete_difference, delete_difference },
{ dfw_begin_backup, begin_backup },
{ dfw_end_backup, end_backup },
{ dfw_null, NULL }
};
USHORT DFW_assign_index_type(DeferredWork* work, SSHORT field_type, SSHORT ttype)
{
/**************************************
*
* D F W _ a s s i g n _ i n d e x _ t y p e
*
**************************************
*
* Functional description
* Define the index segment type based
* on the field's type and subtype.
*
**************************************/
thread_db* tdbb = JRD_get_thread_data();
if (field_type == dtype_varying ||
field_type == dtype_cstring ||
field_type == dtype_text)
{
switch (ttype)
{
case ttype_none:
return idx_string;
case ttype_binary:
return idx_byte_array;
case ttype_metadata:
return idx_metadata;
case ttype_ascii:
return idx_string;
}
/* Dynamic text cannot occur here as this is for an on-disk
index, which must be bound to a text type. */
fb_assert(ttype != ttype_dynamic);
if (INTL_defined_type(tdbb, ttype))
return INTL_TEXT_TO_INDEX(ttype);
ERR_post_nothrow(isc_no_meta_update,
isc_arg_gds, isc_random, isc_arg_string,
ERR_cstring(work->dfw_name), 0);
INTL_texttype_lookup(tdbb, ttype); // should punt
ERR_punt(); // if INTL_texttype_lookup hasn't punt
}
switch (field_type)
{
case dtype_timestamp:
return idx_timestamp2;
case dtype_sql_date:
return idx_sql_date;
case dtype_sql_time:
return idx_sql_time;
// idx_numeric2 used for 64-bit Integer support
case dtype_int64:
return idx_numeric2;
default:
return idx_numeric;
}
}
void DFW_delete_deferred( jrd_tra* transaction, SLONG sav_number)
{
/**************************************
*
* D F W _ d e l e t e _ d e f e r r e d
*
**************************************
*
* Functional description
* Get rid of work deferred that was to be done at
* COMMIT time as the statement has been rolled back.
*
* if (sav_number == -1), then remove all entries.
*
**************************************/
/* If there is no deferred work, just return */
if (!transaction->tra_deferred_work) {
return;
}
/* Remove deferred work and events which are to be rolled back */
bool deferred_meta = false;
DeferredWork* work;
for (DeferredWork** ptr = &transaction->tra_deferred_work; (work = *ptr);)
{
if ((work->dfw_sav_number == sav_number) || (sav_number == -1))
{
*ptr = work->dfw_next;
delete work;
}
else
{
ptr = &(*ptr)->dfw_next;
if (work->dfw_type != dfw_post_event)
deferred_meta = true;
}
}
if (!deferred_meta) {
transaction->tra_flags &= ~TRA_deferred_meta;
}
}
void DFW_merge_work(jrd_tra* transaction,
SLONG old_sav_number,
SLONG new_sav_number)
{
/**************************************
*
* D F W _ m e r g e _ w o r k
*
**************************************
*
* Functional description
* Merge the deferred work with the previous level. This will
* be called only if there is a previous level.
*
**************************************/
/* If there is no deferred work, just return */
if (!transaction->tra_deferred_work)
return;
/* Decrement the save point number in the deferred block
* i.e. merge with the previous level.
*/
DeferredWork* work;
for (DeferredWork** ptr = &transaction->tra_deferred_work; (work = *ptr);)
{
if (work->dfw_sav_number == old_sav_number)
{
work->dfw_sav_number = new_sav_number;
/* merge this entry with other identical entries at
* same save point level. Start from the beginning and
* stop with the that is being merged.
*/
DeferredWork* work_m;
for (DeferredWork** ptr_m = &transaction->tra_deferred_work;
((work_m = *ptr_m) && (work_m != work));
ptr_m = &(*ptr_m)->dfw_next)
{
if (work_m->dfw_type == work->dfw_type &&
work_m->dfw_id == work->dfw_id &&
work_m->dfw_name == work->dfw_name &&
work_m->dfw_sav_number == work->dfw_sav_number)
{
/* Yes! There is a duplicate entry. Take out the
* entry for which the save point was decremented
*/
*ptr = work->dfw_next;
if (work_m->dfw_name.length()) {
work_m->dfw_count += work->dfw_count;
}
delete work;
work = NULL;
break;
}
}
}
if (work) {
ptr = &(*ptr)->dfw_next;
}
}
}
void DFW_perform_system_work(void)
{
/**************************************
*
* D F W _ p e r f o r m _ s y s t e m _ w o r k
*
**************************************
*
* Functional description
* Flush out the work left to be done in the
* system transaction.
*
**************************************/
Database* dbb = GET_DBB();
DFW_perform_work(dbb->dbb_sys_trans);
}
void DFW_perform_work(jrd_tra* transaction)
{
/**************************************
*
* D F W _ p e r f o r m _ w o r k
*
**************************************
*
* Functional description
* Do work deferred to COMMIT time 'cause that time has
* come.
*
**************************************/
thread_db* tdbb = JRD_get_thread_data();
/* If no deferred work or it's all deferred event posting
don't bother */
if (!transaction->tra_deferred_work ||
!(transaction->tra_flags & TRA_deferred_meta))
{
return;
}
Jrd::ContextPoolHolder context(tdbb, transaction->tra_pool);
/* Loop for as long as any of the deferred work routines says that it has
more to do. A deferred work routine should be able to deal with any
value of phase, either to say that it wants to be called again in the
next phase (by returning true) or that it has nothing more to do in this
or later phases (by returning false). By convention, phase 0 has been
designated as the cleanup phase. If any non-zero phase punts, then phase 0
is executed for all deferred work blocks to cleanup work-in-progress. */
bool dump_shadow = false;
SSHORT phase = 1;
bool more;
ISC_STATUS_ARRAY err_status = {0};
do
{
more = false;
try {
tdbb->tdbb_flags |= TDBB_deferred;
for (const deferred_task* task = task_table;
task->task_type != dfw_null; ++task)
{
for (DeferredWork* work = transaction->tra_deferred_work;
work;
work = work->dfw_next)
{
if (work->dfw_type == task->task_type)
{
if (work->dfw_type == dfw_add_shadow)
{
dump_shadow = true;
}
if ((*task->task_routine)(tdbb, phase, work, transaction))
{
more = true;
}
}
}
}
if (!phase) {
Firebird::stuff_exception(tdbb->tdbb_status_vector,
Firebird::status_exception(err_status, true));
ERR_punt();
}
++phase;
tdbb->tdbb_flags &= ~TDBB_deferred;
}
catch (const Firebird::Exception& ex) {
tdbb->tdbb_flags &= ~TDBB_deferred;
/* Do any necessary cleanup */
if (!phase) {
Firebird::stuff_exception(tdbb->tdbb_status_vector, ex);
ERR_punt();
}
else {
Firebird::stuff_exception(err_status, ex);
}
phase = 0;
more = true;
}
} while (more);
/* Remove deferred work blocks so that system transaction and
commit retaining transactions don't re-execute them. Leave
events to be posted after commit */
DeferredWork* work;
for (DeferredWork** ptr = &transaction->tra_deferred_work; (work = *ptr);)
{
if ((work->dfw_type == dfw_post_event) ||
(work->dfw_type == dfw_delete_shadow))
{
ptr = &(*ptr)->dfw_next;
}
else
{
*ptr = work->dfw_next;
delete work;
}
}
transaction->tra_flags &= ~TRA_deferred_meta;
if (dump_shadow) {
SDW_dump_pages();
}
}
void DFW_perform_post_commit_work(jrd_tra* transaction)
{
/**************************************
*
* D F W _ p e r f o r m _ p o s t _ c o m m i t _ w o r k
*
**************************************
*
* Functional description
* Perform any post commit work
* 1. Post any pending events.
* 2. Unlink shadow files for dropped shadows
*
* Then, delete it from chain of pending work.
*
**************************************/
ISC_STATUS_ARRAY status;
if (!transaction->tra_deferred_work)
return;
bool pending_events = false;
Database* dbb = GET_DBB();
Lock* lock = dbb->dbb_lock;
DeferredWork* work;
for (DeferredWork** ptr = &transaction->tra_deferred_work; (work = *ptr);)
{
if (work->dfw_type == dfw_post_event)
{
EVENT_post( status,
lock->lck_length,
(const TEXT*) & lock->lck_key,
work->dfw_name.length(),
work->dfw_name.c_str(),
work->dfw_count);
*ptr = work->dfw_next;
delete work;
pending_events = true;
}
else if (work->dfw_type == dfw_delete_shadow)
{
unlink(work->dfw_name.c_str());
*ptr = work->dfw_next;
delete work;
}
else
{
ptr = &(*ptr)->dfw_next;
}
}
if (pending_events)
EVENT_deliver();
}
DeferredWork* DFW_post_work(jrd_tra* transaction, enum dfw_t type, const dsc* desc,
USHORT id)
{
/**************************************
*
* D F W _ p o s t _ w o r k
*
**************************************
*
* Functional description
* Post work to be deferred to commit time.
*
**************************************/
/* get the current save point number */
const SLONG sav_number = transaction->tra_save_point ?
transaction->tra_save_point->sav_number : 0;
/* post work */
return post_work(transaction,
sav_number,
&transaction->tra_deferred_work,
type,
desc,
id);
}
DeferredWork* DFW_post_work_arg( jrd_tra* transaction, DeferredWork* work, const dsc* desc,
USHORT id)
{
/**************************************
*
* D F W _ p o s t _ w o r k _ a r g
*
**************************************
*
* Functional description
* Post an argument for work to be deferred to commit time.
*
**************************************/
return post_work(transaction,
work->dfw_sav_number,
&work->dfw_args,
work->dfw_type,
desc,
id);
}
void DFW_update_index(const TEXT* name, USHORT id, const SelectivityList& selectivity)
{
/**************************************
*
* D F W _ u p d a t e _ i n d e x
*
**************************************
*
* Functional description
* Update information in the index relation after creation
* of the index.
*
**************************************/
thread_db* tdbb = JRD_get_thread_data();
Database* dbb = tdbb->getDatabase();
if (dbb->dbb_ods_version >= ODS_VERSION11) {
jrd_req* request = CMP_find_request(tdbb, irq_m_index_seg, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request)
SEG IN RDB$INDEX_SEGMENTS WITH SEG.RDB$INDEX_NAME EQ name
SORTED BY SEG.RDB$FIELD_POSITION
if (!REQUEST(irq_m_index_seg))
REQUEST(irq_m_index_seg) = request;
MODIFY SEG USING
SEG.RDB$STATISTICS = selectivity[SEG.RDB$FIELD_POSITION];
END_MODIFY;
END_FOR;
if (!REQUEST(irq_m_index_seg))
REQUEST(irq_m_index_seg) = request;
}
jrd_req* request = CMP_find_request(tdbb, irq_m_index, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request)
IDX IN RDB$INDICES WITH IDX.RDB$INDEX_NAME EQ name
if (!REQUEST(irq_m_index))
REQUEST(irq_m_index) = request;
MODIFY IDX USING
IDX.RDB$INDEX_ID = id + 1;
IDX.RDB$STATISTICS = selectivity.back();
END_MODIFY;
END_FOR;
if (!REQUEST(irq_m_index))
REQUEST(irq_m_index) = request;
}
static bool add_file(thread_db* tdbb, SSHORT phase, DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* a d d _ f i l e
*
**************************************
*
* Functional description
* Add a file to a database.
* This file could be a regular database
* file or a shadow file. Either way we
* require exclusive access to the database.
*
**************************************/
USHORT section, shadow_number;
SLONG start, max;
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
switch (phase)
{
case 0:
CCH_release_exclusive(tdbb);
return false;
case 1:
case 2:
return true;
case 3:
if (CCH_exclusive(tdbb, LCK_EX, WAIT_PERIOD))
{
return true;
}
else
{
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_lock_timeout,
isc_arg_gds, isc_obj_in_use,
isc_arg_string, ERR_cstring(dbb->dbb_filename.c_str()),
0);
return false;
}
case 4:
CCH_flush(tdbb, FLUSH_FINI, 0L);
max = PageSpace::maxAlloc(dbb) + 1;
jrd_req* handle = 0;
jrd_req* handle2 = 0;
/* Check the file name for node name. This has already
* been done for shadows in add_shadow() */
if (work->dfw_type != dfw_add_shadow) {
check_filename(work->dfw_name, true);
}
/* get any files to extend into */
FOR(REQUEST_HANDLE handle) X IN RDB$FILES
WITH X.RDB$FILE_NAME EQ work->dfw_name.c_str()
/* First expand the file name This has already been done
** for shadows in add_shadow ()) */
if (work->dfw_type != dfw_add_shadow)
{
MODIFY X USING
ISC_expand_filename(X.RDB$FILE_NAME, 0,
X.RDB$FILE_NAME, sizeof(X.RDB$FILE_NAME), false);
END_MODIFY;
}
/* If there is no starting position specified, or if it is
too low a value, make a stab at assigning one based on
the indicated preference for the previous file length. */
if ((start = X.RDB$FILE_START) < max)
{
FOR(REQUEST_HANDLE handle2)
FIRST 1 Y IN RDB$FILES
WITH Y.RDB$SHADOW_NUMBER EQ X.RDB$SHADOW_NUMBER
AND Y.RDB$FILE_SEQUENCE NOT MISSING
SORTED BY DESCENDING Y.RDB$FILE_SEQUENCE
start = Y.RDB$FILE_START + Y.RDB$FILE_LENGTH;
END_FOR;
}
start = MAX(max, start);
shadow_number = X.RDB$SHADOW_NUMBER;
if ((shadow_number &&
(section = SDW_add_file(X.RDB$FILE_NAME,
start,
shadow_number))) ||
(section = PAG_add_file(X.RDB$FILE_NAME, start)))
{
MODIFY X USING
X.RDB$FILE_SEQUENCE = section;
X.RDB$FILE_START = start;
END_MODIFY;
}
END_FOR;
CMP_release(tdbb, handle);
if (handle2)
{
CMP_release(tdbb, handle2);
}
if (section)
{
handle = NULL;
section--;
FOR(REQUEST_HANDLE handle) X IN RDB$FILES
WITH X.RDB$FILE_SEQUENCE EQ section
AND X.RDB$SHADOW_NUMBER EQ shadow_number
MODIFY X USING
X.RDB$FILE_LENGTH = start - X.RDB$FILE_START;
END_MODIFY;
END_FOR;
CMP_release(tdbb, handle);
}
CCH_release_exclusive(tdbb);
break;
}
return false;
}
static bool add_shadow( thread_db* tdbb, SSHORT phase, DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* a d d _ s h a d o w
*
**************************************
*
* Functional description
* A file or files have been added for shadowing.
* Get all files for this particular shadow first
* in order of starting page, if specified, then
* in sequence order.
*
**************************************/
jrd_req* handle;
Shadow* shadow;
USHORT sequence, add_sequence;
bool finished;
ULONG min_page;
Firebird::PathName expanded_fname;
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
switch (phase)
{
case 0:
CCH_release_exclusive(tdbb);
return false;
case 1:
case 2:
case 3:
return true;
case 4:
check_filename(work->dfw_name, false);
/* could have two cases:
1) this shadow has already been written to, so add this file using
the standard routine to extend a database
2) this file is part of a newly added shadow which has already been
fetched in totem and prepared for writing to, so just ignore it
*/
finished = false;
handle = NULL;
FOR(REQUEST_HANDLE handle)
F IN RDB$FILES
WITH F.RDB$FILE_NAME EQ work->dfw_name.c_str()
expanded_fname = F.RDB$FILE_NAME;
ISC_expand_filename(expanded_fname, false);
MODIFY F USING
expanded_fname.copyTo(F.RDB$FILE_NAME, sizeof(F.RDB$FILE_NAME));
END_MODIFY;
for (shadow = dbb->dbb_shadow; shadow; shadow = shadow->sdw_next)
{
if ((F.RDB$SHADOW_NUMBER == shadow->sdw_number) &&
!(shadow->sdw_flags & SDW_IGNORE))
{
if (F.RDB$FILE_FLAGS & FILE_shadow)
{
/* This is the case of a bogus duplicate posted
* work when we added a multi-file shadow
*/
finished = true;
}
else if (shadow->sdw_flags & (SDW_dumped))
{
/* Case of adding a file to a currently active
* shadow set.
* Note: as of 1995-January-31 there is
* no SQL syntax that supports this, but there
* may be GDML
*/
if (!CCH_exclusive(tdbb, LCK_EX, WAIT_PERIOD))
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_lock_timeout,
isc_arg_gds, isc_obj_in_use,
isc_arg_string,
ERR_cstring(dbb->dbb_filename.c_str()),
0);
add_file(tdbb, phase, work, NULL);
finished = true;
}
else
{
/* We cannot add a file to a shadow that is still
* in the process of being created.
*/
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_obj_in_use,
isc_arg_string,
ERR_cstring(dbb->dbb_filename.c_str()),
0);
}
break;
}
}
END_FOR;
CMP_release(tdbb, handle);
if (finished) {
return false;
}
/* this file is part of a new shadow, so get all files for the shadow
in order of the starting page for the file */
/* Note that for a multi-file shadow, we have several pieces of
* work posted (one dfw_add_shadow for each file). Rather than
* trying to cancel the other pieces of work we ignore them
* when they arrive in this routine.
*/
sequence = 0;
min_page = 0;
shadow = NULL;
handle = NULL;
FOR(REQUEST_HANDLE handle)
X IN RDB$FILES CROSS
Y IN RDB$FILES
OVER RDB$SHADOW_NUMBER
WITH X.RDB$FILE_NAME EQ expanded_fname.c_str()
SORTED BY Y.RDB$FILE_START
/* for the first file, create a brand new shadow; for secondary
files that have a starting page specified, add a file */
if (!sequence)
SDW_add(Y.RDB$FILE_NAME, Y.RDB$SHADOW_NUMBER, Y.RDB$FILE_FLAGS);
else if (Y.RDB$FILE_START)
{
if (!shadow)
{
for (shadow = dbb->dbb_shadow;
shadow;
shadow = shadow->sdw_next)
{
if ((Y.RDB$SHADOW_NUMBER == shadow->sdw_number) &&
!(shadow->sdw_flags & SDW_IGNORE))
{
break;
}
}
}
if (!shadow)
BUGCHECK(203); /* msg 203 shadow block not found for extend file */
min_page = MAX((min_page + 1), (ULONG) Y.RDB$FILE_START);
add_sequence =
SDW_add_file(Y.RDB$FILE_NAME,
min_page,
Y.RDB$SHADOW_NUMBER);
}
/* update the sequence number and bless the file entry as being
good */
if (!sequence || (Y.RDB$FILE_START && add_sequence))
{
MODIFY Y
Y.RDB$FILE_FLAGS |= FILE_shadow;
Y.RDB$FILE_SEQUENCE = sequence;
Y.RDB$FILE_START = min_page;
END_MODIFY;
sequence++;
}
END_FOR;
CMP_release(tdbb, handle);
break;
}
return false;
}
static bool add_difference( thread_db* tdbb, SSHORT phase, DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* a d d _ d i f f e r e n c e
*
**************************************
*
* Functional description
* Add backup difference file to the database
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
if (dbb->dbb_ods_version < ODS_VERSION11)
ERR_post(isc_wish_list, 0);
switch (phase)
{
case 0:
return false;
case 1:
case 2:
return true;
case 3:
dbb->dbb_backup_manager->lock_shared_database(tdbb, true);
try {
if (dbb->dbb_backup_manager->get_state() != nbak_state_normal)
{
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_wrong_backup_state, 0);
}
check_filename(work->dfw_name, true);
dbb->dbb_backup_manager->set_difference(tdbb, work->dfw_name.c_str());
dbb->dbb_backup_manager->unlock_shared_database(tdbb);
}
catch (const Firebird::Exception&) {
dbb->dbb_backup_manager->unlock_shared_database(tdbb);
throw;
}
return false;
}
return false;
}
static bool delete_difference( thread_db* tdbb,
SSHORT phase,
DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* d e l e t e _ d i f f e r e n c e
*
**************************************
*
* Delete backup difference file for database
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
if (dbb->dbb_ods_version < ODS_VERSION11)
ERR_post(isc_wish_list, 0);
switch (phase)
{
case 0:
return false;
case 1:
case 2:
return true;
case 3:
dbb->dbb_backup_manager->lock_shared_database(tdbb, true);
try {
if (dbb->dbb_backup_manager->get_state() != nbak_state_normal)
{
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_wrong_backup_state, 0);
}
dbb->dbb_backup_manager->set_difference(tdbb, NULL);
dbb->dbb_backup_manager->unlock_shared_database(tdbb);
}
catch (const Firebird::Exception&) {
dbb->dbb_backup_manager->unlock_shared_database(tdbb);
throw;
}
return false;
}
return false;
}
static bool begin_backup( thread_db* tdbb,
SSHORT phase,
DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* b e g i n _ b a c k u p
*
**************************************
*
* Begin backup storing changed pages in difference file
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
if (dbb->dbb_ods_version < ODS_VERSION11)
ERR_post(isc_wish_list, 0);
switch (phase)
{
case 0:
return false;
case 1:
case 2:
return true;
case 3:
dbb->dbb_backup_manager->begin_backup(tdbb);
return false;
}
return false;
}
static bool end_backup( thread_db* tdbb,
SSHORT phase,
DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* e n d _ b a c k u p
*
**************************************
*
* End backup and merge difference file if neseccary
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
if (dbb->dbb_ods_version < ODS_VERSION11)
ERR_post(isc_wish_list, 0);
switch (phase)
{
case 0:
return false;
case 1:
case 2:
return true;
case 3:
// End backup normally
dbb->dbb_backup_manager->end_backup(tdbb, false);
return false;
}
return false;
}
static void check_dependencies(thread_db* tdbb,
const TEXT* dpdo_name,
const TEXT* field_name,
int dpdo_type,
jrd_tra* transaction)
{
/**************************************
*
* c h e c k _ d e p e n d e n c i e s
*
**************************************
*
* Functional description
* Check the dependency list for relation or relation.field
* before deleting such.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
int i;
SLONG dep_counts[obj_type_MAX];
for (i = 0; i < obj_type_MAX; i++)
dep_counts[i] = 0;
if (field_name)
{
jrd_req* request = CMP_find_request(tdbb, irq_ch_f_dpd, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request)
DEP IN RDB$DEPENDENCIES
WITH DEP.RDB$DEPENDED_ON_NAME EQ dpdo_name
AND DEP.RDB$DEPENDED_ON_TYPE = dpdo_type
AND DEP.RDB$FIELD_NAME EQ field_name
REDUCED TO DEP.RDB$DEPENDENT_NAME
if (!REQUEST(irq_ch_f_dpd))
REQUEST(irq_ch_f_dpd) = request;
/* If the found object is also being deleted, there's no dependency */
if (!find_depend_in_dfw(tdbb,
DEP.RDB$DEPENDENT_NAME,
DEP.RDB$DEPENDENT_TYPE,
0,
transaction))
{
dep_counts[DEP.RDB$DEPENDENT_TYPE]++;
}
END_FOR;
if (!REQUEST(irq_ch_f_dpd))
REQUEST(irq_ch_f_dpd) = request;
}
else
{
jrd_req* request = CMP_find_request(tdbb, irq_ch_dpd, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request)
DEP IN RDB$DEPENDENCIES
WITH DEP.RDB$DEPENDED_ON_NAME EQ dpdo_name
AND DEP.RDB$DEPENDED_ON_TYPE = dpdo_type
REDUCED TO DEP.RDB$DEPENDENT_NAME
if (!REQUEST(irq_ch_dpd))
REQUEST(irq_ch_dpd) = request;
/* If the found object is also being deleted, there's no dependency */
if (!find_depend_in_dfw(tdbb,
DEP.RDB$DEPENDENT_NAME,
DEP.RDB$DEPENDENT_TYPE,
0,
transaction))
{
dep_counts[DEP.RDB$DEPENDENT_TYPE]++;
}
END_FOR;
if (!REQUEST(irq_ch_dpd))
REQUEST(irq_ch_dpd) = request;
}
for (i = 0; i < obj_type_MAX; i++) {
if (dep_counts[i])
{
ISC_STATUS obj_type;
switch (dpdo_type)
{
case obj_relation:
obj_type = isc_table_name;
break;
case obj_procedure:
obj_type = isc_proc_name;
break;
case obj_collation:
obj_type = isc_collation_name;
break;
case obj_exception:
obj_type = isc_exception_name;
break;
case obj_field:
obj_type = isc_domain_name;
break;
case obj_generator:
obj_type = isc_generator_name;
break;
case obj_udf:
obj_type = isc_udf_name;
break;
case obj_index:
obj_type = isc_index_name;
break;
default:
fb_assert(FALSE);
break;
}
if (field_name) {
ERR_post( isc_no_meta_update,
isc_arg_gds, isc_no_delete, /* Msg353: can not delete */
isc_arg_gds, isc_field_name,
isc_arg_string, ERR_cstring(field_name),
isc_arg_gds, isc_dependency,
isc_arg_number, dep_counts[i],
0); /* Msg310: there are %ld dependencies */
}
else {
ERR_post( isc_no_meta_update,
isc_arg_gds, isc_no_delete, /* can not delete */
isc_arg_gds, obj_type,
isc_arg_string, ERR_cstring(dpdo_name),
isc_arg_gds, isc_dependency,
isc_arg_number, dep_counts[i],
0); /* there are %ld dependencies */
}
}
}
}
static void check_filename(const Firebird::string& name, bool shareExpand)
{
/**************************************
*
* c h e c k _ f i l e n a m e
*
**************************************
*
* Functional description
* Make sure that a file path doesn't contain an
* inet node name.
*
**************************************/
const Firebird::PathName file_name(name.ToPathName());
const bool valid = file_name.find("::") == Firebird::PathName::npos;
if (!valid || ISC_check_if_remote(file_name, shareExpand)) {
ERR_post(isc_no_meta_update, isc_arg_gds, isc_node_name_err, 0);
/* Msg305: A node name is not permitted in a secondary, shadow, or log file name */
}
if (!ISC_verify_database_access(file_name)) {
ERR_post(isc_conf_access_denied,
isc_arg_string, "additional database file",
isc_arg_string, ERR_cstring(name),
isc_arg_end);
}
}
static void check_system_generator(const TEXT* gen_name, const dfw_t action)
{
// CVC: Replace this with a call to SCL when we have ACL's for gens.
for (const gen* generator = generators; generator->gen_name; generator++)
if (!strcmp(generator->gen_name, gen_name)) // did we find a sys gen?
ERR_post(isc_no_meta_update,
isc_arg_gds, action == dfw_delete_generator ?
isc_no_delete // Msg353: can not delete
: isc_no_update, // Msg520: can not update
isc_arg_gds, isc_generator_name,
isc_arg_string, ERR_cstring(gen_name),
isc_arg_gds, isc_random,
isc_arg_string, "This is a system generator.",
0);
}
static bool formatsAreEqual(const Format* old_format, const Format* new_format)
{
/**************************************
*
* Functional description
* Compare two format blocks
*
**************************************/
if ((old_format->fmt_length != new_format->fmt_length)
|| (old_format->fmt_count != new_format->fmt_count))
{
return false;
}
Format::fmt_desc_const_iterator old_desc = old_format->fmt_desc.begin();
const Format::fmt_desc_const_iterator old_end = old_format->fmt_desc.end();
Format::fmt_desc_const_iterator new_desc = new_format->fmt_desc.begin();
while (old_desc != old_end)
{
if ((old_desc->dsc_dtype != new_desc->dsc_dtype)
|| (old_desc->dsc_scale != new_desc->dsc_scale)
|| (old_desc->dsc_length != new_desc->dsc_length)
|| (old_desc->dsc_sub_type != new_desc->dsc_sub_type)
|| (old_desc->dsc_flags != new_desc->dsc_flags)
|| (old_desc->dsc_address != new_desc->dsc_address))
{
return false;
}
++new_desc;
++old_desc;
}
return true;
}
static bool compute_security( thread_db* tdbb,
SSHORT phase,
DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* c o m p u t e _ s e c u r i t y
*
**************************************
*
* Functional description
* There was a change in a security class. Recompute everything
* it touches.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
switch (phase)
{
case 1:
case 2:
return true;
case 3:
{
/* Get security class. This may return NULL if it doesn't exist */
SecurityClass* s_class = SCL_recompute_class(tdbb, work->dfw_name.c_str());
jrd_req* handle = NULL;
FOR(REQUEST_HANDLE handle) X IN RDB$DATABASE
WITH X.RDB$SECURITY_CLASS EQ work->dfw_name.c_str()
tdbb->getAttachment()->att_security_class = s_class;
END_FOR;
CMP_release(tdbb, handle);
break;
}
}
return false;
}
static bool modify_index( thread_db* tdbb,
SSHORT phase,
DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* m o d i f y _ i n d e x
*
**************************************
*
* Functional description
* Create\drop an index or change the state of an index between active/inactive.
* If index owns by global temporary table with on commit preserve rows scope
* change index instance for this temporary table too. For "create index" work
* item create base index instance before temp index instance. For index
* deletion delete temp index instance first to release index usage counter
* before deletion of base index instance.
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
const bool have_gtt =
(ENCODE_ODS(dbb->dbb_ods_version, dbb->dbb_minor_version) >= ODS_11_1);
bool is_create = true;
dfw_task_routine task_routine = NULL;
switch (work->dfw_type)
{
case dfw_create_index :
task_routine = create_index;
break;
case dfw_create_expression_index :
task_routine = PCMET_expression_index;
break;
case dfw_delete_index :
case dfw_delete_expression_index :
task_routine = delete_index;
is_create = false;
break;
};
fb_assert(task_routine);
bool more = false, more2 = false;
if (is_create) {
more = (*task_routine)(tdbb, phase, work, transaction);
}
if (have_gtt)
{
bool gtt_preserve = false;
jrd_rel* relation = NULL;
if (is_create)
{
jrd_req* request = NULL;
FOR(REQUEST_HANDLE request)
IDX IN RDB$INDICES CROSS
REL IN RDB$RELATIONS OVER RDB$RELATION_NAME WITH
IDX.RDB$INDEX_NAME EQ work->dfw_name.c_str()
gtt_preserve = (REL.RDB$RELATION_TYPE == rel_global_temp_preserve);
relation = MET_lookup_relation(tdbb, REL.RDB$RELATION_NAME);
END_FOR;
CMP_release(tdbb, request);
}
else
{
relation = MET_lookup_relation_id(tdbb, work->dfw_id, false);
gtt_preserve = (relation) && (relation->rel_flags & REL_temp_conn);
}
if (gtt_preserve && relation)
{
tdbb->tdbb_flags &= ~TDBB_deferred;
try {
if (relation->getPages(tdbb, -1, false)) {
more2 = (*task_routine) (tdbb, phase, work, transaction);
}
tdbb->tdbb_flags |= TDBB_deferred;
}
catch (...)
{
tdbb->tdbb_flags |= TDBB_deferred;
throw;
}
}
}
if (!is_create) {
more = (*task_routine)(tdbb, phase, work, transaction);
}
return (more || more2);
}
static bool create_index( thread_db* tdbb,
SSHORT phase,
DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* c r e a t e _ i n d e x
*
**************************************
*
* Functional description
* Create a new index or change the state of an index between active/inactive.
*
**************************************/
jrd_req* request;
jrd_rel* relation;
jrd_rel* partner_relation;
index_desc idx;
int key_count;
jrd_req* handle;
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
switch (phase)
{
case 0:
handle = NULL;
/* Drop those indices at clean up time. */
FOR(REQUEST_HANDLE handle) IDXN IN RDB$INDICES CROSS
IREL IN RDB$RELATIONS OVER RDB$RELATION_NAME
WITH IDXN.RDB$INDEX_NAME EQ work->dfw_name.c_str()
/* Views do not have indices */
if (IREL.RDB$VIEW_BLR.NULL)
{
relation = MET_lookup_relation(tdbb, IDXN.RDB$RELATION_NAME);
RelationPages* relPages = relation->getPages(tdbb, -1, false);
if (!relPages) {
return false;
}
// we need to special handle temp tables with ON PRESERVE ROWS only
const bool isTempIndex = (relation->rel_flags & REL_temp_conn) &&
(relPages->rel_instance_id != 0);
/* Fetch the root index page and mark MUST_WRITE, and then
delete the index. It will also clean the index slot. Note
that the previous fixed definition of MAX_IDX (64) has been
dropped in favor of a computed value saved in the Database */
if (relPages->rel_index_root)
{
if (work->dfw_id != dbb->dbb_max_idx)
{
WIN window(relPages->rel_pg_space_id, relPages->rel_index_root);
CCH_FETCH(tdbb, &window, LCK_write, pag_root);
CCH_MARK_MUST_WRITE(tdbb, &window);
const bool tree_exists = BTR_delete_index(tdbb, &window, work->dfw_id);
if (!isTempIndex) {
work->dfw_id = dbb->dbb_max_idx;
}
else if (tree_exists)
{
IndexLock* idx_lock = CMP_get_index_lock(tdbb, relation, work->dfw_id);
if (idx_lock)
{
if (!--idx_lock->idl_count) {
LCK_release(tdbb, idx_lock->idl_lock);
}
}
}
}
if (!IDXN.RDB$INDEX_ID.NULL)
{
MODIFY IDXN USING
IDXN.RDB$INDEX_ID.NULL = TRUE;
END_MODIFY;
}
}
}
END_FOR;
CMP_release(tdbb, handle);
return false;
case 1:
case 2:
return true;
case 3:
key_count = 0;
relation = NULL;
idx.idx_flags = 0;
/* Fetch the information necessary to create the index. On the first
time thru, check to see if the index already exists. If so, delete
it. If the index inactive flag is set, don't create the index */
request = CMP_find_request(tdbb, irq_c_index, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request)
IDX IN RDB$INDICES CROSS
SEG IN RDB$INDEX_SEGMENTS OVER RDB$INDEX_NAME CROSS
RFR IN RDB$RELATION_FIELDS OVER RDB$FIELD_NAME,
RDB$RELATION_NAME CROSS
FLD IN RDB$FIELDS CROSS
REL IN RDB$RELATIONS OVER RDB$RELATION_NAME WITH
FLD.RDB$FIELD_NAME EQ RFR.RDB$FIELD_SOURCE AND
IDX.RDB$INDEX_NAME EQ work->dfw_name.c_str()
if (!REQUEST(irq_c_index))
REQUEST(irq_c_index) = request;
if (!relation)
{
relation =
MET_lookup_relation_id(tdbb, REL.RDB$RELATION_ID, false);
if (!relation)
{
EXE_unwind(tdbb, request);
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_idx_create_err,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
/* Msg308: can't create index %s */
}
if (IDX.RDB$INDEX_ID && IDX.RDB$STATISTICS < 0.0)
{
SelectivityList selectivity(*tdbb->getDefaultPool());
const USHORT id = IDX.RDB$INDEX_ID - 1;
IDX_statistics(tdbb, relation, id, selectivity);
DFW_update_index(work->dfw_name.c_str(), id, selectivity);
EXE_unwind(tdbb, request);
return false;
}
if (IDX.RDB$INDEX_ID)
{
IDX_delete_index( tdbb,
relation,
(USHORT)(IDX.RDB$INDEX_ID - 1));
MODIFY IDX
IDX.RDB$INDEX_ID.NULL = TRUE;
END_MODIFY;
}
if (IDX.RDB$INDEX_INACTIVE)
{
EXE_unwind(tdbb, request);
return false;
}
idx.idx_count = IDX.RDB$SEGMENT_COUNT;
if (!idx.idx_count || idx.idx_count > MAX_INDEX_SEGMENTS)
{
EXE_unwind(tdbb, request);
if (!idx.idx_count)
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_idx_seg_err,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
/* Msg304: segment count of 0 defined for index %s */
else
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_idx_key_err,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
/* Msg311: too many keys defined for index %s */
}
if (IDX.RDB$UNIQUE_FLAG)
idx.idx_flags |= idx_unique;
if (IDX.RDB$INDEX_TYPE == 1)
idx.idx_flags |= idx_descending;
if (!IDX.RDB$FOREIGN_KEY.NULL)
idx.idx_flags |= idx_foreign;
jrd_req* rc_request = NULL;
FOR(REQUEST_HANDLE rc_request)
RC IN RDB$RELATION_CONSTRAINTS WITH
RC.RDB$INDEX_NAME EQ work->dfw_name.c_str() AND
RC.RDB$CONSTRAINT_TYPE = PRIMARY_KEY
idx.idx_flags |= idx_primary;
END_FOR;
CMP_release(tdbb, rc_request);
}
if (++key_count > idx.idx_count ||
SEG.RDB$FIELD_POSITION > idx.idx_count ||
FLD.RDB$FIELD_TYPE == blr_blob ||
!FLD.RDB$DIMENSIONS.NULL)
{
EXE_unwind(tdbb, request);
if (key_count > idx.idx_count)
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_idx_key_err,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
/* Msg311: too many keys defined for index %s */
else if (SEG.RDB$FIELD_POSITION > idx.idx_count)
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_inval_key_posn,
/* Msg358: invalid key position */
isc_arg_gds, isc_field_name,
isc_arg_string, ERR_cstring(RFR.RDB$FIELD_NAME),
isc_arg_gds, isc_index_name,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
else if (FLD.RDB$FIELD_TYPE == blr_blob)
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_blob_idx_err,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
/* Msg350: attempt to index blob column in index %s */
else
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_array_idx_err,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
/* Msg351: attempt to index array column in index %s */
}
idx.idx_rpt[SEG.RDB$FIELD_POSITION].idx_field = RFR.RDB$FIELD_ID;
if (FLD.RDB$CHARACTER_SET_ID.NULL)
FLD.RDB$CHARACTER_SET_ID = CS_NONE;
SSHORT collate;
if (!RFR.RDB$COLLATION_ID.NULL)
collate = RFR.RDB$COLLATION_ID;
else if (!FLD.RDB$COLLATION_ID.NULL)
collate = FLD.RDB$COLLATION_ID;
else
collate = COLLATE_NONE;
const SSHORT text_type =
INTL_CS_COLL_TO_TTYPE(FLD.RDB$CHARACTER_SET_ID, collate);
idx.idx_rpt[SEG.RDB$FIELD_POSITION].idx_itype =
DFW_assign_index_type(work,
gds_cvt_blr_dtype[FLD.RDB$FIELD_TYPE],
text_type);
// Initialize selectivity to zero. Otherwise random rubbish makes its way into database
idx.idx_rpt[SEG.RDB$FIELD_POSITION].idx_selectivity = 0;
END_FOR;
if (!REQUEST(irq_c_index))
REQUEST(irq_c_index) = request;
if (key_count != idx.idx_count)
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_key_field_err,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
/* Msg352: too few key columns found for index %s (incorrect column name?) */
if (!relation)
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_idx_create_err,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
/* Msg308: can't create index %s */
/* Make sure the relation info is all current */
MET_scan_relation(tdbb, relation);
if (relation->rel_view_rse)
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_idx_create_err,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
/* Msg308: can't create index %s */
/* Actually create the index */
Lock *relationLock = NULL, *partnerLock = NULL;
bool releaseRelationLock = false, releasePartnerLock = false;
partner_relation = NULL;
try
{
// Protect relation from modification to create consistent index
relationLock =
protect_relation(tdbb, transaction, relation, releaseRelationLock);
if (idx.idx_flags & idx_foreign)
{
idx.idx_id = MAX_USHORT;
if (MET_lookup_partner(tdbb, relation, &idx, work->dfw_name.c_str()))
{
partner_relation =
MET_lookup_relation_id(tdbb, idx.idx_primary_relation, true);
}
if (!partner_relation)
{
Firebird::MetaName constraint_name;
MET_lookup_cnstrt_for_index(tdbb, constraint_name, work->dfw_name);
ERR_post(isc_partner_idx_not_found,
isc_arg_string, ERR_cstring(constraint_name), 0);
}
// Get an protected_read lock on the both relations if the index being
// defined enforces a foreign key constraint. This will prevent
// the constraint from being violated during index construction.
partnerLock =
protect_relation(tdbb, transaction,
partner_relation, releasePartnerLock);
int bad_segment;
if (!IDX_check_master_types(tdbb, idx, partner_relation, bad_segment))
{
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_partner_idx_incompat_type,
isc_arg_number, bad_segment + 1,
0);
}
/* hvlad: this code was never called but i preserve it for Claudio review and decision
// CVC: Currently, the server doesn't enforce FK creation more than at DYN level.
// If DYN is bypassed, then FK creation succeeds and operation will fail at run-time.
// The aim is to check REFERENCES at DDL time instead of DML time and behave accordingly
// to ANSI SQL rules for REFERENCES rights.
// For testing purposes, I'm calling SCL_check_index, although most of the DFW ops are
// carried using internal metadata structures that are refreshed from system tables.
// Don't bother if the master's owner is the same than the detail's owner.
// If both tables aren't defined in the same session, partner_relation->rel_owner_name
// won't be loaded hence, we need to be careful about null pointers.
if (relation->rel_owner_name.length() == 0 ||
partner_relation->rel_owner_name.length() == 0 ||
relation->rel_owner_name != partner_relation->rel_owner_name)
{
SCL_check_index(tdbb, partner_relation->rel_name,
idx.idx_id + 1, SCL_sql_references);
}
*/
}
fb_assert(work->dfw_id <= dbb->dbb_max_idx);
idx.idx_id = work->dfw_id;
SelectivityList selectivity(*tdbb->getDefaultPool());
IDX_create_index(tdbb, relation, &idx, work->dfw_name.c_str(),
&work->dfw_id, transaction, selectivity);
fb_assert(work->dfw_id == idx.idx_id);
DFW_update_index(work->dfw_name.c_str(), idx.idx_id, selectivity);
if (partner_relation)
{
relation->rel_flags |= REL_check_partners;
// signal to other processes about new constraint
LCK_convert_non_blocking(tdbb, relation->rel_partners_lock,
LCK_EX, LCK_WAIT);
LCK_release(tdbb, relation->rel_partners_lock);
if (relation != partner_relation) {
partner_relation->rel_flags |= REL_check_partners;
LCK_convert_non_blocking(tdbb, partner_relation->rel_partners_lock,
LCK_EX, LCK_WAIT);
LCK_release(tdbb, partner_relation->rel_partners_lock);
}
}
if (relationLock && releaseRelationLock) {
release_protect_lock(tdbb, transaction, relationLock);
}
if (partnerLock && releasePartnerLock) {
release_protect_lock(tdbb, transaction, partnerLock);
}
}
catch (const Firebird::Exception&)
{
if (relationLock && releaseRelationLock) {
release_protect_lock(tdbb, transaction, relationLock);
}
if (partnerLock && releasePartnerLock) {
release_protect_lock(tdbb, transaction, partnerLock);
}
throw;
}
break;
}
return false;
}
static bool create_procedure( thread_db* tdbb,
SSHORT phase,
DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* c r e a t e _ p r o c e d u r e
*
**************************************
*
* Functional description
* Create a new procedure.
*
**************************************/
SET_TDBB(tdbb);
switch (phase)
{
case 1:
case 2:
return true;
case 3:
{
const DeferredWork* arg = work->dfw_args;
while (arg && (arg->dfw_type != dfw_arg_check_blr))
arg = arg->dfw_next;
get_procedure_dependencies(work, arg == NULL);
jrd_prc* procedure = MET_lookup_procedure(tdbb, work->dfw_name, (arg ? false : true));
if (!procedure) {
return false;
}
// Never used.
procedure->prc_flags |= PRC_create;
}
break;
}
return false;
}
static bool create_relation(thread_db* tdbb,
SSHORT phase,
DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* c r e a t e _ r e l a t i o n
*
**************************************
*
* Functional description
* Create a new relation.
*
**************************************/
jrd_req* request;
jrd_rel* relation;
USHORT rel_id, external_flag;
bid blob_id;
jrd_req* handle;
Lock* lock;
blob_id.clear();
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
const USHORT major_version = dbb->dbb_ods_version;
const USHORT minor_original = dbb->dbb_minor_original;
USHORT local_min_relation_id;
if (ENCODE_ODS(major_version, minor_original) < ODS_9_0)
local_min_relation_id = MIN_RELATION_ID;
else
local_min_relation_id = USER_DEF_REL_INIT_ID;
switch (phase)
{
case 0:
if (work->dfw_lock)
{
LCK_release(tdbb, work->dfw_lock);
delete work->dfw_lock;
work->dfw_lock = NULL;
}
break;
case 1:
case 2:
return true;
case 3:
/* Take a relation lock on rel id -1 before actually
generating a relation id. */
work->dfw_lock = lock = FB_NEW_RPT(*tdbb->getDefaultPool(), sizeof(SLONG)) Lock;
lock->lck_dbb = dbb;
lock->lck_length = sizeof(SLONG);
lock->lck_key.lck_long = -1;
lock->lck_type = LCK_relation;
lock->lck_owner_handle = LCK_get_owner_handle(tdbb, lock->lck_type);
lock->lck_parent = dbb->dbb_lock;
lock->lck_owner = tdbb->getAttachment();
LCK_lock_non_blocking(tdbb, lock, LCK_EX, LCK_WAIT);
/* Assign a relation ID and dbkey length to the new relation.
Probe the candidate relation ID returned from the system
relation RDB$DATABASE to make sure it isn't already assigned.
This can happen from nefarious manipulation of RDB$DATABASE
or wraparound of the next relation ID. Keep looking for a
usable relation ID until the search space is exhausted. */
rel_id = 0;
request = CMP_find_request(tdbb, irq_c_relation, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request)
X IN RDB$DATABASE CROSS Y IN RDB$RELATIONS WITH
Y.RDB$RELATION_NAME EQ work->dfw_name.c_str()
if (!REQUEST(irq_c_relation))
REQUEST(irq_c_relation) = request;
blob_id = Y.RDB$VIEW_BLR;
external_flag = Y.RDB$EXTERNAL_FILE[0];
MODIFY X USING
rel_id = X.RDB$RELATION_ID;
if (rel_id < local_min_relation_id ||
rel_id > MAX_RELATION_ID)
{
rel_id = X.RDB$RELATION_ID = local_min_relation_id;
}
while ( (relation = MET_lookup_relation_id(tdbb, rel_id++, false)) )
{
if (rel_id < local_min_relation_id ||
rel_id > MAX_RELATION_ID)
{
rel_id = local_min_relation_id;
}
if (rel_id == X.RDB$RELATION_ID)
{
EXE_unwind(tdbb, request);
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_table_name,
isc_arg_string, ERR_cstring(work->dfw_name),
isc_arg_gds, isc_imp_exc,
0);
}
}
X.RDB$RELATION_ID = (rel_id > MAX_RELATION_ID) ?
local_min_relation_id : rel_id;
MODIFY Y USING
Y.RDB$RELATION_ID = --rel_id;
if (blob_id.isEmpty())
Y.RDB$DBKEY_LENGTH = 8;
else
{
/* update the dbkey length to include each of the base
relations */
handle = NULL;
Y.RDB$DBKEY_LENGTH = 0;
FOR(REQUEST_HANDLE handle)
Z IN RDB$VIEW_RELATIONS CROSS
R IN RDB$RELATIONS OVER RDB$RELATION_NAME
WITH Z.RDB$VIEW_NAME = work->dfw_name.c_str()
Y.RDB$DBKEY_LENGTH += R.RDB$DBKEY_LENGTH;
END_FOR;
CMP_release(tdbb, handle);
}
END_MODIFY
END_MODIFY
END_FOR;
LCK_release(tdbb, lock);
delete lock;
work->dfw_lock = NULL;
if (!REQUEST(irq_c_relation))
REQUEST(irq_c_relation) = request;
/* if this is not a view, create the relation */
if (rel_id && blob_id.isEmpty() && !external_flag)
{
relation = MET_relation(tdbb, rel_id);
DPM_create_relation(tdbb, relation);
}
return true;
case 4:
/* get the relation and flag it to check for dependencies
in the view blr (if it exists) and any computed fields */
request = CMP_find_request(tdbb, irq_c_relation2, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request)
X IN RDB$RELATIONS WITH
X.RDB$RELATION_NAME EQ work->dfw_name.c_str()
if (!REQUEST(irq_c_relation2))
REQUEST(irq_c_relation2) = request;
rel_id = X.RDB$RELATION_ID;
relation = MET_relation(tdbb, rel_id);
relation->rel_flags |= REL_get_dependencies;
relation->rel_flags &= ~REL_scanned;
DFW_post_work(transaction, dfw_scan_relation, NULL, rel_id);
END_FOR;
if (!REQUEST(irq_c_relation2))
REQUEST(irq_c_relation2) = request;
break;
}
return false;
}
static bool create_trigger(thread_db* tdbb, SSHORT phase, DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* c r e a t e _ t r i g g e r
*
**************************************
*
* Functional description
* Perform required actions on creation of trigger.
*
**************************************/
SET_TDBB(tdbb);
switch (phase)
{
case 1:
case 2:
return true;
case 3:
{
const DeferredWork* arg = work->dfw_args;
while (arg && (arg->dfw_type != dfw_arg_check_blr))
arg = arg->dfw_next;
get_trigger_dependencies(work, arg == NULL);
return true;
}
case 4:
{
const DeferredWork* arg = work->dfw_args;
while (arg && (arg->dfw_type != dfw_arg_rel_name))
arg = arg->dfw_next;
if (!arg)
{
arg = work->dfw_args;
while (arg && (arg->dfw_type != dfw_arg_trg_type))
arg = arg->dfw_next;
fb_assert(arg);
if (arg && (arg->dfw_id & TRIGGER_TYPE_MASK) == TRIGGER_TYPE_DB)
{
MET_load_trigger(tdbb, NULL, work->dfw_name,
&tdbb->getDatabase()->dbb_triggers[arg->dfw_id & ~TRIGGER_TYPE_DB]);
}
}
}
break;
}
return false;
}
static bool create_collation(thread_db* tdbb, SSHORT phase, DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* c r e a t e _ c o l l a t i o n
*
**************************************
*
* Functional description
* Get collation id or setup specific attributes.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
switch (phase)
{
case 0:
return false;
case 1:
{
const USHORT charSetId = TTYPE_TO_CHARSET(work->dfw_id);
jrd_req* handle = NULL;
FOR(REQUEST_HANDLE handle)
COLL IN RDB$COLLATIONS
CROSS CS IN RDB$CHARACTER_SETS
OVER RDB$CHARACTER_SET_ID
WITH COLL.RDB$COLLATION_NAME EQ work->dfw_name.c_str() AND
COLL.RDB$CHARACTER_SET_ID EQ charSetId
if (COLL.RDB$COLLATION_ID.NULL)
{
// ASF: User collations are created with the last number available,
// to minimize the possibility of conflicts with future system collations.
// The greater available number is 126 to avoid signed/unsigned problems.
SSHORT id = 126;
jrd_req* request = CMP_find_request(tdbb, irq_l_colls, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request)
Y IN RDB$COLLATIONS
WITH Y.RDB$CHARACTER_SET_ID = COLL.RDB$CHARACTER_SET_ID
SORTED BY DESCENDING Y.RDB$COLLATION_ID
if (!REQUEST(irq_l_colls))
REQUEST(irq_l_colls) = request;
if (!COLL.RDB$COLLATION_ID.NULL)
{
EXE_unwind(tdbb, request);
break;
}
if (Y.RDB$COLLATION_ID + 1 <= id)
COLL.RDB$COLLATION_ID.NULL = FALSE;
else
id = Y.RDB$COLLATION_ID - 1;
END_FOR
if (!REQUEST(irq_l_colls))
REQUEST(irq_l_colls) = request;
if (COLL.RDB$COLLATION_ID.NULL)
{
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_max_coll_per_charset,
0);
}
else
{
MODIFY COLL USING
COLL.RDB$COLLATION_ID = id;
END_MODIFY
}
}
else
{
SLONG length = 0;
Firebird::HalfStaticArray<UCHAR, BUFFER_SMALL> buffer;
if (!COLL.RDB$SPECIFIC_ATTRIBUTES.NULL)
{
blb* blob = BLB_open(tdbb, dbb->dbb_sys_trans, &COLL.RDB$SPECIFIC_ATTRIBUTES);
length = blob->blb_length + 10;
length = BLB_get_data(tdbb, blob, buffer.getBuffer(length), length);
}
const Firebird::string specificAttributes((const char*) buffer.begin(), length);
Firebird::string newSpecificAttributes;
// ASF: If setupCollationAttributes fail we store the original
// attributes. This should be what we want for new databases
// and restores. CREATE COLLATION will fail in DYN.
if (IntlManager::setupCollationAttributes(
fb_utils::exact_name(COLL.RDB$BASE_COLLATION_NAME.NULL ?
COLL.RDB$COLLATION_NAME : COLL.RDB$BASE_COLLATION_NAME),
fb_utils::exact_name(CS.RDB$CHARACTER_SET_NAME),
specificAttributes, newSpecificAttributes))
{
// if nothing changed, we do nothing
if (newSpecificAttributes != specificAttributes)
{
MODIFY COLL USING
if (newSpecificAttributes.isEmpty())
COLL.RDB$SPECIFIC_ATTRIBUTES.NULL = TRUE;
else
{
COLL.RDB$SPECIFIC_ATTRIBUTES.NULL = FALSE;
blb* blob = BLB_create(tdbb, dbb->dbb_sys_trans, &COLL.RDB$SPECIFIC_ATTRIBUTES);
BLB_put_segment(tdbb, blob,
(const UCHAR*) newSpecificAttributes.begin(),
newSpecificAttributes.length());
BLB_close(tdbb, blob);
}
END_MODIFY;
}
}
}
END_FOR;
CMP_release(tdbb, handle);
}
return true;
}
return false;
}
static bool delete_collation(thread_db* tdbb, SSHORT phase, DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* d e l e t e _ c o l l a t i o n
*
**************************************
*
* Functional description
* Check if it is allowable to delete
* a collation, and if so, clean up after it.
*
**************************************/
SET_TDBB(tdbb);
switch (phase)
{
case 0:
return false;
case 1:
check_dependencies(tdbb, work->dfw_name.c_str(), NULL,
obj_collation, transaction);
return true;
case 2:
return true;
case 3:
INTL_texttype_unload(tdbb, work->dfw_id);
return true;
}
return false;
}
static bool delete_exception( thread_db* tdbb,
SSHORT phase,
DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* d e l e t e _ e x c e p t i o n
*
**************************************
*
* Functional description
* Check if it is allowable to delete
* an exception, and if so, clean up after it.
*
**************************************/
SET_TDBB(tdbb);
switch (phase)
{
case 0:
return false;
case 1:
check_dependencies(tdbb, work->dfw_name.c_str(), NULL,
obj_exception, transaction);
return true;
case 2:
return true;
case 3:
return true;
case 4:
break;
}
return false;
}
static bool delete_generator(thread_db* tdbb, SSHORT phase, DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* d e l e t e _ g e n e r a t o r
*
**************************************
*
* Functional description
* Check if it is allowable to delete
* a generator, and if so, clean up after it.
* CVC: This function was modelled after delete_exception.
*
**************************************/
SET_TDBB(tdbb);
const char* gen_name = work->dfw_name.c_str();
switch (phase)
{
case 0:
return false;
case 1:
check_system_generator(gen_name, dfw_delete_generator);
check_dependencies(tdbb, gen_name, 0,
obj_generator, transaction);
return true;
case 2:
return true;
case 3:
return true;
case 4:
break;
}
return false;
}
static bool modify_generator(thread_db* tdbb, SSHORT phase, DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* m o d i f y _ g e n e r a t o r
*
**************************************
*
* Functional description
* Check if it is allowable to modify
* a generator's information in rdb$generators.
* CVC: For now, the function always forbids this operation.
* This has nothing to do with gen_id or set generator.
*
**************************************/
SET_TDBB(tdbb);
const char* gen_name = work->dfw_name.c_str();
switch (phase)
{
case 0:
return false;
case 1:
check_system_generator(gen_name, dfw_modify_generator);
if (work->dfw_id) // != 0 means not only the desc was changed.
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_generator_name,
isc_arg_string, ERR_cstring(gen_name),
isc_arg_gds, isc_random,
isc_arg_string, "Only can modify description for user generators.",
0);
return true;
case 2:
return true;
case 3:
return true;
case 4:
break;
}
return false;
}
static bool delete_udf(thread_db* tdbb, SSHORT phase, DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* d e l e t e _ u d f
*
**************************************
*
* Functional description
* Check if it is allowable to delete
* an udf, and if so, clean up after it.
* CVC: This function was modelled after delete_exception.
*
**************************************/
SET_TDBB(tdbb);
switch (phase)
{
case 0:
return false;
case 1:
check_dependencies(tdbb, work->dfw_name.c_str(), 0,
obj_udf, transaction);
return true;
case 2:
return true;
case 3:
return true;
case 4:
break;
}
return false;
}
static bool create_field(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
*
* c r e a t e _ f i e l d
*
**************************************
*
* Functional description
* Store dependencies of a field.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
switch (phase)
{
case 0:
return false;
case 1:
{
Firebird::MetaName depName(work->dfw_name);
jrd_req* handle = NULL;
bid validation;
validation.clear();
FOR(REQUEST_HANDLE handle)
FLD IN RDB$FIELDS WITH
FLD.RDB$FIELD_NAME EQ depName.c_str()
if (!FLD.RDB$VALIDATION_BLR.NULL)
validation = FLD.RDB$VALIDATION_BLR;
END_FOR;
CMP_release(tdbb, handle);
if (!validation.isEmpty())
{
JrdMemoryPool* new_pool = JrdMemoryPool::createPool();
{
Jrd::ContextPoolHolder context(tdbb, new_pool);
MET_get_dependencies(tdbb, NULL, NULL, NULL, &validation,
NULL, NULL, depName, obj_validation, 0, depName);
JrdMemoryPool::deletePool(new_pool);
}
}
}
break;
}
return false;
}
static bool delete_field( thread_db* tdbb,
SSHORT phase,
DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* d e l e t e _ f i e l d
*
**************************************
*
* Functional description
* This whole routine exists just to
* return an error if someone attempts to
* delete a global field that is in use
*
**************************************/
int field_count;
jrd_req* handle;
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
switch (phase)
{
case 1:
/* Look up the field in RFR. If we can't find the field,
go ahead with the delete. */
handle = NULL;
field_count = 0;
FOR(REQUEST_HANDLE handle)
RFR IN RDB$RELATION_FIELDS CROSS
REL IN RDB$RELATIONS
OVER RDB$RELATION_NAME
WITH RFR.RDB$FIELD_SOURCE EQ work->dfw_name.c_str()
/* If the rfr field is also being deleted, there's no dependency */
if (!find_depend_in_dfw(tdbb,
RFR.RDB$FIELD_NAME,
obj_computed,
REL.RDB$RELATION_ID,
transaction))
{
field_count++;
}
END_FOR;
CMP_release(tdbb, handle);
if (field_count)
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_no_delete, /* Msg353: can not delete */
isc_arg_gds, isc_domain_name,
isc_arg_string, ERR_cstring(work->dfw_name),
isc_arg_gds, isc_dependency,
isc_arg_number, (SLONG) field_count,
0); /* Msg310: there are %ld dependencies */
check_dependencies(tdbb, work->dfw_name.c_str(), NULL,
obj_field, transaction);
case 2:
return true;
case 3:
MET_delete_dependencies(tdbb, work->dfw_name, obj_computed);
MET_delete_dependencies(tdbb, work->dfw_name, obj_validation);
break;
}
return false;
}
static bool modify_field(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
*
* m o d i f y _ f i e l d
*
**************************************
*
* Functional description
* Handle constraint dependencies of a field.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
switch (phase)
{
case 0:
return false;
case 1:
{
Firebird::MetaName depName(work->dfw_name);
jrd_req* handle = NULL;
bid validation;
validation.clear();
FOR(REQUEST_HANDLE handle)
FLD IN RDB$FIELDS WITH
FLD.RDB$FIELD_NAME EQ depName.c_str()
if (!FLD.RDB$VALIDATION_BLR.NULL)
validation = FLD.RDB$VALIDATION_BLR;
END_FOR;
CMP_release(tdbb, handle);
if (validation.isEmpty())
MET_delete_dependencies(tdbb, depName, obj_validation);
else
{
JrdMemoryPool* new_pool = JrdMemoryPool::createPool();
{
Jrd::ContextPoolHolder context(tdbb, new_pool);
MET_get_dependencies(tdbb, NULL, NULL, NULL, &validation,
NULL, NULL, depName, obj_validation, 0, depName);
JrdMemoryPool::deletePool(new_pool);
}
}
}
break;
}
return false;
}
static bool delete_global(thread_db* tdbb, SSHORT phase, DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* d e l e t e _ g l o b a l
*
**************************************
*
* Functional description
* If a local field has been deleted,
* check to see if its global field
* is computed. If so, delete all its
* dependencies under the assumption
* that a global computed field has only
* one local field.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
switch (phase)
{
case 1:
case 2:
return true;
case 3:
{
jrd_req* handle = NULL;
FOR(REQUEST_HANDLE handle)
FLD IN RDB$FIELDS WITH
FLD.RDB$FIELD_NAME EQ work->dfw_name.c_str() AND
FLD.RDB$COMPUTED_BLR NOT MISSING
MET_delete_dependencies(tdbb, work->dfw_name, obj_computed);
END_FOR;
CMP_release(tdbb, handle);
break;
}
}
return false;
}
static void check_partners(thread_db* tdbb, const USHORT rel_id)
{
/**************************************
*
* c h e c k _ p a r t n e r s
*
**************************************
*
* Functional description
* Signal other processes to check partners of relation rel_id
* Used when FK index was dropped
*
**************************************/
const Database* dbb = tdbb->getDatabase();
vec<jrd_rel*>* relations = dbb->dbb_relations;
fb_assert(relations);
fb_assert(rel_id < relations->count());
jrd_rel *relation = (*relations)[rel_id];
fb_assert(relation);
LCK_lock_non_blocking(tdbb, relation->rel_partners_lock, LCK_EX, LCK_WAIT);
LCK_release(tdbb, relation->rel_partners_lock);
relation->rel_flags |= REL_check_partners;
}
static bool delete_index(thread_db* tdbb, SSHORT phase, DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* d e l e t e _ i n d e x
*
**************************************
*
* Functional description
*
**************************************/
IndexLock* index = NULL;
SET_TDBB(tdbb);
const DeferredWork* arg = work->dfw_args;
while (arg && (arg->dfw_type != dfw_arg_index_name))
arg = arg->dfw_next;
fb_assert(arg);
const USHORT id = arg->dfw_id - 1;
// Look up the relation. If we can't find the relation,
// don't worry about the index.
jrd_rel* relation = MET_lookup_relation_id(tdbb, work->dfw_id, false);
if (!relation) {
return false;
}
RelationPages* relPages = relation->getPages(tdbb, -1, false);
if (!relPages) {
return false;
}
// we need to special handle temp tables with ON PRESERVE ROWS only
const bool isTempIndex = (relation->rel_flags & REL_temp_conn) &&
(relPages->rel_instance_id != 0);
switch (phase)
{
case 0:
index = CMP_get_index_lock(tdbb, relation, id);
if (index) {
if (!index->idl_count) {
LCK_release(tdbb, index->idl_lock);
}
}
return false;
case 1:
check_dependencies(tdbb, arg->dfw_name.c_str(), NULL,
obj_index, transaction);
return true;
case 2:
return true;
case 3:
// Make sure nobody is currently using the index
// If we about to delete temp index instance then usage counter
// will remains 1 and will be decremented by IDX_delete_index at
// phase 4
index = CMP_get_index_lock(tdbb, relation, id);
if (index)
{
// take into account lock probably used by temp index instance
bool temp_lock_released = false;
if (isTempIndex && (index->idl_count == 1))
{
index_desc idx;
if (BTR_lookup(tdbb, relation, id, &idx, relPages) == FB_SUCCESS)
{
index->idl_count--;
LCK_release(tdbb, index->idl_lock);
temp_lock_released = true;
}
}
// Try to clear trigger cache to release lock
if (index->idl_count)
MET_clear_cache(tdbb);
if (!isTempIndex)
{
if (index->idl_count ||
!LCK_lock_non_blocking(tdbb, index->idl_lock, LCK_EX,
transaction->getLockWait()))
{
// restore lock used by temp index instance
if (temp_lock_released)
{
LCK_lock_non_blocking(tdbb, index->idl_lock, LCK_SR, LCK_WAIT);
index->idl_count++;
}
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_obj_in_use,
isc_arg_string, "INDEX",
0);
}
index->idl_count++;
}
}
return true;
case 4:
index = CMP_get_index_lock(tdbb, relation, id);
if (isTempIndex && index)
index->idl_count++;
IDX_delete_index(tdbb, relation, id);
if (isTempIndex)
return false;
if (work->dfw_type == dfw_delete_expression_index)
{
MET_delete_dependencies(tdbb, arg->dfw_name, obj_expression_index);
}
// if index was bound to deleted FK constraint
// then work->dfw_args was set in VIO_erase
arg = work->dfw_args;
while (arg && (arg->dfw_type != dfw_arg_partner_rel_id))
{
arg = arg->dfw_next;
}
if (arg) {
if (arg->dfw_id) {
check_partners(tdbb, relation->rel_id);
if (relation->rel_id != arg->dfw_id) {
check_partners(tdbb, arg->dfw_id);
}
}
else {
// partner relation was not found in VIO_erase
// we must check partners of all relations in database
MET_update_partners(tdbb);
}
}
if (index)
{
/* in order for us to have gotten the lock in phase 3
* idl_count HAD to be 0, therefore after having incremented
* it for the exclusive lock it would have to be 1.
* IF now it is NOT 1 then someone else got a lock to
* the index and something is seriously wrong */
fb_assert(index->idl_count == 1);
if (!--index->idl_count)
{
/* Release index existence lock and memory. */
for (IndexLock** ptr = &relation->rel_index_locks;
*ptr;
ptr = &(*ptr)->idl_next)
{
if (*ptr == index)
{
*ptr = index->idl_next;
break;
}
}
if (index->idl_lock)
{
LCK_release(tdbb, index->idl_lock);
delete index->idl_lock;
}
delete index;
/* Release index refresh lock and memory. */
for (IndexBlock** iptr = &relation->rel_index_blocks;
*iptr;
iptr = &(*iptr)->idb_next)
{
if ((*iptr)->idb_id == id)
{
IndexBlock* index_block = *iptr;
*iptr = index_block->idb_next;
/* Lock was released in IDX_delete_index(). */
if (index_block->idb_lock)
delete index_block->idb_lock;
delete index_block;
break;
}
}
}
}
break;
}
return false;
}
static bool delete_parameter( thread_db* tdbb,
SSHORT phase,
DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* d e l e t e _ p a r a m e t e r
*
**************************************
*
* Functional description
* Return an error if someone attempts to
* delete a field from a procedure and it is
* used by a view or procedure.
*
**************************************/
SET_TDBB(tdbb);
switch (phase)
{
case 1:
/* hvlad: temporary disable procedure parameters dependency check
until proper solution (something like dyn_mod_parameter)
will be implemented. This check never worked properly
so no harm is done
if (MET_lookup_procedure_id(tdbb, work->dfw_id, false, true, 0))
{
const DeferredWork* arg = work->dfw_args;
fb_assert(arg && (arg->dfw_type == dfw_arg_proc_name));
check_dependencies(tdbb, arg->dfw_name.c_str(),
work->dfw_name.c_str(),
obj_procedure, transaction);
}
*/
case 2:
return true;
case 3:
break;
}
return false;
}
static bool delete_procedure( thread_db* tdbb,
SSHORT phase,
DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* d e l e t e _ p r o c e d u r e
*
**************************************
*
* Functional description
* Check if it is allowable to delete
* a procedure , and if so, clean up after it.
*
**************************************/
jrd_prc* procedure;
USHORT old_flags;
SET_TDBB(tdbb);
switch (phase)
{
case 0:
procedure = MET_lookup_procedure_id(tdbb, work->dfw_id, false, true, 0);
if (!procedure) {
return false;
}
if (procedure->prc_existence_lock)
{
LCK_convert_non_blocking(tdbb, procedure->prc_existence_lock,
LCK_SR, transaction->getLockWait());
}
return false;
case 1:
check_dependencies(tdbb, work->dfw_name.c_str(), NULL,
obj_procedure, transaction);
return true;
case 2:
procedure = MET_lookup_procedure_id(tdbb, work->dfw_id, false, true, 0);
if (!procedure) {
return false;
}
if (procedure->prc_existence_lock)
{
if (!LCK_convert_non_blocking(tdbb, procedure->prc_existence_lock,
LCK_EX, transaction->getLockWait()))
{
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_obj_in_use,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
}
}
/* If we are in a multi-client server, someone else may have marked
procedure obsolete. Unmark and we will remark it later. */
procedure->prc_flags &= ~PRC_obsolete;
return true;
case 3:
return true;
case 4:
procedure = MET_lookup_procedure_id(tdbb, work->dfw_id, true, true, 0);
if (!procedure) {
return false;
}
// Do not allow to drop procedure used by user requests
if (procedure->prc_use_count && MET_procedure_in_use(tdbb, procedure))
{
/*
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_obj_in_use,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
*/
gds__log("Deleting procedure %s which is currently in use by active user requests",
work->dfw_name.c_str());
MET_delete_dependencies(tdbb, work->dfw_name, obj_procedure);
if (procedure->prc_existence_lock) {
LCK_release(tdbb, procedure->prc_existence_lock);
}
(*tdbb->getDatabase()->dbb_procedures)[procedure->prc_id] = NULL;
return false;
}
old_flags = procedure->prc_flags;
procedure->prc_flags |= PRC_obsolete;
if (procedure->prc_request)
{
if (CMP_clone_is_active(procedure->prc_request))
{
procedure->prc_flags = old_flags;
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_obj_in_use,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
}
CMP_release(tdbb, procedure->prc_request);
procedure->prc_request = 0;
}
/* delete dependency lists */
MET_delete_dependencies(tdbb, work->dfw_name, obj_procedure);
if (procedure->prc_existence_lock) {
LCK_release(tdbb, procedure->prc_existence_lock);
}
break;
}
return false;
}
static bool delete_relation(thread_db* tdbb, SSHORT phase, DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* d e l e t e _ r e l a t i o n
*
**************************************
*
* Functional description
* Check if it is allowable to delete
* a relation, and if so, clean up after it.
*
**************************************/
jrd_req* request;
jrd_rel* relation;
Resource* rsc;
USHORT view_count;
bool adjusted;
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
switch (phase)
{
case 0:
relation = MET_lookup_relation_id(tdbb, work->dfw_id, false);
if (!relation) {
return false;
}
if (relation->rel_existence_lock)
{
LCK_convert_non_blocking(tdbb, relation->rel_existence_lock,
LCK_SR, transaction->getLockWait());
}
relation->rel_flags &= ~REL_deleting;
return false;
case 1:
/* check if any views use this as a base relation */
request = NULL;
view_count = 0;
FOR(REQUEST_HANDLE request)
X IN RDB$VIEW_RELATIONS WITH
X.RDB$RELATION_NAME EQ work->dfw_name.c_str()
/* If the view is also being deleted, there's no dependency */
if (!find_depend_in_dfw(tdbb, X.RDB$VIEW_NAME, obj_view, 0,
transaction))
{
view_count++;
}
END_FOR;
CMP_release(tdbb, request);
if (view_count)
{
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_no_delete, /* Msg353: can not delete */
isc_arg_gds, isc_table_name,
isc_arg_string, ERR_cstring(work->dfw_name),
isc_arg_gds, isc_dependency,
isc_arg_number, (SLONG) view_count,
0); /* Msg310: there are %ld dependencies */
}
check_dependencies(tdbb, work->dfw_name.c_str(), NULL,
obj_relation, transaction);
return true;
case 2:
relation = MET_lookup_relation_id(tdbb, work->dfw_id, false);
if (!relation) {
return false;
}
/* Let relation be deleted if only this transaction is using it */
adjusted = false;
if (relation->rel_use_count == 1)
{
for (rsc = transaction->tra_resources.begin(); rsc < transaction->tra_resources.end(); rsc++)
{
if (rsc->rsc_rel == relation)
{
--relation->rel_use_count;
adjusted = true;
break;
}
}
}
if (relation->rel_use_count)
MET_clear_cache(tdbb);
if (relation->rel_use_count ||
(relation->rel_existence_lock &&
!LCK_convert_non_blocking(tdbb, relation->rel_existence_lock,
LCK_EX, transaction->getLockWait())))
{
if (adjusted) {
++relation->rel_use_count;
}
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_obj_in_use,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
}
return true;
case 3:
return true;
case 4:
relation = MET_lookup_relation_id(tdbb, work->dfw_id, true);
if (!relation) {
return false;
}
/* Flag relation delete in progress so that active sweep or
garbage collector threads working on relation can skip over it. */
relation->rel_flags |= REL_deleting;
/* The sweep and garbage collector threads have no more than
a single record latency in responding to the flagged relation
deletion. Nevertheless, as a defensive programming measure,
don't wait forever if something has gone awry and the sweep
count doesn't run down. */
for (int wait = 0; wait < 60; wait++)
{
if (!relation->rel_sweep_count) {
break;
}
THREAD_EXIT();
THREAD_SLEEP(1 * 1000);
THREAD_ENTER();
}
if (relation->rel_sweep_count)
{
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_obj_in_use,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
}
#ifdef GARBAGE_THREAD
/* Free any memory associated with the relation's
garbage collection bitmap. */
if (relation->rel_gc_bitmap)
{
delete relation->rel_gc_bitmap;
relation->rel_gc_bitmap = 0;
}
if (relation->rel_garbage)
{
delete relation->rel_garbage;
relation->rel_garbage = 0;
}
#endif
if (relation->rel_file) {
EXT_fini (relation, false);
}
RelationPages* relPages = relation->getBasePages();
if (relPages->rel_index_root) {
IDX_delete_indices(tdbb, relation, relPages);
}
if (relPages->rel_pages) {
DPM_delete_relation(tdbb, relation);
}
/* if this is a view (or even if we don't know),
delete dependency lists */
if (relation->rel_view_rse || !(relation->rel_flags & REL_scanned)) {
MET_delete_dependencies(tdbb, work->dfw_name, obj_view);
}
/* Now that the data, pointer, and index pages are gone,
get rid of formats as well */
if (relation->rel_existence_lock) {
LCK_release(tdbb, relation->rel_existence_lock);
}
if (relation->rel_partners_lock) {
LCK_release(tdbb, relation->rel_partners_lock);
}
request = NULL;
FOR(REQUEST_HANDLE request) X IN RDB$FORMATS WITH
X.RDB$RELATION_ID EQ relation->rel_id
ERASE X;
END_FOR;
relation->rel_name = "";
relation->rel_flags |= REL_deleted;
relation->rel_flags &= ~REL_deleting;
// Release relation triggers
MET_release_triggers(tdbb, &relation->rel_pre_store);
MET_release_triggers(tdbb, &relation->rel_post_store);
MET_release_triggers(tdbb, &relation->rel_pre_erase);
MET_release_triggers(tdbb, &relation->rel_post_erase);
MET_release_triggers(tdbb, &relation->rel_pre_modify);
MET_release_triggers(tdbb, &relation->rel_post_modify);
CMP_release(tdbb, request);
break;
}
return false;
}
static bool delete_rfr(thread_db* tdbb, SSHORT phase, DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* d e l e t e _ r f r
*
**************************************
*
* Functional description
* This whole routine exists just to
* return an error if someone attempts to
* 1. delete a field from a relation if the relation
* is used in a view and the field is referenced in
* the view.
* 2. drop the last column of a table
*
**************************************/
int rel_exists, field_count;
jrd_req* handle;
Firebird::MetaName f;
jrd_rel* relation;
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
switch (phase)
{
case 1:
/* first check if there are any fields used explicitly by the view */
handle = NULL;
field_count = 0;
FOR(REQUEST_HANDLE handle)
REL IN RDB$RELATIONS CROSS
VR IN RDB$VIEW_RELATIONS OVER RDB$RELATION_NAME CROSS
VFLD IN RDB$RELATION_FIELDS WITH
REL.RDB$RELATION_ID EQ work->dfw_id AND
VFLD.RDB$VIEW_CONTEXT EQ VR.RDB$VIEW_CONTEXT AND
VFLD.RDB$RELATION_NAME EQ VR.RDB$VIEW_NAME AND
VFLD.RDB$BASE_FIELD EQ work->dfw_name.c_str()
/* If the view is also being deleted, there's no dependency */
if (!find_depend_in_dfw(tdbb, VR.RDB$VIEW_NAME, obj_view,
0, transaction))
{
f = VFLD.RDB$BASE_FIELD;
field_count++;
}
END_FOR;
CMP_release(tdbb, handle);
if (field_count)
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_no_delete, /* Msg353: can not delete */
isc_arg_gds, isc_field_name,
isc_arg_string, ERR_cstring(f),
isc_arg_gds, isc_dependency,
isc_arg_number, (SLONG) field_count,
0); /* Msg310: there are %ld dependencies */
/* now check if there are any dependencies generated through the blr
that defines the relation */
if ( (relation = MET_lookup_relation_id(tdbb, work->dfw_id, false)) )
check_dependencies(tdbb, relation->rel_name.c_str(), work->dfw_name.c_str(),
obj_relation, transaction);
/* see if the relation itself is being dropped */
handle = NULL;
rel_exists = 0;
FOR(REQUEST_HANDLE handle)
REL IN RDB$RELATIONS WITH REL.RDB$RELATION_ID EQ work->dfw_id
rel_exists++;
END_FOR;
if (handle)
CMP_release(tdbb, handle);
/* if table exists, check if this is the last column in the table */
if (rel_exists)
{
field_count = 0;
handle = NULL;
FOR(REQUEST_HANDLE handle)
REL IN RDB$RELATIONS CROSS
RFLD IN RDB$RELATION_FIELDS OVER RDB$RELATION_NAME
WITH REL.RDB$RELATION_ID EQ work->dfw_id
field_count++;
END_FOR;
if (handle)
{
CMP_release(tdbb, handle);
}
if (!field_count)
{
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_del_last_field,
0);
/* Msg354: last column in a relation cannot be deleted */
}
}
case 2:
return true;
case 3:
/* Unlink field from data structures. Don't try to actually release field and
friends -- somebody may be pointing to them */
relation = MET_lookup_relation_id(tdbb, work->dfw_id, false);
if (relation)
{
const int id = MET_lookup_field(tdbb, relation, work->dfw_name, 0);
if (id >= 0)
{
vec<jrd_fld*>* vector = relation->rel_fields;
if (vector &&
(ULONG)id < vector->count() &&
(*vector)[id])
{
(*vector)[id] = NULL;
}
}
}
break;
}
return false;
}
static bool delete_shadow( thread_db* tdbb,
SSHORT phase,
DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* d e l e t e _ s h a d o w
*
**************************************
*
* Functional description
* Provide deferred work interface to
* MET_delete_shadow.
*
**************************************/
SET_TDBB(tdbb);
switch (phase)
{
case 1:
case 2:
return true;
case 3:
MET_delete_shadow(tdbb, work->dfw_id);
break;
}
return false;
}
static bool delete_trigger( thread_db* tdbb,
SSHORT phase,
DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* d e l e t e _ t r i g g e r
*
**************************************
*
* Functional description
* Cleanup after a deleted trigger.
*
**************************************/
SET_TDBB(tdbb);
switch (phase)
{
case 1:
case 2:
return true;
case 3:
/* get rid of dependencies */
MET_delete_dependencies(tdbb, work->dfw_name, obj_trigger);
return true;
case 4:
{
const DeferredWork* arg = work->dfw_args;
while (arg && (arg->dfw_type != dfw_arg_rel_name))
arg = arg->dfw_next;
if (!arg)
{
arg = work->dfw_args;
while (arg && (arg->dfw_type != dfw_arg_trg_type))
arg = arg->dfw_next;
fb_assert(arg);
if (arg && (arg->dfw_id & TRIGGER_TYPE_MASK) == TRIGGER_TYPE_DB)
{
MET_release_trigger(tdbb,
&tdbb->getDatabase()->dbb_triggers[arg->dfw_id & ~TRIGGER_TYPE_DB],
work->dfw_name);
}
}
}
break;
}
return false;
}
static bool find_depend_in_dfw( thread_db* tdbb,
TEXT* object_name,
USHORT dep_type,
USHORT rel_id,
jrd_tra* transaction)
{
/**************************************
*
* f i n d _ d e p e n d _ i n _ d f w
*
**************************************
*
* Functional description
* Check the object to see if it is being
* deleted as part of the deferred work.
* Return true if it is, false otherwise.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
fb_utils::exact_name(object_name);
enum dfw_t dfw_type;
switch (dep_type)
{
case obj_view:
dfw_type = dfw_delete_relation;
break;
case obj_trigger:
dfw_type = dfw_delete_trigger;
break;
case obj_computed:
dfw_type = (rel_id) ? dfw_delete_rfr : dfw_delete_global;
break;
case obj_validation:
dfw_type = dfw_delete_global;
break;
case obj_procedure:
dfw_type = dfw_delete_procedure;
break;
case obj_expression_index:
dfw_type = dfw_delete_expression_index;
break;
default:
fb_assert(false);
break;
}
/* Look to see if an object of the desired type is being deleted. */
for (const DeferredWork* work = transaction->tra_deferred_work; work;
work = work->dfw_next)
{
if ((work->dfw_type == dfw_type ||
(work->dfw_type == dfw_modify_procedure && dfw_type == dfw_delete_procedure)) &&
work->dfw_name == object_name &&
(!rel_id || rel_id == work->dfw_id))
{
if (work->dfw_type == dfw_modify_procedure)
{
// Don't consider that procedure is in DFW if we are only checking the BLR
const DeferredWork* arg = work->dfw_args;
while (arg && (arg->dfw_type != dfw_arg_check_blr))
arg = arg->dfw_next;
if (!arg)
return true;
}
else
return true;
}
if (work->dfw_type == dfw_type &&
dfw_type == dfw_delete_expression_index)
{
const DeferredWork* arg = work->dfw_args;
while (arg)
{
if (arg->dfw_type == dfw_arg_index_name &&
arg->dfw_name == object_name)
{
return true;
}
arg = arg->dfw_next;
}
}
}
if (dfw_type == dfw_delete_global)
{
if (dep_type == obj_computed)
{
// Computed fields are more complicated. If the global field isn't being
// deleted, see if all of the fields it is the source for, are.
jrd_req* request = CMP_find_request(tdbb, irq_ch_cmp_dpd, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request)
FLD IN RDB$FIELDS CROSS
RFR IN RDB$RELATION_FIELDS CROSS
REL IN RDB$RELATIONS
WITH FLD.RDB$FIELD_NAME EQ RFR.RDB$FIELD_SOURCE
AND FLD.RDB$FIELD_NAME EQ object_name
AND REL.RDB$RELATION_NAME EQ RFR.RDB$RELATION_NAME
if (!REQUEST(irq_ch_cmp_dpd))
REQUEST(irq_ch_cmp_dpd) = request;
if (!find_depend_in_dfw(tdbb, RFR.RDB$FIELD_NAME, obj_computed,
REL.RDB$RELATION_ID, transaction))
{
EXE_unwind(tdbb, request);
return false;
}
END_FOR;
if (!REQUEST(irq_ch_cmp_dpd)) {
REQUEST(irq_ch_cmp_dpd) = request;
}
return true;
}
else if (dep_type == obj_validation)
{
// Maybe it's worth caching in the future?
jrd_req* request = NULL;
FOR(REQUEST_HANDLE request)
FLD IN RDB$FIELDS WITH
FLD.RDB$FIELD_NAME EQ object_name
if (!FLD.RDB$VALIDATION_BLR.NULL)
{
EXE_unwind(tdbb, request);
return false;
}
END_FOR;
return true;
}
}
return false;
}
static void get_array_desc(thread_db* tdbb, const TEXT* field_name,
Ods::InternalArrayDesc* desc)
{
/**************************************
*
* g e t _ a r r a y _ d e s c
*
**************************************
*
* Functional description
* Get array descriptor for an array.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
jrd_req* request = CMP_find_request(tdbb, irq_r_fld_dim, IRQ_REQUESTS);
Ods::InternalArrayDesc::iad_repeat* ranges = 0;
FOR (REQUEST_HANDLE request)
D IN RDB$FIELD_DIMENSIONS WITH D.RDB$FIELD_NAME EQ field_name
if (!REQUEST(irq_r_fld_dim))
{
REQUEST(irq_r_fld_dim) = request;
}
if (D.RDB$DIMENSION >= 0 && D.RDB$DIMENSION < desc->iad_dimensions)
{
ranges = desc->iad_rpt + D.RDB$DIMENSION;
ranges->iad_lower = D.RDB$LOWER_BOUND;
ranges->iad_upper = D.RDB$UPPER_BOUND;
}
END_FOR;
if (!REQUEST(irq_r_fld_dim))
{
REQUEST(irq_r_fld_dim) = request;
}
desc->iad_count = 1;
for (ranges = desc->iad_rpt + desc->iad_dimensions;
--ranges >= desc->iad_rpt;)
{
ranges->iad_length = desc->iad_count;
desc->iad_count *= ranges->iad_upper - ranges->iad_lower + 1;
}
desc->iad_version = Ods::IAD_VERSION_1;
desc->iad_length =
IAD_LEN(MAX(desc->iad_struct_count, desc->iad_dimensions));
desc->iad_element_length = desc->iad_rpt[0].iad_desc.dsc_length;
desc->iad_total_length = desc->iad_element_length * desc->iad_count;
}
static void get_procedure_dependencies(DeferredWork* work, bool compile)
{
/**************************************
*
* g e t _ p r o c e d u r e _ d e p e n d e n c i e s
*
**************************************
*
* Functional description
* Get relations and fields on which this
* procedure depends, either when it's being
* created or when it's modified.
*
**************************************/
thread_db* tdbb = JRD_get_thread_data();
Database* dbb = tdbb->getDatabase();
if (compile)
compile = !(tdbb->getAttachment()->att_flags & ATT_gbak_attachment);
jrd_prc* procedure = NULL;
bid blob_id;
blob_id.clear();
jrd_req* handle = CMP_find_request(tdbb, irq_c_prc_dpd, IRQ_REQUESTS);
FOR(REQUEST_HANDLE handle)
X IN RDB$PROCEDURES WITH
X.RDB$PROCEDURE_NAME EQ work->dfw_name.c_str()
if (!REQUEST(irq_c_prc_dpd))
REQUEST(irq_c_prc_dpd) = handle;
blob_id = X.RDB$PROCEDURE_BLR;
procedure = MET_lookup_procedure(tdbb, work->dfw_name, !compile);
END_FOR;
if (!REQUEST(irq_c_prc_dpd))
REQUEST(irq_c_prc_dpd) = handle;
#ifdef DEV_BUILD
MET_verify_cache(tdbb);
#endif
/* get any dependencies now by parsing the blr */
if (procedure && !blob_id.isEmpty())
{
jrd_req* request = NULL;
/* Nickolay Samofatov: allocate statement memory pool... */
JrdMemoryPool* new_pool = JrdMemoryPool::createPool();
// block is used to ensure MET_verify_cache
// works in not deleted context
{
Jrd::ContextPoolHolder context(tdbb, new_pool);
Firebird::MetaName depName(work->dfw_name);
MET_get_dependencies(tdbb, NULL, NULL, NULL, &blob_id,
compile ? &request : NULL,
NULL, depName, obj_procedure, 0);
if (request)
CMP_release(tdbb, request);
else
JrdMemoryPool::deletePool(new_pool);
}
#ifdef DEV_BUILD
MET_verify_cache(tdbb);
#endif
}
}
static void get_trigger_dependencies(DeferredWork* work, bool compile)
{
/**************************************
*
* g e t _ t r i g g e r _ d e p e n d e n c i e s
*
**************************************
*
* Functional description
* Get relations and fields on which this
* trigger depends, either when it's being
* created or when it's modified.
*
**************************************/
thread_db* tdbb = JRD_get_thread_data();
Database* dbb = tdbb->getDatabase();
if (compile)
compile = !(tdbb->getAttachment()->att_flags & ATT_gbak_attachment);
jrd_rel* relation = NULL;
bid blob_id;
blob_id.clear();
USHORT type = 0;
jrd_req* handle = CMP_find_request(tdbb, irq_c_trigger, IRQ_REQUESTS);
FOR(REQUEST_HANDLE handle)
X IN RDB$TRIGGERS WITH
X.RDB$TRIGGER_NAME EQ work->dfw_name.c_str()
if (!REQUEST(irq_c_trigger))
REQUEST(irq_c_trigger) = handle;
blob_id = X.RDB$TRIGGER_BLR;
type = (USHORT) X.RDB$TRIGGER_TYPE;
relation = MET_lookup_relation(tdbb, X.RDB$RELATION_NAME);
END_FOR;
if (!REQUEST(irq_c_trigger))
REQUEST(irq_c_trigger) = handle;
/* get any dependencies now by parsing the blr */
if ((relation || (type & TRIGGER_TYPE_MASK) == TRIGGER_TYPE_DB) &&
!blob_id.isEmpty())
{
jrd_req* request = NULL;
/* Nickolay Samofatov: allocate statement memory pool... */
JrdMemoryPool* new_pool = JrdMemoryPool::createPool();
const USHORT par_flags = (USHORT)
(type & 1) ? csb_pre_trigger : csb_post_trigger;
Jrd::ContextPoolHolder context(tdbb, new_pool);
Firebird::MetaName depName(work->dfw_name);
MET_get_dependencies(tdbb, relation, NULL, NULL, &blob_id,
compile ? &request : NULL,
NULL, depName, obj_trigger, par_flags);
if (request)
CMP_release(tdbb, request);
else
JrdMemoryPool::deletePool(new_pool);
}
}
static void load_trigs(thread_db* tdbb, jrd_rel* relation, trig_vec** triggers)
{
/**************************************
*
* l o a d _ t r i g s
*
**************************************
*
* Functional description
* We have just loaded the triggers onto the local vector
* triggers. Its now time to place them at their rightful
* place ie the relation block.
*
**************************************/
trig_vec* tmp_vector;
tmp_vector = relation->rel_pre_store;
relation->rel_pre_store = triggers[TRIGGER_PRE_STORE];
MET_release_triggers(tdbb, &tmp_vector);
tmp_vector = relation->rel_post_store;
relation->rel_post_store = triggers[TRIGGER_POST_STORE];
MET_release_triggers(tdbb, &tmp_vector);
tmp_vector = relation->rel_pre_erase;
relation->rel_pre_erase = triggers[TRIGGER_PRE_ERASE];
MET_release_triggers(tdbb, &tmp_vector);
tmp_vector = relation->rel_post_erase;
relation->rel_post_erase = triggers[TRIGGER_POST_ERASE];
MET_release_triggers(tdbb, &tmp_vector);
tmp_vector = relation->rel_pre_modify;
relation->rel_pre_modify = triggers[TRIGGER_PRE_MODIFY];
MET_release_triggers(tdbb, &tmp_vector);
tmp_vector = relation->rel_post_modify;
relation->rel_post_modify = triggers[TRIGGER_POST_MODIFY];
MET_release_triggers(tdbb, &tmp_vector);
}
static Format* make_format(thread_db* tdbb, jrd_rel* relation, USHORT* version,
TemporaryField* stack)
{
/**************************************
*
* m a k e _ f o r m a t
*
**************************************
*
* Functional description
* Make a format block for a relation.
*
**************************************/
TemporaryField* tfb;
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
/* Figure out the highest field id and allocate a format block */
USHORT count = 0;
for (tfb = stack; tfb; tfb = tfb->tfb_next)
count = MAX(count, tfb->tfb_id);
Format* format = Format::newFormat(*dbb->dbb_permanent, count + 1);
if (version)
format->fmt_version = *version;
/* Fill in the format block from the temporary field blocks */
for (tfb = stack; tfb; tfb = tfb->tfb_next)
{
dsc* desc = &format->fmt_desc[tfb->tfb_id];
if (tfb->tfb_flags & TFB_array)
{
desc->dsc_dtype = dtype_array;
desc->dsc_length = sizeof(ISC_QUAD);
}
else
*desc = tfb->tfb_desc;
if (tfb->tfb_flags & TFB_computed)
desc->dsc_dtype |= COMPUTED_FLAG;
}
/* Compute the offsets of the various fields */
ULONG offset = FLAG_BYTES(count);
count = 0;
for (Format::fmt_desc_iterator desc2 = format->fmt_desc.begin();
count < format->fmt_count;
++count, ++desc2)
{
if (desc2->dsc_dtype & COMPUTED_FLAG)
{
desc2->dsc_dtype &= ~COMPUTED_FLAG;
continue;
}
if (desc2->dsc_dtype)
{
offset = MET_align(dbb, &(*desc2), offset);
desc2->dsc_address = (UCHAR *) (IPTR) offset;
offset += desc2->dsc_length;
}
}
format->fmt_length = (USHORT)offset;
/* Release the temporary field blocks */
while ( (tfb = stack) )
{
stack = tfb->tfb_next;
delete tfb;
}
if (offset > MAX_FORMAT_SIZE)
{
delete format;
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_rec_size_err,
isc_arg_number, (SLONG) offset,
isc_arg_gds, isc_table_name,
isc_arg_string, ERR_cstring(relation->rel_name),
0);
/* Msg361: new record size of %ld bytes is too big */
}
Format* old_format;
if ((format->fmt_version)
&& (old_format = MET_format(tdbb, relation, (format->fmt_version - 1)))
&& (formatsAreEqual(old_format, format)))
{
delete format;
*version = old_format->fmt_version;
return old_format;
}
/* Link the format block into the world */
vec<Format*>* vector = relation->rel_formats =
vec<Format*>::newVector(*dbb->dbb_permanent, relation->rel_formats,
format->fmt_version + 1);
(*vector)[format->fmt_version] = format;
/* Store format in system relation */
jrd_req* request = CMP_find_request(tdbb, irq_format3, IRQ_REQUESTS);
STORE(REQUEST_HANDLE request)
FMTS IN RDB$FORMATS
if (!REQUEST(irq_format3))
REQUEST(irq_format3) = request;
FMTS.RDB$FORMAT = format->fmt_version;
FMTS.RDB$RELATION_ID = relation->rel_id;
blb* blob = BLB_create(tdbb, dbb->dbb_sys_trans, &FMTS.RDB$DESCRIPTOR);
if (sizeof(Ods::Descriptor) == sizeof(struct dsc) || dbb->dbb_ods_version < ODS_VERSION11) {
// For ODS10 and earlier put descriptors in their in-memory representation
// This makes 64-bit and 32-bit ODS10 different for the same architecture.
BLB_put_segment(tdbb,
blob,
reinterpret_cast<const UCHAR*>(&(format->fmt_desc[0])),
(USHORT)(format->fmt_count * sizeof(dsc)));
}
else {
// Use generic representation of formats with 32-bit offsets
Firebird::Array<Ods::Descriptor> odsDescs;
Ods::Descriptor *odsDesc = odsDescs.getBuffer(format->fmt_count);
for (Format::fmt_desc_const_iterator desc = format->fmt_desc.begin();
desc < format->fmt_desc.end(); ++desc, ++odsDesc)
{
*odsDesc = *desc;
}
BLB_put_segment(tdbb, blob,
(UCHAR*) odsDescs.begin(), odsDescs.getCount() * sizeof(Ods::Descriptor));
}
BLB_close(tdbb, blob);
END_STORE;
if (!REQUEST(irq_format3))
REQUEST(irq_format3) = request;
return format;
}
static bool make_version(thread_db* tdbb, SSHORT phase, DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* m a k e _ v e r s i o n
*
**************************************
*
* Functional description
* Make a new format version for a relation. While we're at it, make
* sure all fields have id's. If the relation is a view, make a
* a format anyway -- used for view updates.
*
* While we're in the vicinity, also check the updatability of fields.
*
**************************************/
TemporaryField* stack;
TemporaryField* external;
jrd_rel* relation;
//bid blob_id;
//blob_id.clear();
USHORT n;
int physical_fields = 0;
bool external_flag = false;
bool computed_field;
trig_vec* triggers[TRIGGER_MAX];
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
bool null_view;
switch (phase)
{
case 1:
case 2:
return true;
case 3:
relation = NULL;
stack = external = NULL;
computed_field = false;
for (n = 0; n < TRIGGER_MAX; n++) {
triggers[n] = NULL;
}
jrd_req* request_fmt1 = CMP_find_request(tdbb, irq_format1, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request_fmt1)
REL IN RDB$RELATIONS WITH REL.RDB$RELATION_NAME EQ work->dfw_name.c_str()
if (!REQUEST(irq_format1)) {
REQUEST(irq_format1) = request_fmt1;
}
relation =
MET_lookup_relation_id(tdbb, REL.RDB$RELATION_ID, false);
if (!relation)
{
EXE_unwind(tdbb, request_fmt1);
return false;
}
const bid blob_id = REL.RDB$VIEW_BLR;
null_view = blob_id.isEmpty();
external_flag = REL.RDB$EXTERNAL_FILE[0];
if (REL.RDB$FORMAT == MAX_TABLE_VERSIONS)
{
EXE_unwind(tdbb, request_fmt1);
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_table_name,
isc_arg_string, ERR_cstring(work->dfw_name),
isc_arg_gds, isc_version_err,
0);
/* Msg357: too many versions */
}
MODIFY REL USING
blb* blob = BLB_create(tdbb,
dbb->dbb_sys_trans,
&REL.RDB$RUNTIME);
jrd_req* request_fmtx = CMP_find_request(tdbb, irq_format2, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request_fmtx)
RFR IN RDB$RELATION_FIELDS CROSS
FLD IN RDB$FIELDS WITH
RFR.RDB$RELATION_NAME EQ work->dfw_name.c_str() AND
RFR.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME
/* Update RFR to reflect new fields id */
if (!RFR.RDB$FIELD_ID.NULL && RFR.RDB$FIELD_ID >= REL.RDB$FIELD_ID)
REL.RDB$FIELD_ID = RFR.RDB$FIELD_ID + 1;
if (RFR.RDB$FIELD_ID.NULL || RFR.RDB$UPDATE_FLAG.NULL)
{
MODIFY RFR USING
if (RFR.RDB$FIELD_ID.NULL)
{
if (external_flag)
{
RFR.RDB$FIELD_ID = RFR.RDB$FIELD_POSITION;
/* RFR.RDB$FIELD_POSITION.NULL is
needed to be referenced in the
code somewhere for GPRE to include
this field in the structures that
it generates at the top of this func. */
RFR.RDB$FIELD_ID.NULL =
RFR.RDB$FIELD_POSITION.NULL;
}
else
{
RFR.RDB$FIELD_ID = REL.RDB$FIELD_ID;
RFR.RDB$FIELD_ID.NULL = FALSE;
}
REL.RDB$FIELD_ID++;
}
if (RFR.RDB$UPDATE_FLAG.NULL)
{
RFR.RDB$UPDATE_FLAG.NULL = FALSE;
RFR.RDB$UPDATE_FLAG = 1;
if (!FLD.RDB$COMPUTED_BLR.isEmpty())
{
RFR.RDB$UPDATE_FLAG = 0;
computed_field = true;
}
if (!null_view && REL.RDB$DBKEY_LENGTH > 8)
{
jrd_req* temp = NULL;
RFR.RDB$UPDATE_FLAG = 0;
FOR(REQUEST_HANDLE temp) X IN RDB$TRIGGERS WITH
X.RDB$RELATION_NAME EQ work->dfw_name.c_str() AND
X.RDB$TRIGGER_TYPE EQ 1
RFR.RDB$UPDATE_FLAG = 1;
END_FOR;
CMP_release(tdbb, temp);
}
}
END_MODIFY;
}
/* Store stuff in field summary */
n = RFR.RDB$FIELD_ID;
put_summary_record(blob, RSR_field_id, (UCHAR*)&n, sizeof(n));
put_summary_record(blob, RSR_field_name,
(UCHAR*) RFR.RDB$FIELD_NAME,
fb_utils::name_length(RFR.RDB$FIELD_NAME));
if (!FLD.RDB$COMPUTED_BLR.isEmpty()
&& !RFR.RDB$VIEW_CONTEXT)
{
put_summary_blob(blob,
RSR_computed_blr,
&FLD.RDB$COMPUTED_BLR);
}
else if (!null_view)
{
n = RFR.RDB$VIEW_CONTEXT;
put_summary_record(blob,
RSR_view_context,
(UCHAR*)&n,
sizeof(n));
put_summary_record(blob, RSR_base_field,
(UCHAR*) RFR.RDB$BASE_FIELD,
fb_utils::name_length(RFR.RDB$BASE_FIELD));
}
put_summary_blob(blob, RSR_missing_value,
&FLD.RDB$MISSING_VALUE);
put_summary_blob(blob, RSR_default_value,
(RFR.RDB$DEFAULT_VALUE.isEmpty() ?
&FLD.RDB$DEFAULT_VALUE :
&RFR.RDB$DEFAULT_VALUE));
put_summary_blob(blob, RSR_validation_blr,
&FLD.RDB$VALIDATION_BLR);
if (FLD.RDB$NULL_FLAG || RFR.RDB$NULL_FLAG)
{
put_summary_record(blob,
RSR_field_not_null,
nonnull_validation_blr,
sizeof(nonnull_validation_blr));
}
n = fb_utils::name_length(RFR.RDB$SECURITY_CLASS);
if (!RFR.RDB$SECURITY_CLASS.NULL && n)
put_summary_record(blob, RSR_security_class,
(UCHAR*) RFR.RDB$SECURITY_CLASS, n);
/* Make a temporary field block */
TemporaryField* tfb = FB_NEW(*tdbb->getDefaultPool()) TemporaryField;
tfb->tfb_next = stack;
stack = tfb;
/* for text data types, grab the CHARACTER_SET and
COLLATION to give the type of international text */
if (FLD.RDB$CHARACTER_SET_ID.NULL)
FLD.RDB$CHARACTER_SET_ID = CS_NONE;
SSHORT collation = COLLATE_NONE; /* codepoint collation */
if (!FLD.RDB$COLLATION_ID.NULL)
collation = FLD.RDB$COLLATION_ID;
if (!RFR.RDB$COLLATION_ID.NULL)
collation = RFR.RDB$COLLATION_ID;
if (!DSC_make_descriptor(&tfb->tfb_desc, FLD.RDB$FIELD_TYPE,
FLD.RDB$FIELD_SCALE,
FLD.RDB$FIELD_LENGTH,
FLD.RDB$FIELD_SUB_TYPE,
FLD.RDB$CHARACTER_SET_ID, collation))
{
EXE_unwind(tdbb, request_fmt1);
EXE_unwind(tdbb, request_fmtx);
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_random,
isc_arg_string, ERR_cstring(work->dfw_name), 0);
}
/* Make sure the text type specified is implemented */
if (!(validate_text_type(tdbb, tfb)))
{
EXE_unwind(tdbb, request_fmt1);
EXE_unwind(tdbb, request_fmtx);
ERR_post_nothrow(isc_no_meta_update,
isc_arg_gds, isc_random,
isc_arg_string, ERR_cstring(work->dfw_name), 0);
INTL_texttype_lookup(tdbb,
(DTYPE_IS_TEXT(tfb->tfb_desc.dsc_dtype) ?
tfb->tfb_desc.dsc_ttype() : tfb->tfb_desc.dsc_blob_ttype())); // should punt
ERR_punt(); // if INTL_texttype_lookup hasn't punt
}
// dimitr: view fields shouldn't be marked as computed
if (null_view && !FLD.RDB$COMPUTED_BLR.isEmpty())
tfb->tfb_flags |= TFB_computed;
else
++physical_fields;
tfb->tfb_id = RFR.RDB$FIELD_ID;
if ((n = FLD.RDB$DIMENSIONS))
setup_array(tdbb, blob, FLD.RDB$FIELD_NAME, n, tfb);
if (external_flag)
{
tfb = FB_NEW(*tdbb->getDefaultPool()) TemporaryField;
tfb->tfb_next = external;
external = tfb;
fb_assert(FLD.RDB$EXTERNAL_TYPE <= MAX_UCHAR);
tfb->tfb_desc.dsc_dtype = (UCHAR)FLD.RDB$EXTERNAL_TYPE;
fb_assert(FLD.RDB$EXTERNAL_SCALE >= MIN_SCHAR &&
FLD.RDB$EXTERNAL_SCALE <= MAX_SCHAR);
tfb->tfb_desc.dsc_scale = (SCHAR)FLD.RDB$EXTERNAL_SCALE;
tfb->tfb_desc.dsc_length = FLD.RDB$EXTERNAL_LENGTH;
tfb->tfb_id = RFR.RDB$FIELD_ID;
}
END_FOR;
if (!REQUEST(irq_format2))
REQUEST(irq_format2) = request_fmtx;
if (!physical_fields)
{
EXE_unwind(tdbb, request_fmt1);
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_table_name,
isc_arg_string, ERR_cstring(work->dfw_name),
isc_arg_gds, isc_must_have_phys_field,
0);
}
blob = setup_triggers(tdbb, relation, null_view, triggers, blob);
BLB_close(tdbb, blob);
USHORT version = REL.RDB$FORMAT;
version++;
relation->rel_current_format = make_format(tdbb, relation,
&version, stack);
REL.RDB$FORMAT = version;
END_MODIFY;
END_FOR;
if (!REQUEST(irq_format1))
REQUEST(irq_format1) = request_fmt1;
/* If we didn't find the relation, it is probably being dropped */
if (!relation) {
return false;
}
if (!(relation->rel_flags & REL_sys_trigs_being_loaded))
load_trigs(tdbb, relation, triggers);
/* in case somebody changed the view definition or a computed
field, reset the dependencies by deleting the current ones
and setting a flag for MET_scan_relation to find the new ones */
if (!null_view)
MET_delete_dependencies(tdbb, work->dfw_name, obj_view);
{ // begin scope
const DeferredWork* arg = work->dfw_args;
while (arg && (arg->dfw_type != dfw_arg_force_computed))
arg = arg->dfw_next;
if (arg && arg->dfw_type == dfw_arg_force_computed)
{
computed_field = true;
MET_delete_dependencies(tdbb, arg->dfw_name, obj_computed);
}
} // end scope
if (!null_view || computed_field)
relation->rel_flags |= REL_get_dependencies;
if (external_flag)
{
jrd_req* temp = NULL;
FOR(REQUEST_HANDLE temp) FMTS IN RDB$FORMATS WITH
FMTS.RDB$RELATION_ID EQ relation->rel_id AND
FMTS.RDB$FORMAT EQ 0
ERASE FMTS;
END_FOR;
CMP_release(tdbb, temp);
make_format(tdbb, relation, 0, external);
}
relation->rel_flags &= ~REL_scanned;
DFW_post_work(transaction, dfw_scan_relation, NULL, relation->rel_id);
break;
}
return false;
}
static bool modify_procedure( thread_db* tdbb,
SSHORT phase,
DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* m o d i f y _ p r o c e d u r e
*
**************************************
*
* Functional description
* Perform required actions when modifying procedure.
*
**************************************/
jrd_prc* procedure;
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
switch (phase)
{
case 0:
{
procedure = MET_lookup_procedure_id(tdbb,
work->dfw_id,
false,
true,
0);
if (!procedure) {
return false;
}
if (procedure->prc_existence_lock)
{
LCK_convert_non_blocking(tdbb, procedure->prc_existence_lock,
LCK_SR, transaction->getLockWait());
}
return false;
}
case 1:
return true;
case 2:
return true;
case 3:
procedure = MET_lookup_procedure_id(tdbb, work->dfw_id, false, true, 0);
if (!procedure) {
return false;
}
if (procedure->prc_existence_lock)
{
/* Let procedure be deleted if only this transaction is using it */
if (!LCK_convert_non_blocking(tdbb, procedure->prc_existence_lock,
LCK_EX, transaction->getLockWait()))
{
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_obj_in_use,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
}
}
/* If we are in a multi-client server, someone else may have marked
procedure obsolete. Unmark and we will remark it later. */
procedure->prc_flags &= ~PRC_obsolete;
return true;
case 4:
procedure = MET_lookup_procedure_id(tdbb, work->dfw_id, false, true, 0);
if (!procedure) {
return false;
}
try {
#ifdef SUPERSERVER
if (!(tdbb->getDatabase()->dbb_flags & DBB_sp_rec_mutex_init))
{
THD_rec_mutex_init(&tdbb->getDatabase()->dbb_sp_rec_mutex);
tdbb->getDatabase()->dbb_flags |= DBB_sp_rec_mutex_init;
}
THREAD_EXIT();
if (THD_rec_mutex_lock(&tdbb->getDatabase()->dbb_sp_rec_mutex))
{
THREAD_ENTER();
return false;
}
THREAD_ENTER();
#endif /* SUPERSERVER */
// Do not allow to modify procedure used by user requests
if (procedure->prc_use_count && MET_procedure_in_use(tdbb, procedure))
{
/*
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_obj_in_use,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
*/
gds__log("Modifying procedure %s which is currently in use by active user requests",
work->dfw_name.c_str());
USHORT prc_alter_count = procedure->prc_alter_count;
if (prc_alter_count > MAX_PROC_ALTER)
{
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_proc_name,
isc_arg_string, ERR_cstring(work->dfw_name),
isc_arg_gds, isc_version_err,
0);
/* Msg357: too many versions */
}
if (procedure->prc_existence_lock) {
LCK_release(tdbb, procedure->prc_existence_lock);
}
(*tdbb->getDatabase()->dbb_procedures)[procedure->prc_id] = NULL;
if (!(procedure = MET_lookup_procedure_id(tdbb,
work->dfw_id,
false,
true,
PRC_being_altered)))
{
#ifdef SUPERSERVER
THD_rec_mutex_unlock(&tdbb->getDatabase()->dbb_sp_rec_mutex);
#endif
return false;
}
procedure->prc_alter_count = ++prc_alter_count;
}
procedure->prc_flags |= PRC_being_altered;
if (procedure->prc_request)
{
if (CMP_clone_is_active(procedure->prc_request))
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_obj_in_use,
isc_arg_string, ERR_cstring(work->dfw_name),
0);
/* release the request */
CMP_release(tdbb, procedure->prc_request);
procedure->prc_request = 0;
}
/* delete dependency lists */
MET_delete_dependencies(tdbb, work->dfw_name, obj_procedure);
/* the procedure has just been scanned by MET_lookup_procedure_id
and its PRC_scanned flag is set. We are going to reread it
from file (create all new dependencies) and do not want this
flag to be set. That is why we do not add PRC_obsolete and
PRC_being_altered flags, we set only these two flags
*/
procedure->prc_flags = (PRC_obsolete | PRC_being_altered);
if (procedure->prc_existence_lock) {
LCK_release(tdbb, procedure->prc_existence_lock);
}
/* remove procedure from cache */
MET_remove_procedure(tdbb, work->dfw_id, NULL);
/* Now handle the new definition */
const DeferredWork* arg = work->dfw_args;
while (arg && (arg->dfw_type != dfw_arg_check_blr))
arg = arg->dfw_next;
get_procedure_dependencies(work, arg == NULL);
procedure->prc_flags &= ~(PRC_obsolete | PRC_being_altered);
} // try
catch (const Firebird::Exception& ex)
{
Firebird::stuff_exception(tdbb->tdbb_status_vector, ex);
#ifdef SUPERSERVER
THD_rec_mutex_unlock(&tdbb->getDatabase()->dbb_sp_rec_mutex);
#endif
ERR_punt();
}
#ifdef SUPERSERVER
THD_rec_mutex_unlock(&tdbb->getDatabase()->dbb_sp_rec_mutex);
#endif
return true;
case 5:
if (ENCODE_ODS(dbb->dbb_ods_version, dbb->dbb_minor_original) >= ODS_11_1)
{
const DeferredWork* arg = work->dfw_args;
while (arg && (arg->dfw_type != dfw_arg_check_blr))
arg = arg->dfw_next;
if (arg && arg->dfw_type == dfw_arg_check_blr)
{
SSHORT valid_blr = FALSE;
JrdMemoryPool* new_pool = JrdMemoryPool::createPool();
try
{
Jrd::ContextPoolHolder context(tdbb, new_pool);
// compile the procedure to know if the BLR is still valid
if (MET_procedure(tdbb, work->dfw_id, false, 0))
valid_blr = TRUE;
}
catch (const Firebird::Exception&)
{
}
JrdMemoryPool::deletePool(new_pool);
jrd_req* request = CMP_find_request(tdbb, irq_prc_validate, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request)
PRC IN RDB$PROCEDURES WITH
PRC.RDB$PROCEDURE_ID EQ work->dfw_id
if (!REQUEST(irq_prc_validate))
REQUEST(irq_prc_validate) = request;
MODIFY PRC USING
PRC.RDB$VALID_BLR = valid_blr;
PRC.RDB$VALID_BLR.NULL = FALSE;
END_MODIFY;
END_FOR;
if (!REQUEST(irq_prc_validate))
REQUEST(irq_prc_validate) = request;
}
}
break;
}
return false;
}
static bool modify_trigger(thread_db* tdbb, SSHORT phase, DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* m o d i f y _ t r i g g e r
*
**************************************
*
* Functional description
* Perform required actions when modifying trigger.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
switch (phase)
{
case 1:
case 2:
return true;
case 3:
{
const DeferredWork* arg = work->dfw_args;
while (arg && (arg->dfw_type != dfw_arg_check_blr))
arg = arg->dfw_next;
/* get rid of old dependencies, bring in the new */
MET_delete_dependencies(tdbb, work->dfw_name, obj_trigger);
get_trigger_dependencies(work, arg == NULL);
}
return true;
case 4:
{
const DeferredWork* arg = work->dfw_args;
while (arg && (arg->dfw_type != dfw_arg_rel_name))
arg = arg->dfw_next;
if (!arg)
{
arg = work->dfw_args;
while (arg && (arg->dfw_type != dfw_arg_trg_type))
arg = arg->dfw_next;
fb_assert(arg);
if (arg && (arg->dfw_id & TRIGGER_TYPE_MASK) == TRIGGER_TYPE_DB)
{
MET_release_trigger(tdbb,
&tdbb->getDatabase()->dbb_triggers[arg->dfw_id & ~TRIGGER_TYPE_DB],
work->dfw_name);
MET_load_trigger(tdbb, NULL, work->dfw_name,
&tdbb->getDatabase()->dbb_triggers[arg->dfw_id & ~TRIGGER_TYPE_DB]);
}
}
}
if (ENCODE_ODS(dbb->dbb_ods_version, dbb->dbb_minor_original) >= ODS_11_1)
{
const DeferredWork* arg = work->dfw_args;
while (arg && (arg->dfw_type != dfw_arg_check_blr))
arg = arg->dfw_next;
if (arg)
{
const Firebird::MetaName relation_name(arg->dfw_name);
SSHORT valid_blr = FALSE;
try
{
jrd_rel* relation = MET_lookup_relation(tdbb, relation_name);
if (relation)
{
// remove cached triggers from relation
relation->rel_flags &= ~REL_scanned;
MET_scan_relation(tdbb, relation);
trig_vec* triggers[TRIGGER_MAX];
int i;
for (i = 0; i < TRIGGER_MAX; ++i)
triggers[i] = NULL;
JrdMemoryPool* new_pool = JrdMemoryPool::createPool();
try
{
Jrd::ContextPoolHolder context(tdbb, new_pool);
MET_load_trigger(tdbb, relation, work->dfw_name, triggers);
for (i = 0; i < TRIGGER_MAX; ++i)
{
if (triggers[i])
{
for (size_t j = 0; j < triggers[i]->getCount(); ++j)
(*triggers[i])[j].compile(tdbb);
MET_release_triggers(tdbb, &triggers[i]);
}
}
valid_blr = TRUE;
}
catch (const Firebird::Exception&)
{
JrdMemoryPool::deletePool(new_pool);
throw;
}
JrdMemoryPool::deletePool(new_pool);
}
}
catch (const Firebird::Exception&)
{
}
jrd_req* request = CMP_find_request(tdbb, irq_trg_validate, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request)
TRG IN RDB$TRIGGERS WITH
TRG.RDB$TRIGGER_NAME EQ work->dfw_name.c_str()
if (!REQUEST(irq_trg_validate))
REQUEST(irq_trg_validate) = request;
MODIFY TRG USING
TRG.RDB$VALID_BLR = valid_blr;
TRG.RDB$VALID_BLR.NULL = FALSE;
END_MODIFY;
END_FOR;
if (!REQUEST(irq_trg_validate))
REQUEST(irq_trg_validate) = request;
}
}
break;
}
return false;
}
static DeferredWork* post_work(jrd_tra* transaction,
SLONG sav_number,
DeferredWork** list,
enum dfw_t type,
const dsc* desc,
USHORT id)
{
/**************************************
*
* p o s t _ w o r k
*
**************************************
*
* Functional description
* Post a piece of work to be deferred to commit time.
* If we already know about it, so much the better.
*
**************************************/
const char* string;
USHORT length;
TEXT temp[MAXPATHLEN]; // Must hold largest metadata field or filename
if (!desc)
{
string = NULL;
length = 0;
}
else
{
/* Find the actual length of the string, searching until the claimed
end of the string, or the terminating \0, whichever comes first. */
length = MOV_make_string(desc,
ttype_metadata,
&string,
(vary*)temp,
sizeof(temp));
const char* p = string;
const char* const q = string + length;
while (p < q && *p)
{
++p;
}
// Trim trailing blanks (bug 3355)
while (--p >= string && *p == ' ')
;
length = (p + 1) - string;
}
DeferredWork* work;
DeferredWork** ptr;
// Check to see if work is already posted
for (ptr = list; (work = *ptr); ptr = &(*ptr)->dfw_next)
{
if (work->dfw_type == type &&
work->dfw_id == id &&
work->dfw_name.length() == length &&
work->dfw_sav_number == sav_number)
{
if (!length)
return *ptr;
if (!memcmp(string, work->dfw_name.c_str(), length)) {
++work->dfw_count;
return *ptr;
}
}
}
// Not already posted, so do so now.
*ptr = work = FB_NEW(*transaction->tra_pool) DeferredWork(*transaction->tra_pool);
work->dfw_type = type;
work->dfw_id = id;
work->dfw_count = 1;
work->dfw_sav_number = sav_number;
work->dfw_name.assign(string, length);
if (type != dfw_post_event)
transaction->tra_flags |= TRA_deferred_meta;
else if (transaction->tra_save_point)
transaction->tra_save_point->sav_flags |= SAV_event_post;
return *ptr;
}
static void put_summary_blob(blb* blob, RSR_T type, bid* blob_id)
{
/**************************************
*
* p u t _ s u m m a r y _ b l o b
*
**************************************
*
* Functional description
* Put an attribute record to the relation summary blob.
*
**************************************/
UCHAR temp[128];
thread_db* tdbb = JRD_get_thread_data();
Database* dbb = tdbb->getDatabase();
/* If blob is null, don't bother */
if (blob_id->isEmpty())
return;
// Go ahead and open blob
blb* blr = BLB_open(tdbb, dbb->dbb_sys_trans, blob_id);
fb_assert(blr->blb_length <= MAX_USHORT);
USHORT length = (USHORT)blr->blb_length;
UCHAR* const buffer = (length > sizeof(temp)) ?
FB_NEW(*getDefaultMemoryPool()) UCHAR[length] : temp;
try {
length = (USHORT)BLB_get_data(tdbb, blr, buffer, (SLONG) length);
put_summary_record(blob, type, buffer, length);
}
catch (const Firebird::Exception&) {
if (buffer != temp)
delete[] buffer;
throw;
}
if (buffer != temp)
delete[] buffer;
}
static Lock* protect_relation(thread_db* tdbb, jrd_tra* transaction, jrd_rel* relation, bool& releaseLock)
{
/**************************************
*
* p r o t e c t _ r e l a t i o n
*
**************************************
*
* Functional description
* Lock relation with protected_read level or raise existing relation lock
* to this level to ensure nobody can write to this relation.
* Used when new index is built.
* releaseLock set to true if there was no existing lock before
*
**************************************/
Lock* relLock = RLCK_transaction_relation_lock(transaction, relation);
releaseLock = (relLock->lck_logical == LCK_none);
bool inUse = false;
if (!releaseLock) {
if ( (relLock->lck_logical < LCK_PR) &&
!LCK_convert_non_blocking(tdbb, relLock, LCK_PR, transaction->getLockWait()) )
{
inUse = true;
}
}
else {
if ( !LCK_lock_non_blocking(tdbb, relLock, LCK_PR, transaction->getLockWait()) ) {
inUse = true;
}
}
if (inUse ) {
ERR_post(isc_no_meta_update,
isc_arg_gds, isc_obj_in_use,
isc_arg_string, relation->rel_name.c_str(),
0);
}
return relLock;
}
static void put_summary_record(blb* blob,
RSR_T type,
const UCHAR* data,
USHORT length)
{
/**************************************
*
* p u t _ s u m m a r y _ r e c o r d
*
**************************************
*
* Functional description
* Put an attribute record to the relation summary blob.
*
**************************************/
thread_db* tdbb = JRD_get_thread_data();
fb_assert(length < MAX_USHORT); // otherwise length + 1 wraps. Or do we bugcheck???
UCHAR temp[129];
UCHAR* const buffer = ((size_t) (length + 1) > sizeof(temp)) ?
FB_NEW(*getDefaultMemoryPool()) UCHAR[length + 1] : temp;
UCHAR* p = buffer;
*p++ = (UCHAR) type;
memcpy(p, data, length);
try {
BLB_put_segment(tdbb, blob, buffer, length + 1);
}
catch (const Firebird::Exception&) {
if (buffer != temp)
delete[] buffer;
throw;
}
if (buffer != temp)
delete[] buffer;
}
static void release_protect_lock(thread_db* tdbb, jrd_tra* transaction, Lock* relLock)
{
vec<Lock*>* vector = transaction->tra_relation_locks;
if (vector) {
vec<Lock*>::iterator lock = vector->begin();
for (ULONG i = 0; i < vector->count(); ++i, ++lock)
{
if (*lock == relLock)
{
LCK_release(tdbb, relLock);
*lock = 0;
break;
}
}
}
}
static bool scan_relation(thread_db* tdbb, SSHORT phase, DeferredWork* work,
jrd_tra* transaction)
{
/**************************************
*
* s c a n _ r e l a t i o n
*
**************************************
*
* Functional description
* Call MET_scan_relation with the appropriate
* relation.
*
**************************************/
SET_TDBB(tdbb);
switch (phase)
{
case 1:
case 2:
return true;
case 3:
// dimitr: I suspect that nobody expects an updated format to
// appear at stage 3, so the logic would work reliably
// if this line is removed (and hence we rely on the
// 4th stage only). But I leave it here for the time being.
MET_scan_relation(tdbb, MET_relation(tdbb, work->dfw_id));
return true;
case 4:
MET_scan_relation(tdbb, MET_relation(tdbb, work->dfw_id));
break;
}
return false;
}
#ifdef NOT_USED_OR_REPLACED
static bool shadow_defined(thread_db* tdbb)
{
/**************************************
*
* s h a d o w _ d e f i n e d
*
**************************************
*
* Functional description
* Return TRUE if any shadows have been has been defined
* for the database else return false.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
bool result = false;
jrd_req* handle = NULL;
FOR(REQUEST_HANDLE handle) FIRST 1 X IN RDB$FILES
WITH X.RDB$SHADOW_NUMBER > 0
result = true;
END_FOR
CMP_release(tdbb, handle);
return result;
}
#endif
static void setup_array(thread_db* tdbb, blb* blob, const TEXT* field_name, USHORT n,
TemporaryField* tfb)
{
/**************************************
*
* s e t u p _ a r r a y
*
**************************************
*
* Functional description
*
* setup an array descriptor in a tfb
*
**************************************/
SLONG stuff[256];
put_summary_record(blob, RSR_dimensions, (UCHAR*) &n, sizeof(n));
tfb->tfb_flags |= TFB_array;
Ods::InternalArrayDesc* array = reinterpret_cast<Ods::InternalArrayDesc*>(stuff);
MOVE_CLEAR(array, (SLONG) sizeof(Ods::InternalArrayDesc));
array->iad_dimensions = n;
array->iad_struct_count = 1;
array->iad_rpt[0].iad_desc = tfb->tfb_desc;
get_array_desc(tdbb, field_name, array);
put_summary_record(blob, RSR_array_desc, (UCHAR*) array, array->iad_length);
}
static blb* setup_triggers(thread_db* tdbb, jrd_rel* relation, bool null_view,
trig_vec** triggers, blb* blob)
{
/**************************************
*
* s e t u p _ t r i g g e r s
*
**************************************
*
* Functional description
*
* Get the triggers in the right order, which appears
* to be system triggers first, then user triggers,
* then triggers that implement check constraints.
*
* BUG #8458: Check constraint triggers have to be loaded
* (and hence executed) after the user-defined
* triggers because user-defined triggers can modify
* the values being inserted or updated so that
* the end values stored in the database don't
* fulfill the check constraint. .
*
**************************************/
if (!relation)
return blob;
Database* dbb = tdbb->getDatabase();
/* system triggers */
jrd_req* request_fmtx = CMP_find_request(tdbb, irq_format4, IRQ_REQUESTS);
FOR (REQUEST_HANDLE request_fmtx)
TRG IN RDB$TRIGGERS
WITH TRG.RDB$RELATION_NAME = relation->rel_name.c_str()
AND TRG.RDB$SYSTEM_FLAG = 1
SORTED BY TRG.RDB$TRIGGER_SEQUENCE
if (!TRG.RDB$TRIGGER_INACTIVE)
setup_trigger_details(tdbb, relation, blob, triggers,
TRG.RDB$TRIGGER_NAME, TRG.RDB$RELATION_NAME, null_view);
END_FOR;
if (!REQUEST(irq_format4))
REQUEST(irq_format4) = request_fmtx;
/* user triggers */
request_fmtx = CMP_find_request(tdbb, irq_format5, IRQ_REQUESTS);
FOR (REQUEST_HANDLE request_fmtx)
TRG IN RDB$TRIGGERS
WITH TRG.RDB$RELATION_NAME EQ relation->rel_name.c_str()
AND (TRG.RDB$SYSTEM_FLAG = 0 OR TRG.RDB$SYSTEM_FLAG MISSING)
AND (NOT ANY
CHK IN RDB$CHECK_CONSTRAINTS CROSS
RCN IN RDB$RELATION_CONSTRAINTS
WITH TRG.RDB$TRIGGER_NAME EQ CHK.RDB$TRIGGER_NAME
AND CHK.RDB$CONSTRAINT_NAME EQ RCN.RDB$CONSTRAINT_NAME
AND (RCN.RDB$CONSTRAINT_TYPE EQ CHECK_CNSTRT
OR RCN.RDB$CONSTRAINT_TYPE EQ FOREIGN_KEY)
)
SORTED BY TRG.RDB$TRIGGER_SEQUENCE
if (!TRG.RDB$TRIGGER_INACTIVE)
setup_trigger_details(tdbb, relation, blob, triggers,
TRG.RDB$TRIGGER_NAME, TRG.RDB$RELATION_NAME, null_view);
END_FOR;
if (!REQUEST(irq_format5))
REQUEST(irq_format5) = request_fmtx;
/* check constraint triggers. We're looking for triggers that belong
to the table and are system triggers (i.e. system flag in (3, 4, 5))
or a user looking trigger that's involved in a check constraint */
request_fmtx = CMP_find_request(tdbb, irq_format6, IRQ_REQUESTS);
FOR (REQUEST_HANDLE request_fmtx)
TRG IN RDB$TRIGGERS
WITH TRG.RDB$RELATION_NAME = relation->rel_name.c_str()
AND (TRG.RDB$SYSTEM_FLAG BT fb_sysflag_check_constraint AND fb_sysflag_view_check
OR ((TRG.RDB$SYSTEM_FLAG = 0 OR TRG.RDB$SYSTEM_FLAG MISSING)
AND ANY
CHK IN RDB$CHECK_CONSTRAINTS CROSS
RCN IN RDB$RELATION_CONSTRAINTS
WITH TRG.RDB$TRIGGER_NAME EQ CHK.RDB$TRIGGER_NAME
AND CHK.RDB$CONSTRAINT_NAME EQ RCN.RDB$CONSTRAINT_NAME
AND (RCN.RDB$CONSTRAINT_TYPE EQ CHECK_CNSTRT
OR RCN.RDB$CONSTRAINT_TYPE EQ FOREIGN_KEY)
)
)
SORTED BY TRG.RDB$TRIGGER_SEQUENCE
if (!TRG.RDB$TRIGGER_INACTIVE)
setup_trigger_details(tdbb, relation, blob, triggers,
TRG.RDB$TRIGGER_NAME, TRG.RDB$RELATION_NAME, null_view);
END_FOR;
if (!REQUEST(irq_format6))
REQUEST(irq_format6) = request_fmtx;
return blob;
}
static void setup_trigger_details(thread_db* tdbb,
jrd_rel* relation,
blb* blob,
trig_vec** triggers,
const TEXT* trigger_name,
const TEXT* relation_name,
bool null_view)
{
/**************************************
*
* s e t u p _ t r i g g e r _ d e t a i l s
*
**************************************
*
* Functional description
* Stuff trigger details in places.
*
* for a view, load the trigger temporarily --
* this is inefficient since it will just be reloaded
* in MET_scan_relation () but it needs to be done
* in case the view would otherwise be non-updatable
*
**************************************/
put_summary_record(blob, RSR_trigger_name,
(const UCHAR*) trigger_name, fb_utils::name_length(trigger_name));
if (!null_view) {
if (relation->rel_name.length() == 0)
relation->rel_name = relation_name;
MET_load_trigger(tdbb, relation, trigger_name, triggers);
}
}
static bool validate_text_type(thread_db* tdbb,
const TemporaryField* tfb)
{
/**************************************
*
* v a l i d a t e _ t e x t _ t y p e
*
**************************************
*
* Functional description
* Make sure the text type specified is implemented
*
**************************************/
if ((DTYPE_IS_TEXT (tfb->tfb_desc.dsc_dtype) &&
!INTL_defined_type(tdbb, tfb->tfb_desc.dsc_ttype()))
||
(tfb->tfb_desc.dsc_dtype == dtype_blob &&
tfb->tfb_desc.dsc_sub_type == isc_blob_text &&
!INTL_defined_type(tdbb, tfb->tfb_desc.dsc_blob_ttype())))
{
return false;
}
else {
return true;
}
}