mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-24 18:43:02 +01:00
4456 lines
108 KiB
Plaintext
4456 lines
108 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)
|
|
*
|
|
*/
|
|
|
|
#include "firebird.h"
|
|
#include "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/all_proto.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/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 "../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 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 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 delete_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*, USHORT, jrd_tra*);
|
|
static void check_filename(const TEXT*, USHORT);
|
|
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*, internal_array_desc*);
|
|
static void get_procedure_dependencies(DeferredWork*);
|
|
static void get_trigger_dependencies(DeferredWork*);
|
|
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 void setup_trigger_details(thread_db*, jrd_rel*, blb*, vec**, UCHAR *, UCHAR *, BOOLEAN);
|
|
//static bool shadow_defined(thread_db*);
|
|
static bool validate_text_type (thread_db*, ISC_STATUS *, TemporaryField*);
|
|
|
|
static const UCHAR nonnull_validation_blr[] =
|
|
{
|
|
blr_version5,
|
|
blr_not,
|
|
blr_missing,
|
|
blr_fid, 0, 0, 0,
|
|
blr_eoc
|
|
};
|
|
|
|
struct deferred_task
|
|
{
|
|
enum dfw_t task_type;
|
|
bool (*task_routine) (thread_db*, SSHORT, DeferredWork*, jrd_tra*);
|
|
};
|
|
|
|
static const deferred_task task_table[] =
|
|
{
|
|
{ dfw_add_file, add_file },
|
|
{ dfw_add_shadow, add_shadow },
|
|
{ dfw_delete_index, delete_index },
|
|
{ dfw_delete_rfr, delete_rfr },
|
|
{ dfw_delete_relation, delete_relation },
|
|
{ dfw_delete_shadow, delete_shadow },
|
|
{ dfw_delete_field, delete_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, create_index },
|
|
#ifdef EXPRESSION_INDICES
|
|
{ dfw_create_expression_index, PCMET_expression_index },
|
|
#endif
|
|
{ dfw_delete_expression_index, delete_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_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_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);
|
|
|
|
ISC_STATUS_ARRAY status;
|
|
if (INTL_defined_type(tdbb, status, ttype))
|
|
return INTL_TEXT_TO_INDEX(ttype);
|
|
|
|
ERR_post(isc_no_meta_update,
|
|
isc_arg_gds, isc_random, isc_arg_string,
|
|
ERR_cstring(work->dfw_name), status[0], status[1], status[2],
|
|
status[3],
|
|
0);
|
|
}
|
|
|
|
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_length == work->dfw_name_length &&
|
|
work_m->dfw_sav_number == work->dfw_sav_number)
|
|
{
|
|
if ((work->dfw_name_length) &&
|
|
(memcmp(work->dfw_name,
|
|
work_m->dfw_name,
|
|
work->dfw_name_length)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
do
|
|
{
|
|
more = false;
|
|
try {
|
|
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) {
|
|
ERR_punt();
|
|
}
|
|
++phase;
|
|
}
|
|
catch (const std::exception& ex) {
|
|
Firebird::stuff_exception(tdbb->tdbb_status_vector, ex);
|
|
/* Do any necessary cleanup */
|
|
if (!phase) {
|
|
ERR_punt();
|
|
}
|
|
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,
|
|
(TEXT *) & lock->lck_key,
|
|
work->dfw_name_length,
|
|
work->dfw_name,
|
|
work->dfw_count);
|
|
*ptr = work->dfw_next;
|
|
delete work;
|
|
pending_events = true;
|
|
}
|
|
else if (work->dfw_type == dfw_delete_shadow)
|
|
{
|
|
unlink(work->dfw_name);
|
|
*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);
|
|
}
|
|
|
|
|
|
void 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.
|
|
*
|
|
**************************************/
|
|
|
|
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->tdbb_database;
|
|
|
|
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;
|
|
Firebird::PathName temp;
|
|
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->tdbb_database;
|
|
|
|
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_file->fil_string),
|
|
0);
|
|
return false;
|
|
}
|
|
case 4:
|
|
CCH_flush(tdbb, FLUSH_FINI, 0L);
|
|
max = PIO_max_alloc(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, work->dfw_name_length);
|
|
}
|
|
|
|
/* get any files to extend into */
|
|
|
|
FOR(REQUEST_HANDLE handle) X IN RDB$FILES
|
|
WITH X.RDB$FILE_NAME EQ work->dfw_name
|
|
/* First expand the file name This has already been done
|
|
** for shadows in add_shadow ()) */
|
|
if (work->dfw_type != dfw_add_shadow)
|
|
{
|
|
ISC_expand_filename(X.RDB$FILE_NAME, temp);
|
|
MODIFY X USING
|
|
strcpy(X.RDB$FILE_NAME, temp.c_str());
|
|
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->tdbb_database;
|
|
|
|
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, work->dfw_name_length);
|
|
|
|
/* 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
|
|
ISC_expand_filename(F.RDB$FILE_NAME, expanded_fname);
|
|
MODIFY F USING
|
|
strcpy(F.RDB$FILE_NAME, expanded_fname.c_str());
|
|
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_file->fil_string),
|
|
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_file->fil_string),
|
|
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->tdbb_database;
|
|
|
|
switch (phase)
|
|
{
|
|
case 0:
|
|
return false;
|
|
|
|
case 1:
|
|
case 2:
|
|
return true;
|
|
case 3:
|
|
if (!dbb->backup_manager->lock_state(true))
|
|
ERR_punt();
|
|
try {
|
|
if (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, work->dfw_name_length);
|
|
dbb->backup_manager->set_difference(work->dfw_name);
|
|
} catch(const std::exception&) {
|
|
dbb->backup_manager->unlock_state();
|
|
throw;
|
|
}
|
|
dbb->backup_manager->unlock_state();
|
|
|
|
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->tdbb_database;
|
|
|
|
switch (phase)
|
|
{
|
|
case 0:
|
|
return false;
|
|
case 1:
|
|
case 2:
|
|
return true;
|
|
case 3:
|
|
if (!dbb->backup_manager->lock_state(true))
|
|
ERR_punt();
|
|
try {
|
|
if (dbb->backup_manager->get_state() != nbak_state_normal)
|
|
{
|
|
ERR_post(isc_no_meta_update,
|
|
isc_arg_gds, isc_wrong_backup_state, 0);
|
|
}
|
|
dbb->backup_manager->set_difference(NULL);
|
|
} catch(const std::exception&) {
|
|
dbb->backup_manager->unlock_state();
|
|
throw;
|
|
}
|
|
dbb->backup_manager->unlock_state();
|
|
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->tdbb_database;
|
|
|
|
switch (phase)
|
|
{
|
|
case 0:
|
|
return false;
|
|
case 1:
|
|
case 2:
|
|
return true;
|
|
case 3:
|
|
dbb->backup_manager->begin_backup();
|
|
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->tdbb_database;
|
|
|
|
switch (phase)
|
|
{
|
|
case 0:
|
|
return false;
|
|
case 1:
|
|
case 2:
|
|
return true;
|
|
case 3:
|
|
// End backup normally
|
|
dbb->backup_manager->end_backup(false);
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void check_dependencies(thread_db* tdbb,
|
|
const TEXT* dpdo_name,
|
|
const TEXT* field_name,
|
|
USHORT 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->tdbb_database;
|
|
|
|
USHORT i;
|
|
USHORT dep_counts[obj_count];
|
|
for (i = 0; i < (USHORT) obj_count; 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_count; 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_exception:
|
|
obj_type = isc_exception_name;
|
|
break;
|
|
case obj_generator:
|
|
obj_type = isc_generator_name;
|
|
break;
|
|
case obj_udf:
|
|
obj_type = isc_udf_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, (SLONG) 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, (SLONG) dep_counts[i],
|
|
0); /* there are %ld dependencies */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void check_filename(const TEXT* name, USHORT l)
|
|
{
|
|
/**************************************
|
|
*
|
|
* 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.
|
|
*
|
|
**************************************/
|
|
TEXT file_name[MAXPATHLEN];
|
|
bool valid = true;
|
|
|
|
l = MIN(l, sizeof(file_name) - 1);
|
|
TEXT* p = file_name;
|
|
for (const TEXT* q = name; l--; *p++ = *q++)
|
|
; // empty body
|
|
*p = 0;
|
|
for (p = file_name; *p; p++) {
|
|
if (p[0] == ':' && p[1] == ':')
|
|
valid = false;
|
|
}
|
|
|
|
if (!valid || ISC_check_if_remote(file_name, false)) {
|
|
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(name)) {
|
|
ERR_post(isc_conf_access_denied,
|
|
isc_arg_string, "additional database file",
|
|
isc_arg_string, ERR_cstring(name),
|
|
isc_arg_end);
|
|
}
|
|
}
|
|
|
|
|
|
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->tdbb_database;
|
|
|
|
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);
|
|
|
|
jrd_req* handle = NULL;
|
|
FOR(REQUEST_HANDLE handle) X IN RDB$DATABASE
|
|
WITH X.RDB$SECURITY_CLASS EQ work->dfw_name
|
|
|
|
tdbb->tdbb_attachment->att_security_class = s_class;
|
|
|
|
END_FOR;
|
|
CMP_release(tdbb, handle);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
|
|
/**** OBSOLETE!!! Only security class name strings in relations and fields.
|
|
Note: fld_security_class --> fld_security_name
|
|
rel_security_class --> rel_security_name
|
|
|
|
jrd_req* handle = NULL;
|
|
for (request_handle handle) x in rdb$relation_fields
|
|
with x.rdb$security_class eq work->dfw_name
|
|
if (relation = MET_lookup_relation (tdbb, x.rdb$relation_name))
|
|
{
|
|
id = MET_lookup_field (tdbb, relation, x.rdb$field_name, 0);
|
|
if (field = MET_get_field (relation, id))
|
|
field->fld_security_class = class;
|
|
}
|
|
end_for;
|
|
CMP_release (tdbb, handle);
|
|
|
|
handle = NULL;
|
|
for (request_handle handle) x in rdb$relations
|
|
with x.rdb$security_class eq work->dfw_name
|
|
if (relation = MET_lookup_relation (tdbb, x.rdb$relation_name))
|
|
relation->rel_security_class = class;
|
|
end_for;
|
|
CMP_release (tdbb, handle);
|
|
|
|
****/
|
|
}
|
|
|
|
|
|
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->tdbb_database;
|
|
|
|
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
|
|
/* Views do not have indices */
|
|
if (IREL.RDB$VIEW_BLR.NULL)
|
|
{
|
|
relation = MET_lookup_relation(tdbb, IDXN.RDB$RELATION_NAME);
|
|
|
|
/* 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 (relation->rel_index_root)
|
|
{
|
|
if (work->dfw_id != dbb->dbb_max_idx)
|
|
{
|
|
WIN window(relation->rel_index_root);
|
|
CCH_FETCH(tdbb, &window, LCK_write, pag_root);
|
|
CCH_MARK_MUST_WRITE(tdbb, &window);
|
|
BTR_delete_index(tdbb, &window, work->dfw_id);
|
|
work->dfw_id = dbb->dbb_max_idx;
|
|
}
|
|
if (!IDXN.RDB$INDEX_ID.NULL)
|
|
{
|
|
MODIFY IDXN USING
|
|
IDXN.RDB$INDEX_ID.NULL = TRUE;
|
|
END_MODIFY;
|
|
}
|
|
}
|
|
}
|
|
END_FOR;
|
|
|
|
CMP_release(tdbb, handle);
|
|
CCH_release_exclusive(tdbb);
|
|
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
|
|
|
|
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, 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 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 */
|
|
|
|
partner_relation = NULL;
|
|
if (idx.idx_flags & idx_foreign)
|
|
{
|
|
/* Get an exclusive lock on the database if the index being
|
|
defined enforces a foreign key constraint. This will prevent
|
|
the constraint from being violated during index construction. */
|
|
|
|
if (MET_lookup_partner(tdbb, relation, &idx, work->dfw_name) &&
|
|
(partner_relation =
|
|
MET_lookup_relation_id( tdbb,
|
|
idx.idx_primary_relation,
|
|
true)) &&
|
|
!CCH_exclusive(tdbb, LCK_EX, LCK_NO_WAIT))
|
|
{
|
|
ERR_post(isc_no_meta_update,
|
|
isc_arg_gds, isc_obj_in_use,
|
|
isc_arg_string, partner_relation->rel_name,
|
|
0);
|
|
|
|
/* 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. */
|
|
|
|
if (partner_relation)
|
|
{
|
|
/* 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 || !partner_relation->rel_owner_name
|
|
|| strcmp(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);
|
|
SelectivityList selectivity(*tdbb->getDefaultPool());
|
|
IDX_create_index(tdbb, relation, &idx, work->dfw_name,
|
|
&work->dfw_id, transaction, selectivity);
|
|
fb_assert(work->dfw_id == idx.idx_id);
|
|
DFW_update_index(work->dfw_name, idx.idx_id, selectivity);
|
|
|
|
if (partner_relation)
|
|
{
|
|
relation->rel_flags |= REL_check_partners;
|
|
partner_relation->rel_flags |= REL_check_partners;
|
|
CCH_release_exclusive(tdbb);
|
|
}
|
|
|
|
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:
|
|
{
|
|
get_procedure_dependencies(work);
|
|
jrd_prc* procedure = MET_lookup_procedure(tdbb, work->dfw_name, false);
|
|
if (!procedure) {
|
|
return false;
|
|
}
|
|
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->tdbb_database;
|
|
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->tdbb_attachment;
|
|
|
|
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
|
|
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
|
|
|
|
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 relation wasn't found, don't do anymore. This can happen
|
|
when the relation is created and deleted in the same transaction. */
|
|
|
|
if (!rel_id)
|
|
break;
|
|
|
|
/* get the relation and flag it to check for dependencies
|
|
in the view blr (if it exists) and any computed fields */
|
|
|
|
relation = MET_relation(tdbb, rel_id);
|
|
relation->rel_flags |= REL_get_dependencies;
|
|
|
|
/* if this is not a view, create the relation */
|
|
|
|
if (blob_id.isEmpty())
|
|
{
|
|
if (!external_flag)
|
|
DPM_create_relation(tdbb, relation);
|
|
}
|
|
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:
|
|
get_trigger_dependencies(work);
|
|
break;
|
|
}
|
|
|
|
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, 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;
|
|
|
|
switch (phase)
|
|
{
|
|
case 0:
|
|
return false;
|
|
case 1:
|
|
{
|
|
// 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, isc_no_delete, // Msg353: can not delete
|
|
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);
|
|
|
|
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;
|
|
|
|
switch (phase)
|
|
{
|
|
case 0:
|
|
return false;
|
|
case 1:
|
|
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, "RDB$GENERATORS cannot be modified.",
|
|
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, 0,
|
|
obj_udf, transaction);
|
|
return true;
|
|
case 2:
|
|
return true;
|
|
case 3:
|
|
return true;
|
|
case 4:
|
|
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->tdbb_database;
|
|
|
|
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
|
|
/* 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_field_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 */
|
|
|
|
case 2:
|
|
return true;
|
|
|
|
case 3:
|
|
MET_delete_dependencies(tdbb, work->dfw_name, obj_computed);
|
|
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->tdbb_database;
|
|
|
|
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 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 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;
|
|
jrd_rel* relation;
|
|
USHORT id;
|
|
|
|
SET_TDBB(tdbb);
|
|
|
|
switch (phase)
|
|
{
|
|
case 0:
|
|
relation = MET_lookup_relation(tdbb, work->dfw_name);
|
|
if (!relation) {
|
|
return false;
|
|
}
|
|
id = work->dfw_id - 1;
|
|
index = CMP_get_index_lock(tdbb, relation, id);
|
|
if (index) {
|
|
if (!index->idl_count) {
|
|
LCK_release(tdbb, index->idl_lock);
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case 1:
|
|
case 2:
|
|
return true;
|
|
|
|
case 3:
|
|
/* Look up the relation. If we can't find the relation,
|
|
don't worry about the index. */
|
|
|
|
relation = MET_lookup_relation(tdbb, work->dfw_name);
|
|
if (!relation) {
|
|
return false;
|
|
}
|
|
|
|
/* Make sure nobody is currently using the index */
|
|
|
|
id = work->dfw_id - 1;
|
|
index = CMP_get_index_lock(tdbb, relation, id);
|
|
if (index)
|
|
{
|
|
// Try to clear trigger cache to release lock
|
|
if (index->idl_count)
|
|
MET_clear_cache(tdbb);
|
|
if (index->idl_count ||
|
|
!LCK_lock_non_blocking(tdbb, index->idl_lock, LCK_EX,
|
|
transaction->getLockWait()))
|
|
{
|
|
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:
|
|
relation = MET_lookup_relation(tdbb, work->dfw_name);
|
|
if (!relation) {
|
|
return false;
|
|
}
|
|
|
|
id = work->dfw_id - 1;
|
|
index = CMP_get_index_lock(tdbb, relation, id);
|
|
IDX_delete_index(tdbb, relation, id);
|
|
if (work->dfw_type == dfw_delete_expression_index)
|
|
{
|
|
MET_delete_dependencies(tdbb,
|
|
work->dfw_name,
|
|
obj_expression_index);
|
|
}
|
|
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:
|
|
if (MET_lookup_procedure_id(tdbb, work->dfw_id, false, true, 0))
|
|
{
|
|
check_dependencies(tdbb, work->dfw_name, work->dfw_name,
|
|
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, 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);
|
|
|
|
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->tdbb_database;
|
|
|
|
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
|
|
/* 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, 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;
|
|
}
|
|
#endif
|
|
if (relation->rel_file) {
|
|
EXT_fini (relation);
|
|
}
|
|
|
|
if (relation->rel_index_root) {
|
|
IDX_delete_indices(tdbb, relation);
|
|
}
|
|
|
|
if (relation->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);
|
|
}
|
|
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 = NULL;
|
|
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, id;
|
|
jrd_req* handle;
|
|
SqlIdentifier f;
|
|
jrd_rel* relation;
|
|
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->tdbb_database;
|
|
|
|
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
|
|
/* 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))
|
|
{
|
|
TEXT* p = f;
|
|
const TEXT* const endp = f + sizeof(f) - 1;
|
|
for (const TEXT* q = VFLD.RDB$BASE_FIELD; *q && p < endp; *p++ = *q++)
|
|
{
|
|
; /* empty body */
|
|
}
|
|
*p = 0;
|
|
fb_utils::exact_name(f);
|
|
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, work->dfw_name,
|
|
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)
|
|
{
|
|
id = MET_lookup_field(tdbb, relation, work->dfw_name, 0);
|
|
if (id >= 0)
|
|
{
|
|
vec* 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);
|
|
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->tdbb_database;
|
|
|
|
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_procedure:
|
|
dfw_type = dfw_delete_procedure;
|
|
break;
|
|
case obj_expression_index:
|
|
dfw_type = dfw_delete_index;
|
|
break;
|
|
default:
|
|
fb_assert(false);
|
|
break;
|
|
}
|
|
|
|
/* Look to see if an object of the desired type is being deleted. */
|
|
|
|
for (DeferredWork* work = transaction->tra_deferred_work; work;
|
|
work = work->dfw_next)
|
|
{
|
|
if (work->dfw_type == dfw_type &&
|
|
!strcmp(object_name, work->dfw_name) &&
|
|
(!rel_id || rel_id == work->dfw_id))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (dfw_type == dfw_delete_global)
|
|
{
|
|
/* 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;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static void get_array_desc(thread_db* tdbb, const TEXT* field_name,
|
|
internal_array_desc* 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->tdbb_database;
|
|
|
|
jrd_req* request = CMP_find_request(tdbb, irq_r_fld_dim, IRQ_REQUESTS);
|
|
|
|
internal_array_desc::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 = 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)
|
|
{
|
|
/**************************************
|
|
*
|
|
* 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->tdbb_database;
|
|
|
|
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
|
|
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, false);
|
|
|
|
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 = 0;
|
|
/* 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);
|
|
|
|
MET_get_dependencies(tdbb, NULL, NULL, NULL, &blob_id, &request,
|
|
NULL, work->dfw_name, obj_procedure);
|
|
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)
|
|
{
|
|
/**************************************
|
|
*
|
|
* 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->tdbb_database;
|
|
|
|
jrd_rel* relation = NULL;
|
|
|
|
jrd_req* handle = CMP_find_request(tdbb, irq_c_trigger, IRQ_REQUESTS);
|
|
|
|
bid blob_id;
|
|
blob_id.clear();
|
|
|
|
FOR(REQUEST_HANDLE handle)
|
|
X IN RDB$TRIGGERS WITH
|
|
X.RDB$TRIGGER_NAME EQ work->dfw_name
|
|
if (!REQUEST(irq_c_trigger))
|
|
REQUEST(irq_c_trigger) = handle;
|
|
blob_id = X.RDB$TRIGGER_BLR;
|
|
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 && !blob_id.isEmpty())
|
|
{
|
|
jrd_req* request = 0;
|
|
/* Nickolay Samofatov: allocate statement memory pool... */
|
|
JrdMemoryPool* new_pool = JrdMemoryPool::createPool();
|
|
Jrd::ContextPoolHolder context(tdbb, new_pool);
|
|
MET_get_dependencies(tdbb, relation, NULL, NULL, &blob_id,
|
|
&request, NULL, work->dfw_name, obj_trigger);
|
|
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->tdbb_database;
|
|
|
|
/* 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);
|
|
format->fmt_count = 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)
|
|
{
|
|
const ULONG old_offset = offset;
|
|
offset = MET_align(&*desc2, (USHORT) offset);
|
|
|
|
/* see of record too big */
|
|
|
|
if (offset < old_offset)
|
|
{
|
|
offset += 65536L;
|
|
break;
|
|
}
|
|
desc2->dsc_address = (UCHAR *) (IPTR) offset;
|
|
offset += desc2->dsc_length;
|
|
}
|
|
}
|
|
|
|
fb_assert(offset <= MAX_USHORT);
|
|
|
|
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* vector = relation->rel_formats =
|
|
vec::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);
|
|
BLB_put_segment(tdbb,
|
|
blob,
|
|
// (UCHAR*) &*(format->fmt_desc.begin()),
|
|
reinterpret_cast<const UCHAR*>(&(format->fmt_desc[0])),
|
|
(USHORT)(format->fmt_count * sizeof(dsc)));
|
|
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, external_flag = 0;
|
|
bool computed_field;
|
|
ISC_STATUS_ARRAY status;
|
|
trig_vec* triggers[TRIGGER_MAX];
|
|
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->tdbb_database;
|
|
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
|
|
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;
|
|
}
|
|
blob_id = REL.RDB$VIEW_BLR;
|
|
null_view = blob_id.isEmpty();
|
|
external_flag = REL.RDB$EXTERNAL_FILE[0];
|
|
if (REL.RDB$FORMAT == 255)
|
|
{
|
|
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 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 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;
|
|
|
|
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);
|
|
|
|
/* Make sure the text type specified is implemented */
|
|
if (!(validate_text_type(tdbb, status, tfb)))
|
|
{
|
|
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),
|
|
status[0], status[1], status[2], status[3],
|
|
0);
|
|
}
|
|
|
|
// dimitr: view fields shoudn't be marked as computed
|
|
if (null_view && !FLD.RDB$COMPUTED_BLR.isEmpty())
|
|
tfb->tfb_flags |= TFB_computed;
|
|
|
|
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;
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
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 relation 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->tdbb_database->dbb_flags & DBB_sp_rec_mutex_init))
|
|
{
|
|
THD_rec_mutex_init(&tdbb->tdbb_database->dbb_sp_rec_mutex);
|
|
tdbb->tdbb_database->dbb_flags |= DBB_sp_rec_mutex_init;
|
|
}
|
|
THREAD_EXIT();
|
|
if (THD_rec_mutex_lock(&tdbb->tdbb_database->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);
|
|
|
|
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 */
|
|
|
|
get_procedure_dependencies(work);
|
|
|
|
procedure->prc_flags &= ~(PRC_obsolete | PRC_being_altered);
|
|
|
|
} // try
|
|
catch (const std::exception& ex)
|
|
{
|
|
Firebird::stuff_exception(tdbb->tdbb_status_vector, ex);
|
|
#ifdef SUPERSERVER
|
|
THD_rec_mutex_unlock(&tdbb->tdbb_database->dbb_sp_rec_mutex);
|
|
#endif
|
|
ERR_punt();
|
|
}
|
|
|
|
#ifdef SUPERSERVER
|
|
THD_rec_mutex_unlock(&tdbb->tdbb_database->dbb_sp_rec_mutex);
|
|
#endif
|
|
|
|
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);
|
|
|
|
switch (phase)
|
|
{
|
|
case 1:
|
|
case 2:
|
|
return true;
|
|
|
|
case 3:
|
|
/* get rid of old dependencies, bring in the new */
|
|
|
|
MET_delete_dependencies(tdbb, work->dfw_name, obj_trigger);
|
|
get_trigger_dependencies(work);
|
|
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[256]; /* Must hold largest metadata field */
|
|
|
|
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, length)) {
|
|
++work->dfw_count;
|
|
return *ptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Not already posted, so do so now. */
|
|
|
|
*ptr = work = FB_NEW_RPT(*transaction->tra_pool, length) DeferredWork;
|
|
work->dfw_type = type;
|
|
work->dfw_id = id;
|
|
work->dfw_count = 1;
|
|
work->dfw_name_length = length;
|
|
work->dfw_sav_number = sav_number;
|
|
if (length)
|
|
MOVE_FAST(string, work->dfw_name, 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->tdbb_database;
|
|
|
|
/* 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 *buffer = (length > sizeof(temp)) ?
|
|
FB_NEW(*getDefaultMemoryPool()) UCHAR[blr->blb_length] : temp;
|
|
try {
|
|
length = (USHORT)BLB_get_data(tdbb, blr, buffer, (SLONG) length);
|
|
put_summary_record(blob, type, buffer, length);
|
|
}
|
|
catch (const std::exception&) {
|
|
if (buffer != temp)
|
|
delete[] buffer;
|
|
throw;
|
|
}
|
|
|
|
if (buffer != temp)
|
|
delete[] buffer;
|
|
}
|
|
|
|
|
|
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();
|
|
UCHAR temp[129];
|
|
|
|
UCHAR* buffer = ((size_t) (length + 1) > sizeof(temp)) ?
|
|
FB_NEW(*getDefaultMemoryPool()) UCHAR[length + 1] : temp;
|
|
|
|
UCHAR* p = buffer;
|
|
|
|
*p++ = (UCHAR) type;
|
|
|
|
if (length) {
|
|
do {
|
|
*p++ = *data++;
|
|
} while (--length);
|
|
}
|
|
|
|
try {
|
|
BLB_put_segment(tdbb, blob, buffer, (USHORT)(p - buffer));
|
|
}
|
|
catch (const std::exception&) {
|
|
if (buffer != temp)
|
|
delete[] buffer;
|
|
throw;
|
|
}
|
|
|
|
if (buffer != temp)
|
|
delete[] buffer;
|
|
}
|
|
|
|
|
|
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:
|
|
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->tdbb_database;
|
|
|
|
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;
|
|
internal_array_desc* array = reinterpret_cast<internal_array_desc*>(stuff);
|
|
MOVE_CLEAR(array, (SLONG) sizeof(internal_array_desc));
|
|
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->tdbb_database;
|
|
|
|
|
|
/* 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
|
|
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
|
|
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
|
|
AND (TRG.RDB$SYSTEM_FLAG BT frb_sysflag_check_constraint AND frb_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)
|
|
relation->rel_name = MET_save_name(tdbb, relation_name);
|
|
MET_load_trigger(tdbb, relation, trigger_name, triggers);
|
|
}
|
|
}
|
|
|
|
|
|
static bool validate_text_type(thread_db* tdbb, ISC_STATUS* status,
|
|
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, status, 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, status, tfb->tfb_desc.dsc_scale)))
|
|
{
|
|
return false;
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|