mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-26 07:23:08 +01:00
4319 lines
109 KiB
C++
4319 lines
109 KiB
C++
/*
|
|
* PROGRAM: JRD Access Method
|
|
* MODULE: exe.c
|
|
* DESCRIPTION: Statement execution
|
|
*
|
|
* 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.21 Claudio Valderrama: Allow inserting strings into blob fields.
|
|
* 2001.6.28 Claudio Valderrama: Move code to cleanup_rpb() as directed
|
|
* by Ann Harrison and cleanup of new record in store() routine.
|
|
* 2001.10.11 Claudio Valderrama: Fix SF Bug #436462: From now, we only
|
|
* count real store, modify and delete operations either in an external
|
|
* file or in a table. Counting on a view caused up to three operations
|
|
* being reported instead of one.
|
|
* 2001.12.03 Claudio Valderrama: new visit to the same issue: views need
|
|
* to count virtual operations, not real I/O on the underlying tables.
|
|
* 2002.09.28 Dmitry Yemanov: Reworked internal_info stuff, enhanced
|
|
* exception handling in SPs/triggers,
|
|
* implemented ROWS_AFFECTED system variable
|
|
*
|
|
* 2002.10.21 Nickolay Samofatov: Added support for explicit pessimistic locks
|
|
* 2002.10.28 Sean Leyne - Code cleanup, removed obsolete "MPEXL" port
|
|
* 2002.10.28 Sean Leyne - Code cleanup, removed obsolete "DecOSF" port
|
|
* 2002.10.29 Nickolay Samofatov: Added support for savepoints
|
|
*
|
|
* 2002.10.30 Sean Leyne - Removed support for obsolete "PC_PLATFORM" define
|
|
*
|
|
*/
|
|
/*
|
|
$Id: exe.cpp,v 1.30 2002-11-14 07:42:50 dimitr Exp $
|
|
*/
|
|
|
|
#include "firebird.h"
|
|
#if TIME_WITH_SYS_TIME
|
|
# include <sys/time.h>
|
|
# include <time.h>
|
|
#else
|
|
# if HAVE_SYS_TIME_H
|
|
# include <sys/time.h>
|
|
# else
|
|
# include <time.h>
|
|
# endif
|
|
#endif
|
|
|
|
#include "../jrd/common.h"
|
|
#include "../jrd/ibsetjmp.h"
|
|
#include <string.h>
|
|
#include "../jrd/jrd.h"
|
|
#include "../jrd/req.h"
|
|
#include "../jrd/val.h"
|
|
#include "../jrd/exe.h"
|
|
#include "../jrd/tra.h"
|
|
#include "gen/codes.h"
|
|
#include "../jrd/ods.h"
|
|
#include "../jrd/btr.h"
|
|
#include "../jrd/rse.h"
|
|
#include "../jrd/lck.h"
|
|
#include "../jrd/intl.h"
|
|
#include "../jrd/rng.h"
|
|
#include "../jrd/sbm.h"
|
|
#include "../jrd/blb.h"
|
|
#include "../jrd/blr.h"
|
|
#include "../jrd/all_proto.h"
|
|
#include "../jrd/bookmark.h"
|
|
#include "../jrd/blb_proto.h"
|
|
#include "../jrd/btr_proto.h"
|
|
#include "../jrd/cmp_proto.h"
|
|
#include "../jrd/dfw_proto.h"
|
|
#include "../jrd/dpm_proto.h"
|
|
#include "../jrd/err_proto.h"
|
|
#include "../jrd/evl_proto.h"
|
|
#include "../jrd/exe_proto.h"
|
|
#include "../jrd/ext_proto.h"
|
|
#include "../jrd/gds_proto.h"
|
|
#include "../jrd/idx_proto.h"
|
|
#include "../jrd/jrd_proto.h"
|
|
|
|
#include "../jrd/lck_proto.h"
|
|
#include "../jrd/met_proto.h"
|
|
#include "../jrd/mov_proto.h"
|
|
#include "../jrd/opt_proto.h"
|
|
#include "../jrd/par_proto.h"
|
|
#include "../jrd/rlck_proto.h"
|
|
#include "../jrd/rse_proto.h"
|
|
#include "../jrd/rng_proto.h"
|
|
#include "../jrd/thd_proto.h"
|
|
#include "../jrd/tra_proto.h"
|
|
#include "../jrd/vio_proto.h"
|
|
#include "../jrd/isc_s_proto.h"
|
|
|
|
#ifdef HSDEBUGSTACK
|
|
#include "TestStck.h"
|
|
#endif
|
|
|
|
extern "C" {
|
|
|
|
|
|
// fwd. decl.
|
|
IDX_E IDX_modify_check_constraints(TDBB tdbb,
|
|
RPB * org_rpb,
|
|
RPB * new_rpb,
|
|
TRA transaction,
|
|
REL * bad_relation, USHORT * bad_index); // defined in idx.cpp
|
|
|
|
|
|
#ifdef SHLIB_DEFS
|
|
#undef send
|
|
#endif
|
|
|
|
|
|
static void assign_xcp_message(TDBB, STR *, const TEXT *);
|
|
static void cleanup_rpb(TDBB, RPB *);
|
|
static JRD_NOD erase(TDBB, JRD_NOD, SSHORT);
|
|
static void execute_looper(TDBB, REQ, TRA, ENUM req::req_s);
|
|
static void exec_sql(TDBB, REQ, DSC *);
|
|
static void execute_procedure(TDBB, JRD_NOD);
|
|
static REQ execute_triggers(TDBB, TRIG_VEC *, REC, REC);
|
|
static JRD_NOD looper(TDBB, REQ, JRD_NOD);
|
|
static JRD_NOD modify(TDBB, register JRD_NOD, SSHORT);
|
|
static void writelock(TDBB, register JRD_NOD);
|
|
static JRD_NOD receive_msg(TDBB, register JRD_NOD);
|
|
static void release_blobs(TDBB, REQ);
|
|
static void release_proc_save_points(REQ);
|
|
#ifdef SCROLLABLE_CURSORS
|
|
static JRD_NOD seek_rse(TDBB, REQ, JRD_NOD);
|
|
static void seek_rsb(TDBB, REQ, RSB, USHORT, SLONG);
|
|
#endif
|
|
static JRD_NOD selct(TDBB, register JRD_NOD);
|
|
static JRD_NOD send_msg(TDBB, register JRD_NOD);
|
|
static void set_error(TDBB, XCP, JRD_NOD);
|
|
static JRD_NOD stall(TDBB, register JRD_NOD);
|
|
static JRD_NOD store(TDBB, register JRD_NOD, SSHORT);
|
|
static BOOLEAN test_and_fixup_error(TDBB, const XCP, REQ);
|
|
static void trigger_failure(TDBB, REQ);
|
|
static void validate(TDBB, JRD_NOD);
|
|
|
|
#ifdef PC_ENGINE
|
|
static BOOLEAN check_crack(RSB, USHORT);
|
|
static JRD_NOD find(TDBB, register JRD_NOD);
|
|
static JRD_NOD find_dbkey(TDBB, register JRD_NOD);
|
|
static LCK implicit_record_lock(TRA, RPB *);
|
|
static JRD_NOD release_bookmark(TDBB, JRD_NOD);
|
|
static JRD_NOD set_bookmark(TDBB, JRD_NOD);
|
|
static JRD_NOD set_index(TDBB, register JRD_NOD);
|
|
static JRD_NOD stream(TDBB, register JRD_NOD);
|
|
#endif
|
|
|
|
#ifdef DEBUG_GDS_ALLOC
|
|
static SLONG memory_debug = 1;
|
|
static SLONG memory_count = 0;
|
|
#endif /* DEBUG_GDS_ALLOC */
|
|
|
|
/* macro definitions */
|
|
|
|
#define NULL_STRING "*** null ***"
|
|
|
|
#if (defined SUPERSERVER) && (defined WIN_NT || defined SOLARIS_MT)
|
|
#define MAX_CLONES 750
|
|
#endif
|
|
|
|
#if defined (HP10) && defined (SUPERSERVER)
|
|
#define MAX_CLONES 110
|
|
#endif
|
|
|
|
#ifndef MAX_CLONES
|
|
#define MAX_CLONES 1000
|
|
#endif
|
|
|
|
#define MAX_CALLBACKS 50
|
|
|
|
#define ALL_TRIGS 0
|
|
#define PRE_TRIG 1
|
|
#define POST_TRIG 2
|
|
|
|
/* this constant defines how many records are locked
|
|
before we check whether record locking has been
|
|
turned off for a given relation; if we set the
|
|
constant to a low number, we will do too much
|
|
locking in the case where record locking is always
|
|
turned on; too high and we will do too much record
|
|
locking in the case where someone is only occasionally
|
|
locking a record */
|
|
|
|
#define RECORD_LOCK_CHECK_INTERVAL 10
|
|
|
|
|
|
#ifdef PC_ENGINE
|
|
// TMN: RAII class for LCK. Unlocks the LCK on destruction.
|
|
class LCK_RAII_wrapper
|
|
{
|
|
LCK_RAII_wrapper() : l(0) {}
|
|
~LCK_RAII_wrapper() {
|
|
if (l) {
|
|
RLCK_unlock_record_implicit(l, 0);
|
|
}
|
|
}
|
|
void assign(LCK lock) { l = lck; }
|
|
|
|
LCK l;
|
|
|
|
private:
|
|
LCK_RAII_wrapper(const LCK_RAII_wrapper&); // no impl.
|
|
void operator=(const LCK_RAII_wrapper&); // no impl.
|
|
};
|
|
#endif
|
|
|
|
|
|
void EXE_assignment(TDBB tdbb, JRD_NOD node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* E X E _ a s s i g n m e n t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Perform an assignment
|
|
*
|
|
**************************************/
|
|
|
|
DSC temp;
|
|
|
|
DEV_BLKCHK(node, type_nod);
|
|
|
|
SET_TDBB(tdbb);
|
|
REQ request = tdbb->tdbb_request;
|
|
BLKCHK(node, type_nod);
|
|
|
|
/* Get descriptors of receiving and sending fields/parmaters, variables, etc. */
|
|
|
|
DSC* missing = NULL;
|
|
if (node->nod_arg[e_asgn_missing]) {
|
|
missing = EVL_expr(tdbb, node->nod_arg[e_asgn_missing]);
|
|
}
|
|
|
|
JRD_NOD to = node->nod_arg[e_asgn_to];
|
|
DSC* to_desc = EVL_assign_to(tdbb, to);
|
|
|
|
request->req_flags &= ~req_null;
|
|
|
|
DSC* from_desc = EVL_expr(tdbb, node->nod_arg[e_asgn_from]);
|
|
|
|
SSHORT null = (request->req_flags & req_null) ? -1 : 0;
|
|
|
|
if (!null && missing && MOV_compare(missing, from_desc) == 0) {
|
|
null = -1;
|
|
}
|
|
|
|
/* If the value is non-missing, move/convert it. Otherwise fill the
|
|
field with appropriate nulls. */
|
|
|
|
if (!null)
|
|
{
|
|
/* if necessary and appropriate, use the indicator variable */
|
|
|
|
if (to->nod_type == nod_argument && to->nod_arg[e_arg_indicator])
|
|
{
|
|
DSC* indicator = EVL_assign_to(tdbb, to->nod_arg[e_arg_indicator]);
|
|
temp.dsc_dtype = dtype_short;
|
|
temp.dsc_length = sizeof(SSHORT);
|
|
temp.dsc_scale = 0;
|
|
temp.dsc_sub_type = 0;
|
|
|
|
SSHORT len;
|
|
|
|
if ((from_desc->dsc_dtype <= dtype_varying) &&
|
|
(to_desc->dsc_dtype <= dtype_varying) &&
|
|
(TEXT_LEN(from_desc) > TEXT_LEN(to_desc)))
|
|
{
|
|
len = TEXT_LEN(from_desc);
|
|
} else {
|
|
len = 0;
|
|
}
|
|
|
|
temp.dsc_address = (UCHAR *) & len;
|
|
MOV_move(&temp, indicator);
|
|
|
|
if (len) {
|
|
temp = *from_desc;
|
|
temp.dsc_length = TEXT_LEN(to_desc);
|
|
if (temp.dsc_dtype == dtype_cstring) {
|
|
temp.dsc_length += 1;
|
|
} else if (temp.dsc_dtype == dtype_varying) {
|
|
temp.dsc_length += 2;
|
|
}
|
|
from_desc = &temp;
|
|
}
|
|
}
|
|
|
|
#ifndef VMS
|
|
if (DTYPE_IS_BLOB(to_desc->dsc_dtype))
|
|
#else
|
|
if (DTYPE_IS_BLOB(to_desc->dsc_dtype)
|
|
&& to_desc->dsc_dtype != dtype_d_float)
|
|
#endif
|
|
{
|
|
/* CVC: This is a case that has hurt me for years and I'm going to solve it.
|
|
It should be possible to copy a string to a blob, even if the charset is
|
|
lost as a result of this experimental implementation. */
|
|
if (from_desc->dsc_dtype <= dtype_varying)
|
|
BLB_move_from_string(tdbb, from_desc, to_desc, to);
|
|
else
|
|
BLB_move(tdbb, from_desc, to_desc, to);
|
|
}
|
|
|
|
else if (!DSC_EQUIV(from_desc, to_desc))
|
|
MOV_move(from_desc, to_desc);
|
|
|
|
else if (from_desc->dsc_dtype == dtype_short)
|
|
*((SSHORT *) to_desc->dsc_address) =
|
|
*((SSHORT *) from_desc->dsc_address);
|
|
|
|
else if (from_desc->dsc_dtype == dtype_long)
|
|
*((SLONG *) to_desc->dsc_address) =
|
|
*((SLONG *) from_desc->dsc_address);
|
|
|
|
else if (from_desc->dsc_dtype == dtype_int64)
|
|
*((SINT64 *) to_desc->dsc_address) =
|
|
*((SINT64 *) from_desc->dsc_address);
|
|
|
|
else if (((U_IPTR) from_desc->dsc_address & (ALIGNMENT - 1)) ||
|
|
((U_IPTR) to_desc->dsc_address & (ALIGNMENT - 1)))
|
|
MOVE_FAST(from_desc->dsc_address, to_desc->dsc_address,
|
|
from_desc->dsc_length);
|
|
|
|
else
|
|
MOVE_FASTER(from_desc->dsc_address, to_desc->dsc_address,
|
|
from_desc->dsc_length);
|
|
to_desc->dsc_flags &= ~DSC_null;
|
|
}
|
|
else if (node->nod_arg[e_asgn_missing2] &&
|
|
(missing = EVL_expr(tdbb, node->nod_arg[e_asgn_missing2])))
|
|
{
|
|
MOV_move(missing, to_desc);
|
|
to_desc->dsc_flags |= DSC_null;
|
|
}
|
|
else
|
|
{
|
|
UCHAR *p;
|
|
USHORT l;
|
|
|
|
l = to_desc->dsc_length;
|
|
p = to_desc->dsc_address;
|
|
switch (to_desc->dsc_dtype) {
|
|
case dtype_text:
|
|
/* YYY - not necessarily the right thing to do */
|
|
/* for text formats that don't have trailing spaces */
|
|
if (l) {
|
|
do {
|
|
*p++ = ' ';
|
|
} while (--l);
|
|
}
|
|
break;
|
|
|
|
case dtype_cstring:
|
|
*p = 0;
|
|
break;
|
|
|
|
case dtype_varying:
|
|
*(SSHORT *) p = 0;
|
|
break;
|
|
|
|
default:
|
|
do
|
|
*p++ = 0;
|
|
while (--l);
|
|
break;
|
|
}
|
|
to_desc->dsc_flags |= DSC_null;
|
|
}
|
|
|
|
/* Handle the null flag as appropriate for fields and message arguments. */
|
|
|
|
if (to->nod_type == nod_field)
|
|
{
|
|
SSHORT id = (USHORT) to->nod_arg[e_fld_id];
|
|
REC record = request->req_rpb[(int) to->nod_arg[e_fld_stream]].rpb_record;
|
|
if (null) {
|
|
SET_NULL(record, id);
|
|
} else {
|
|
CLEAR_NULL(record, id);
|
|
}
|
|
}
|
|
else if (to->nod_type == nod_argument && to->nod_arg[e_arg_flag])
|
|
{
|
|
to_desc = EVL_assign_to(tdbb, to->nod_arg[e_arg_flag]);
|
|
|
|
/* If the null flag is a string with an effective length of one,
|
|
then -1 will not fit. Therefore, store 1 instead. */
|
|
|
|
if (null &&
|
|
to_desc->dsc_dtype <= dtype_varying &&
|
|
to_desc->dsc_length <=
|
|
((to_desc->dsc_dtype == dtype_text) ? 1 :
|
|
((to_desc->dsc_dtype == dtype_cstring) ? 2 :
|
|
3)))
|
|
{
|
|
null = 1;
|
|
}
|
|
|
|
temp.dsc_dtype = dtype_short;
|
|
temp.dsc_length = sizeof(SSHORT);
|
|
temp.dsc_scale = 0;
|
|
temp.dsc_sub_type = 0;
|
|
temp.dsc_address = (UCHAR *) & null;
|
|
MOV_move(&temp, to_desc);
|
|
if (null && to->nod_arg[e_arg_indicator]) {
|
|
to_desc = EVL_assign_to(tdbb, to->nod_arg[e_arg_indicator]);
|
|
MOV_move(&temp, to_desc);
|
|
}
|
|
}
|
|
|
|
request->req_operation = req::req_return;
|
|
}
|
|
|
|
|
|
#ifdef PC_ENGINE
|
|
BOOLEAN EXE_crack(TDBB tdbb, RSB rsb, USHORT flags)
|
|
{
|
|
/**************************************
|
|
*
|
|
* E X E _ c r a c k
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Check whether stream is on a crack, BOF
|
|
* or EOF, according to the flags passed.
|
|
*
|
|
**************************************/
|
|
REQ request;
|
|
RPB *rpb;
|
|
IRSB impure;
|
|
|
|
|
|
DEV_BLKCHK(rsb, type_rsb);
|
|
|
|
SET_TDBB(tdbb);
|
|
request = tdbb->tdbb_request;
|
|
|
|
/* correct boolean rsbs to point to the "real" rsb */
|
|
|
|
if (rsb->rsb_type == rsb_boolean)
|
|
rsb = rsb->rsb_next;
|
|
impure = (IRSB) ((UCHAR *) request + rsb->rsb_impure);
|
|
|
|
/* if any of the passed flags are set, return TRUE */
|
|
|
|
if (impure->irsb_flags & flags)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
|
|
REQ EXE_find_request(TDBB tdbb, REQ request, BOOLEAN validate)
|
|
{
|
|
/**************************************
|
|
*
|
|
* E X E _ f i n d _ r e q u e s t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Find an inactive incarnation of a trigger request. If necessary,
|
|
* clone it.
|
|
*
|
|
**************************************/
|
|
#ifdef ANY_THREADING
|
|
DBB dbb;
|
|
#endif
|
|
REQ clone, next;
|
|
USHORT n, clones, count;
|
|
VEC vector;
|
|
|
|
DEV_BLKCHK(request, type_req);
|
|
|
|
SET_TDBB(tdbb);
|
|
#ifdef ANY_THREADING
|
|
dbb = tdbb->tdbb_database;
|
|
#endif
|
|
|
|
/* I found a core file from my test runs that came from a NULL request -
|
|
* but have no idea what test was running. Let's bugcheck so we can
|
|
* figure it out
|
|
*/
|
|
if (!request)
|
|
BUGCHECK /* REQUEST */ (167); /* msg 167 invalid SEND request */
|
|
|
|
THD_MUTEX_LOCK(dbb->dbb_mutexes + DBB_MUTX_clone);
|
|
clone = NULL;
|
|
count = 0;
|
|
if (!(request->req_flags & req_in_use))
|
|
clone = request;
|
|
else {
|
|
if (request->req_attachment == tdbb->tdbb_attachment)
|
|
count++;
|
|
|
|
/* Request exists and is in use. Search clones for one in use by
|
|
this attachment. If not found, return first inactive request. */
|
|
|
|
clones = (vector =
|
|
request->req_sub_requests) ? (vector->count() - 1) : 0;
|
|
|
|
for (n = 1; n <= clones; n++) {
|
|
next = CMP_clone_request(tdbb, request, n, validate);
|
|
if (next->req_attachment == tdbb->tdbb_attachment) {
|
|
if (!(next->req_flags & req_in_use)) {
|
|
clone = next;
|
|
break;
|
|
}
|
|
else
|
|
count++;
|
|
}
|
|
else if (!(next->req_flags & req_in_use) && !clone)
|
|
clone = next;
|
|
}
|
|
|
|
if (count > MAX_CLONES) {
|
|
THD_MUTEX_UNLOCK(dbb->dbb_mutexes + DBB_MUTX_clone);
|
|
ERR_post(gds_req_max_clones_exceeded, 0);
|
|
}
|
|
if (!clone)
|
|
clone = CMP_clone_request(tdbb, request, n, validate);
|
|
}
|
|
clone->req_attachment = tdbb->tdbb_attachment;
|
|
clone->req_flags |= req_in_use;
|
|
THD_MUTEX_UNLOCK(dbb->dbb_mutexes + DBB_MUTX_clone);
|
|
return clone;
|
|
}
|
|
|
|
|
|
#ifdef PC_ENGINE
|
|
void EXE_mark_crack(TDBB tdbb, RSB rsb, USHORT flag)
|
|
{
|
|
/**************************************
|
|
*
|
|
* E X E _ m a r k _ c r a c k
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Mark a stream as being at a crack,
|
|
* plus report the fact in the status
|
|
* vector.
|
|
*
|
|
**************************************/
|
|
|
|
SET_TDBB(tdbb);
|
|
DEV_BLKCHK(rsb, type_rsb);
|
|
|
|
/* correct boolean rsbs to point to the "real" rsb */
|
|
|
|
if (rsb->rsb_type == rsb_boolean)
|
|
rsb = rsb->rsb_next;
|
|
|
|
RSE_MARK_CRACK(tdbb, rsb, flag);
|
|
|
|
if (flag == irsb_eof)
|
|
ERR_warning(gds_stream_eof, 0);
|
|
else if (flag == irsb_bof)
|
|
ERR_warning(gds_stream_bof, 0);
|
|
else if (flag & irsb_crack)
|
|
ERR_warning(gds_stream_crack, 0);
|
|
}
|
|
#endif
|
|
|
|
|
|
void EXE_receive(TDBB tdbb,
|
|
REQ request,
|
|
USHORT msg,
|
|
USHORT length,
|
|
UCHAR* buffer)
|
|
{
|
|
/**************************************
|
|
*
|
|
* E X E _ r e c e i v e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Move a message from JRD to the host program. This corresponds to
|
|
* a JRD BLR/JRD_NOD send.
|
|
*
|
|
**************************************/
|
|
JRD_NOD message;
|
|
FMT format;
|
|
TRA transaction;
|
|
SAV save_sav_point;
|
|
|
|
SET_TDBB(tdbb);
|
|
|
|
DEV_BLKCHK(request, type_req);
|
|
|
|
transaction = request->req_transaction;
|
|
|
|
if (!(request->req_flags & req_active)) {
|
|
ERR_post(gds_req_sync, 0);
|
|
}
|
|
|
|
if (request->req_flags & req_proc_fetch)
|
|
{
|
|
/* request->req_proc_sav_point stores all the request savepoints.
|
|
When going to continue execution put request save point list
|
|
into transaction->tra_save_point so that it is used in looper.
|
|
When we come back to EXE_receive() restore
|
|
transaction->tra_save_point and merge all work done under
|
|
stored procedure savepoints into the current transaction
|
|
savepoint, which is the savepoint for fetch. */
|
|
|
|
save_sav_point = transaction->tra_save_point;
|
|
transaction->tra_save_point = request->req_proc_sav_point;
|
|
request->req_proc_sav_point = save_sav_point;
|
|
|
|
if (!transaction->tra_save_point) {
|
|
VIO_start_save_point(tdbb, transaction);
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
|
|
if (request->req_message->nod_type == nod_stall
|
|
#ifdef SCROLLABLE_CURSORS
|
|
|| request->req_flags & req_fetch_required
|
|
#endif
|
|
)
|
|
execute_looper(tdbb, request, transaction, req::req_sync);
|
|
|
|
if (!(request->req_flags & req_active) ||
|
|
request->req_operation != req::req_send)
|
|
{
|
|
ERR_post(gds_req_sync, 0);
|
|
}
|
|
|
|
message = request->req_message;
|
|
format = (FMT) message->nod_arg[e_msg_format];
|
|
|
|
if (msg != (USHORT) message->nod_arg[e_msg_number])
|
|
ERR_post(gds_req_sync, 0);
|
|
|
|
if (length != format->fmt_length)
|
|
ERR_post(gds_port_len,
|
|
gds_arg_number, (SLONG) length,
|
|
gds_arg_number, (SLONG) format->fmt_length, 0);
|
|
|
|
if ((U_IPTR) buffer & (ALIGNMENT - 1))
|
|
MOVE_FAST((SCHAR *) request + message->nod_impure, buffer, length);
|
|
else
|
|
MOVE_FASTER((SCHAR *) request + message->nod_impure, buffer, length);
|
|
|
|
execute_looper(tdbb, request, transaction, req::req_proceed);
|
|
|
|
} //try
|
|
catch (std::exception &e)
|
|
{
|
|
if (request->req_flags & req_proc_fetch)
|
|
{
|
|
save_sav_point = transaction->tra_save_point;
|
|
transaction->tra_save_point = request->req_proc_sav_point;
|
|
request->req_proc_sav_point = save_sav_point;
|
|
release_proc_save_points(request);
|
|
Firebird::status_exception::raise(-1);
|
|
}
|
|
throw;
|
|
}
|
|
|
|
if (request->req_flags & req_proc_fetch) {
|
|
save_sav_point = transaction->tra_save_point;
|
|
transaction->tra_save_point = request->req_proc_sav_point;
|
|
request->req_proc_sav_point = save_sav_point;
|
|
VIO_merge_proc_sav_points(tdbb, transaction,
|
|
&request->req_proc_sav_point);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
void EXE_seek(TDBB tdbb, REQ request, USHORT direction, ULONG offset)
|
|
{
|
|
/**************************************
|
|
*
|
|
* E X E _ s e e k
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Seek a given request in a particular direction
|
|
* for offset records.
|
|
*
|
|
**************************************/
|
|
VEC vector;
|
|
RSB rsb;
|
|
SLONG i;
|
|
|
|
SET_TDBB(tdbb);
|
|
DEV_BLKCHK(request, type_req);
|
|
|
|
/* loop through all RSEs in the request,
|
|
and describe the rsb tree for that rsb;
|
|
go backwards because items were popped
|
|
off the stack backwards */
|
|
|
|
vector = request->req_fors;
|
|
if (!vector)
|
|
return FALSE;
|
|
|
|
/* find the top-level rsb in the request and seek it */
|
|
|
|
for (i = vector->vec_count - 1; i >= 0; i--)
|
|
if (rsb = (RSB) vector->vec_object[i]) {
|
|
seek_rsb(tdbb, request, rsb, direction, offset);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
void EXE_send(TDBB tdbb,
|
|
REQ request,
|
|
USHORT msg,
|
|
USHORT length,
|
|
UCHAR* buffer)
|
|
{
|
|
/**************************************
|
|
*
|
|
* E X E _ s e n d
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Send a message from the host program to the engine.
|
|
* This corresponds to a blr_receive or blr_select statement.
|
|
*
|
|
**************************************/
|
|
JRD_NOD node, message, *ptr, *end;
|
|
FMT format;
|
|
TRA transaction;
|
|
#ifdef SCROLLABLE_CURSORS
|
|
USHORT save_operation;
|
|
JRD_NOD save_next = NULL, save_message;
|
|
#endif
|
|
|
|
SET_TDBB(tdbb);
|
|
DEV_BLKCHK(request, type_req);
|
|
|
|
if (!(request->req_flags & req_active))
|
|
ERR_post(gds_req_sync, 0);
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
/* look for an asynchronous send message--if such
|
|
a message was defined, we allow the user to send
|
|
us a message at any time during request execution */
|
|
|
|
if ((message = request->req_async_message) &&
|
|
(node = message->nod_arg[e_send_message]) &&
|
|
(msg == (USHORT) node->nod_arg[e_msg_number])) {
|
|
/* save the current state of the request so we can go
|
|
back to what was interrupted */
|
|
|
|
save_operation = request->req_operation;
|
|
save_message = request->req_message;
|
|
save_next = request->req_next;
|
|
|
|
request->req_operation = req_receive;
|
|
request->req_message = node;
|
|
request->req_next = message->nod_arg[e_send_statement];
|
|
|
|
/* indicate that we are processing an asynchronous message */
|
|
|
|
request->req_flags |= req_async_processing;
|
|
}
|
|
else {
|
|
#endif
|
|
if (request->req_operation != req::req_receive)
|
|
ERR_post(gds_req_sync, 0);
|
|
node = request->req_message;
|
|
#ifdef SCROLLABLE_CURSORS
|
|
}
|
|
#endif
|
|
|
|
transaction = request->req_transaction;
|
|
|
|
if (node->nod_type == nod_message)
|
|
message = node;
|
|
else if (node->nod_type == nod_select)
|
|
for (ptr = node->nod_arg, end = ptr + node->nod_count; ptr < end;
|
|
ptr++) {
|
|
message = (*ptr)->nod_arg[e_send_message];
|
|
if ((USHORT) message->nod_arg[e_msg_number] == msg) {
|
|
request->req_next = *ptr;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
BUGCHECK(167); /* msg 167 invalid SEND request */
|
|
|
|
format = (FMT) message->nod_arg[e_msg_format];
|
|
|
|
if (msg != (USHORT) message->nod_arg[e_msg_number])
|
|
ERR_post(gds_req_sync, 0);
|
|
|
|
if (length != format->fmt_length)
|
|
ERR_post(gds_port_len,
|
|
gds_arg_number, (SLONG) length,
|
|
gds_arg_number, (SLONG) format->fmt_length, 0);
|
|
|
|
if ((U_IPTR) buffer & (ALIGNMENT - 1))
|
|
MOVE_FAST(buffer, (SCHAR *) request + message->nod_impure, length);
|
|
else
|
|
MOVE_FASTER(buffer, (SCHAR *) request + message->nod_impure, length);
|
|
|
|
execute_looper(tdbb, request, transaction, req::req_proceed);
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
if (save_next) {
|
|
/* if the message was sent asynchronously, restore all the
|
|
previous values so that whatever we were trying to do when
|
|
the message came in is what we do next */
|
|
|
|
request->req_operation = save_operation;
|
|
request->req_message = save_message;
|
|
request->req_next = save_next;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void EXE_start(TDBB tdbb, REQ request, TRA transaction)
|
|
{
|
|
/**************************************
|
|
*
|
|
* E X E _ s t a r t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Start an execution running.
|
|
*
|
|
**************************************/
|
|
DBB dbb;
|
|
|
|
SET_TDBB(tdbb);
|
|
dbb = tdbb->tdbb_database;
|
|
|
|
BLKCHK(request, type_req);
|
|
BLKCHK(transaction, type_tra);
|
|
|
|
if (request->req_flags & req_active)
|
|
ERR_post(gds_req_sync, gds_arg_gds, gds_reqinuse, 0);
|
|
|
|
if (transaction->tra_flags & TRA_prepared)
|
|
ERR_post(gds_req_no_trans, 0);
|
|
|
|
/* Post resources to transaction block. In particular, the interest locks
|
|
on relations/indices are copied to the transaction, which is very
|
|
important for (short-lived) dynamically compiled requests. This will
|
|
provide transaction stability by preventing a relation from being
|
|
dropped after it has been referenced from an active transaction. */
|
|
|
|
TRA_post_resources(tdbb, transaction, request->req_resources);
|
|
|
|
request->req_transaction = transaction;
|
|
request->req_flags &= REQ_FLAGS_INIT_MASK;
|
|
request->req_flags |= req_active;
|
|
request->req_flags &= ~req_reserved;
|
|
request->req_operation = req::req_evaluate;
|
|
|
|
/* set up to count records affected by request */
|
|
|
|
request->req_flags |= req_count_records;
|
|
request->req_records_selected = 0;
|
|
request->req_records_updated = 0;
|
|
request->req_records_inserted = 0;
|
|
request->req_records_deleted = 0;
|
|
|
|
/* CVC: set up to count virtual operations on SQL views. */
|
|
|
|
request->req_view_flags = 0;
|
|
request->req_top_view_store = NULL;
|
|
request->req_top_view_modify = NULL;
|
|
request->req_top_view_erase = NULL;
|
|
|
|
/* Store request start time for timestamp work */
|
|
if (!request->req_timestamp)
|
|
request->req_timestamp = time(NULL);
|
|
|
|
if (request->req_invariants) {
|
|
/* Set all invariants to not computed. */
|
|
|
|
vec::iterator ptr, end;
|
|
USHORT *invariant_flags;
|
|
VLU impure;
|
|
|
|
for (ptr = request->req_invariants->begin(),
|
|
end = request->req_invariants->end(); ptr < end; ptr++)
|
|
if (*ptr) {
|
|
impure = (VLU) ((SCHAR *) request + ((JRD_NOD)(*ptr))->nod_impure);
|
|
invariant_flags = (USHORT *) & impure->vlu_string;
|
|
*invariant_flags = 0;
|
|
}
|
|
}
|
|
|
|
/* Start a save point if not in middle of one */
|
|
if (transaction && (transaction != dbb->dbb_sys_trans)) {
|
|
VIO_start_save_point(tdbb, transaction);
|
|
}
|
|
#ifdef WIN_NT
|
|
START_CHECK_FOR_EXCEPTIONS(NULL);
|
|
#endif
|
|
looper(tdbb, request, request->req_top_node);
|
|
#ifdef WIN_NT
|
|
END_CHECK_FOR_EXCEPTIONS(NULL);
|
|
#endif
|
|
|
|
/* If any requested modify/delete/insert ops have completed, forget them */
|
|
|
|
if (transaction && (transaction != dbb->dbb_sys_trans) &&
|
|
transaction->tra_save_point &&
|
|
!(transaction->tra_save_point->sav_flags & SAV_user) &&
|
|
!transaction->tra_save_point->sav_verb_count) {
|
|
/* Forget about any undo for this verb */
|
|
|
|
VIO_verb_cleanup(tdbb, transaction);
|
|
}
|
|
}
|
|
|
|
|
|
void EXE_unwind(TDBB tdbb, REQ request)
|
|
{
|
|
/**************************************
|
|
*
|
|
* E X E _ u n w i n d
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Unwind a request, maybe active, maybe not. This is particlarly
|
|
* simple since nothing really needs to be done.
|
|
*
|
|
**************************************/
|
|
vec::iterator ptr, end;
|
|
JrdMemoryPool *old_pool;
|
|
REQ old_request;
|
|
|
|
DEV_BLKCHK(request, type_req);
|
|
|
|
SET_TDBB(tdbb);
|
|
|
|
if (request->req_flags & req_active) {
|
|
if (request->req_fors) {
|
|
old_pool = tdbb->tdbb_default;
|
|
tdbb->tdbb_default = request->req_pool;
|
|
old_request = tdbb->tdbb_request;
|
|
tdbb->tdbb_request = request;
|
|
tdbb->tdbb_transaction = request->req_transaction;
|
|
for (ptr = request->req_fors->begin(), end =
|
|
request->req_fors->end(); ptr < end; ptr++)
|
|
if (*ptr)
|
|
RSE_close(tdbb, reinterpret_cast < class Rsb *>((Rsb*)(*ptr)));
|
|
tdbb->tdbb_default = old_pool;
|
|
tdbb->tdbb_request = old_request;
|
|
}
|
|
release_blobs(tdbb, request);
|
|
}
|
|
|
|
if (request->req_proc_sav_point && (request->req_flags & req_proc_fetch))
|
|
release_proc_save_points(request);
|
|
|
|
request->req_flags &= ~(req_active | req_proc_fetch | req_reserved);
|
|
request->req_flags |= req_abort | req_stall;
|
|
request->req_timestamp = 0;
|
|
|
|
}
|
|
|
|
|
|
void assign_xcp_message(TDBB tdbb, STR *xcp_msg, const TEXT *msg)
|
|
{
|
|
/**************************************
|
|
*
|
|
* a s s i g n _ x c p _ m e s s a g e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Copy an exception message into XCP structure.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
|
|
if (msg)
|
|
{
|
|
USHORT len = strlen(msg);
|
|
*xcp_msg = FB_NEW_RPT(*tdbb->tdbb_default, len + 1) str();
|
|
(*xcp_msg)->str_length = len;
|
|
memcpy((*xcp_msg)->str_data, msg, len + 1);
|
|
}
|
|
}
|
|
|
|
|
|
/* CVC: Moved to its own routine, originally in store(). */
|
|
static void cleanup_rpb(TDBB tdbb, RPB *rpb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* c l e a n u p _ r p b
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Perform cleaning of rpb, zeroing unassigned fields and
|
|
* the impure tail of varying fields that we don't want to carry
|
|
* when the RLE algorithm is applied.
|
|
*
|
|
**************************************/
|
|
DSC *desc = 0;
|
|
SSHORT n;
|
|
USHORT length;
|
|
REC record = rpb->rpb_record;
|
|
FMT format = record->rec_format;
|
|
register UCHAR *p;
|
|
|
|
SET_TDBB(tdbb); /* Is it necessary? */
|
|
|
|
/*
|
|
Starting from the format, walk through its
|
|
array of descriptors. If the descriptor has
|
|
no address, its a computed field and we shouldn't
|
|
try to fix it. Get a pointer to the actual data
|
|
and see if that field is null by indexing into
|
|
the null flags between the record header and the
|
|
record data.
|
|
*/
|
|
|
|
for (n = 0; n < format->fmt_count; n++)
|
|
{
|
|
desc = &format->fmt_desc[n];
|
|
if (!desc->dsc_address)
|
|
continue;
|
|
p = record->rec_data + (SLONG) desc->dsc_address;
|
|
if (TEST_NULL(record, n))
|
|
{
|
|
if (length = desc->dsc_length)
|
|
do *p++ = 0; while (--length);
|
|
}
|
|
else if (desc->dsc_dtype == dtype_varying)
|
|
{
|
|
VARY *vary;
|
|
|
|
vary = reinterpret_cast<VARY*>(p);
|
|
if ((length = desc->dsc_length - sizeof(USHORT)) > vary->vary_length)
|
|
{
|
|
p = reinterpret_cast<UCHAR*>(vary->vary_string + vary->vary_length);
|
|
length -= vary->vary_length;
|
|
do *p++ = 0; while (--length);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static JRD_NOD erase(TDBB tdbb, JRD_NOD node, SSHORT which_trig)
|
|
{
|
|
/**************************************
|
|
*
|
|
* e r a s e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Perform erase operation.
|
|
*
|
|
**************************************/
|
|
DBB dbb;
|
|
REQ request, trigger;
|
|
RPB *rpb;
|
|
REL relation;
|
|
REC record;
|
|
FMT format;
|
|
TRA transaction;
|
|
#ifdef PC_ENGINE
|
|
RSB rsb = NULL;
|
|
IRSB impure;
|
|
#endif
|
|
|
|
SET_TDBB(tdbb);
|
|
dbb = tdbb->tdbb_database;
|
|
BLKCHK(node, type_nod);
|
|
|
|
request = tdbb->tdbb_request;
|
|
transaction = request->req_transaction;
|
|
rpb = &request->req_rpb[(int) node->nod_arg[e_erase_stream]];
|
|
relation = rpb->rpb_relation;
|
|
|
|
#ifdef PC_ENGINE
|
|
/* for navigational streams, retrieve the rsb */
|
|
|
|
if (node->nod_arg[e_erase_rsb]) {
|
|
rsb = *(RSB *) node->nod_arg[e_erase_rsb];
|
|
impure = (IRSB) ((UCHAR *) request + rsb->rsb_impure);
|
|
}
|
|
#endif
|
|
|
|
switch (request->req_operation) {
|
|
case req::req_evaluate:
|
|
if (!node->nod_arg[e_erase_statement])
|
|
break;
|
|
format = MET_current(tdbb, rpb->rpb_relation);
|
|
record = VIO_record(tdbb, rpb, format, tdbb->tdbb_default);
|
|
rpb->rpb_address = record->rec_data;
|
|
rpb->rpb_length = format->fmt_length;
|
|
rpb->rpb_format_number = format->fmt_version;
|
|
return node->nod_arg[e_erase_statement];
|
|
|
|
case req::req_return:
|
|
break;
|
|
|
|
default:
|
|
return node->nod_parent;
|
|
}
|
|
|
|
#ifdef PC_ENGINE
|
|
/* if we are on a crack in a navigational stream, erase
|
|
is not a valid operation */
|
|
|
|
if (rsb && EXE_crack(tdbb, rsb, irsb_bof | irsb_eof | irsb_crack)) {
|
|
EXE_mark_crack(tdbb, rsb, impure->irsb_flags);
|
|
request->req_operation = req::req_return;
|
|
return node->nod_parent;
|
|
}
|
|
#endif
|
|
|
|
request->req_operation = req::req_return;
|
|
RLCK_reserve_relation(tdbb, transaction, relation, TRUE, TRUE);
|
|
|
|
/* If the stream was sorted, the various fields in the rpb are
|
|
probably junk. Just to make sure that everything is cool,
|
|
refetch and release the record. */
|
|
|
|
if (rpb->rpb_stream_flags & RPB_s_refetch) {
|
|
SLONG tid_fetch;
|
|
|
|
tid_fetch = rpb->rpb_transaction;
|
|
if ((!DPM_get(tdbb, rpb, LCK_read)) ||
|
|
(!VIO_chase_record_version(tdbb,
|
|
rpb,
|
|
NULL,
|
|
transaction,
|
|
reinterpret_cast <
|
|
blk *
|
|
>(tdbb->tdbb_default))))
|
|
ERR_post(gds_deadlock, gds_arg_gds, gds_update_conflict, 0);
|
|
VIO_data(tdbb, rpb,
|
|
reinterpret_cast < blk * >(tdbb->tdbb_request->req_pool));
|
|
|
|
/* If record is present, and the transaction is read committed,
|
|
* make sure the record has not been updated. Also, punt after
|
|
* VIO_data () call which will release the page.
|
|
*/
|
|
|
|
if ((transaction->tra_flags & TRA_read_committed) &&
|
|
(tid_fetch != rpb->rpb_transaction))
|
|
ERR_post(gds_deadlock, gds_arg_gds, gds_update_conflict, 0);
|
|
|
|
rpb->rpb_stream_flags &= ~RPB_s_refetch;
|
|
}
|
|
|
|
#ifdef PC_ENGINE
|
|
/* set up to do record locking; in case of a consistency
|
|
mode transaction, we already have an exclusive lock on
|
|
the table, so don't bother */
|
|
|
|
try {
|
|
|
|
LCK_RAII_wrapper implicit_lock;
|
|
|
|
if (!(transaction->tra_flags & TRA_degree3))
|
|
{
|
|
/* check whether record locking is turned on */
|
|
|
|
LCK record_locking = RLCK_record_locking(relation);
|
|
if (record_locking->lck_physical != LCK_PR)
|
|
{
|
|
/* get an implicit lock on the record */
|
|
|
|
implicit_lock.assign(implicit_record_lock(transaction, rpb));
|
|
|
|
/* set up to catch any errors so that we can
|
|
release the implicit lock */
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (transaction != dbb->dbb_sys_trans)
|
|
++transaction->tra_save_point->sav_verb_count;
|
|
|
|
/* Handle pre-operation trigger */
|
|
|
|
if (relation->rel_pre_erase &&
|
|
which_trig != POST_TRIG &&
|
|
(trigger =
|
|
execute_triggers(tdbb, &relation->rel_pre_erase, rpb->rpb_record,
|
|
0))) trigger_failure(tdbb, trigger);
|
|
|
|
if (relation->rel_file)
|
|
EXT_erase(rpb, reinterpret_cast < int *>(transaction));
|
|
else if (!relation->rel_view_rse)
|
|
// Repeat it as many times as underlying record modifies
|
|
while (TRUE) {
|
|
if (VIO_erase(tdbb, rpb, transaction)) break;
|
|
if ( !(transaction->tra_flags & TRA_read_committed) ||
|
|
(transaction->tra_flags & TRA_rec_version) ||
|
|
(transaction->tra_flags & TRA_nowait) )
|
|
{
|
|
ERR_post(isc_deadlock, isc_arg_gds, isc_update_conflict, 0);
|
|
}
|
|
rpb->rpb_stream_flags |= RPB_s_refetch;
|
|
}
|
|
|
|
|
|
/* Handle post operation trigger */
|
|
|
|
if (relation->rel_post_erase &&
|
|
which_trig != PRE_TRIG &&
|
|
(trigger = execute_triggers(tdbb, &relation->rel_post_erase,
|
|
rpb->rpb_record, 0)))
|
|
{
|
|
trigger_failure(tdbb, trigger);
|
|
}
|
|
|
|
/* call IDX_erase (which checks constraints) after all post erase triggers
|
|
have fired. This is required for cascading referential integrity, which
|
|
can be implemented as post_erase triggers */
|
|
|
|
if (!relation->rel_file & !relation->rel_view_rse)
|
|
{
|
|
REL bad_relation;
|
|
USHORT bad_index;
|
|
|
|
IDX_E error_code =
|
|
IDX_erase(tdbb, rpb, transaction, &bad_relation, &bad_index);
|
|
|
|
if (error_code) {
|
|
ERR_duplicate_error(error_code, bad_relation, bad_index);
|
|
}
|
|
}
|
|
|
|
/* CVC: Increment the counter only if we called VIO/EXT_erase() and
|
|
we were successful. */
|
|
if (!(request->req_view_flags & req_first_erase_return)) {
|
|
request->req_view_flags |= req_first_erase_return;
|
|
if (relation->rel_view_rse) {
|
|
request->req_top_view_erase = relation;
|
|
}
|
|
}
|
|
if (relation == request->req_top_view_erase) {
|
|
if (which_trig == ALL_TRIGS || which_trig == POST_TRIG) {
|
|
request->req_records_deleted++;
|
|
request->req_records_affected++;
|
|
}
|
|
}
|
|
else if (relation->rel_file || !relation->rel_view_rse) {
|
|
request->req_records_deleted++;
|
|
request->req_records_affected++;
|
|
}
|
|
|
|
if (transaction != dbb->dbb_sys_trans) {
|
|
--transaction->tra_save_point->sav_verb_count;
|
|
}
|
|
|
|
#ifdef PC_ENGINE
|
|
|
|
} // try
|
|
catch (...) {
|
|
Firebird::status_exception::raise(-1);
|
|
}
|
|
|
|
/* if the stream is navigational, it is now positioned on a crack */
|
|
|
|
if (rsb) {
|
|
RSE_MARK_CRACK(tdbb, rsb, irsb_crack);
|
|
}
|
|
#endif
|
|
|
|
return node->nod_parent;
|
|
}
|
|
|
|
|
|
static void execute_looper(
|
|
TDBB tdbb,
|
|
REQ request,
|
|
TRA transaction, ENUM req::req_s next_state)
|
|
{
|
|
/**************************************
|
|
*
|
|
* e x e c u t e _ l o o p e r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Wrapper around looper. This will execute
|
|
* looper with the save point mechanism.
|
|
*
|
|
**************************************/
|
|
DBB dbb;
|
|
|
|
DEV_BLKCHK(request, type_req);
|
|
|
|
SET_TDBB(tdbb);
|
|
dbb = tdbb->tdbb_database;
|
|
|
|
/* Start a save point */
|
|
|
|
if (!(request->req_flags & req_proc_fetch) && request->req_transaction)
|
|
if (transaction && (transaction != dbb->dbb_sys_trans))
|
|
VIO_start_save_point(tdbb, transaction);
|
|
|
|
request->req_flags &= ~req_stall;
|
|
request->req_operation = next_state;
|
|
|
|
looper(tdbb, request, request->req_next);
|
|
|
|
/* If any requested modify/delete/insert ops have completed, forget them */
|
|
|
|
if (!(request->req_flags & req_proc_fetch) && request->req_transaction)
|
|
if (transaction && (transaction != dbb->dbb_sys_trans) &&
|
|
transaction->tra_save_point &&
|
|
!transaction->tra_save_point->sav_verb_count) {
|
|
/* Forget about any undo for this verb */
|
|
|
|
VIO_verb_cleanup(tdbb, transaction);
|
|
}
|
|
}
|
|
|
|
|
|
static void exec_sql(TDBB tdbb, REQ request, DSC* dsc)
|
|
{
|
|
/**************************************
|
|
*
|
|
* e x e c _ s q l
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Execute a string as SQL operator.
|
|
*
|
|
**************************************/
|
|
|
|
extern STATUS DLL_EXPORT callback_execute_immediate(
|
|
STATUS *status,
|
|
int* jrd_attachment_handle,
|
|
int* jrd_transaction_handle,
|
|
UCHAR *sql_operator,
|
|
int len);
|
|
|
|
UCHAR *p;
|
|
vary *v = reinterpret_cast <vary*> (
|
|
FB_NEW(*tdbb->tdbb_transaction->tra_pool) char[BUFFER_LARGE + sizeof(vary)]);
|
|
v->vary_length = BUFFER_LARGE;
|
|
SSHORT l;
|
|
STATUS *status, local[ISC_STATUS_LENGTH];
|
|
static char *cba = "Callback Argument";
|
|
|
|
memset(local, 0, sizeof(local));
|
|
status = local;
|
|
|
|
SET_TDBB(tdbb);
|
|
p = 0;
|
|
l = MOV_get_string(dsc, &p, v, BUFFER_LARGE); // !!! How call Msgs ?
|
|
if (p) {
|
|
if (tdbb->tdbb_transaction->tra_callback_count >= MAX_CALLBACKS) {
|
|
status[0] = gds_arg_gds;
|
|
status[1] = gds_req_max_clones_exceeded;
|
|
status[2] = gds_arg_end;
|
|
}
|
|
else {
|
|
tdbb->tdbb_transaction->tra_callback_count++;
|
|
callback_execute_immediate(status,
|
|
reinterpret_cast <int*> (tdbb->tdbb_attachment),
|
|
reinterpret_cast <int*> (tdbb->tdbb_transaction),
|
|
p, l);
|
|
tdbb->tdbb_transaction->tra_callback_count--;
|
|
}
|
|
}
|
|
else {
|
|
status[0] = gds_arg_gds;
|
|
status[1] = gds_convert_error;
|
|
status[2] = gds_arg_string;
|
|
status[3] = reinterpret_cast <long> (cba);
|
|
status[4] = gds_arg_end;
|
|
}
|
|
|
|
if (status[1]) {
|
|
memcpy(tdbb->tdbb_status_vector, status, sizeof(local));
|
|
ERR_punt();
|
|
}
|
|
|
|
delete v;
|
|
}
|
|
|
|
|
|
static void execute_procedure(TDBB tdbb, JRD_NOD node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* e x e c u t e _ p r o c e d u r e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Execute a stored procedure. Begin by
|
|
* assigning the input parameters. End
|
|
* by assigning the output parameters.
|
|
*
|
|
**************************************/
|
|
REQ request, proc_request;
|
|
JRD_NOD in_message, out_message, temp;
|
|
FMT format;
|
|
USHORT in_msg_length, out_msg_length;
|
|
SCHAR *in_msg, *out_msg;
|
|
PRC procedure;
|
|
STR temp_buffer = NULL;
|
|
SLONG save_point_number;
|
|
TRA transaction;
|
|
JrdMemoryPool *old_pool;
|
|
|
|
SET_TDBB(tdbb);
|
|
BLKCHK(node, type_nod);
|
|
|
|
request = tdbb->tdbb_request;
|
|
|
|
if ( (temp = node->nod_arg[e_esp_inputs]) ) {
|
|
JRD_NOD *ptr, *end;
|
|
|
|
for (ptr = temp->nod_arg, end = ptr + temp->nod_count; ptr < end;
|
|
ptr++)
|
|
EXE_assignment(tdbb, *ptr);
|
|
}
|
|
|
|
if ( (in_message = node->nod_arg[e_esp_in_msg]) ) {
|
|
format = (FMT) in_message->nod_arg[e_msg_format];
|
|
in_msg_length = format->fmt_length;
|
|
in_msg = (SCHAR *) request + in_message->nod_impure;
|
|
}
|
|
if ( (out_message = node->nod_arg[e_esp_out_msg]) ) {
|
|
format = (FMT) out_message->nod_arg[e_msg_format];
|
|
out_msg_length = format->fmt_length;
|
|
out_msg = (SCHAR *) request + out_message->nod_impure;
|
|
}
|
|
|
|
procedure = (PRC) node->nod_arg[e_esp_procedure];
|
|
proc_request = EXE_find_request(tdbb, procedure->prc_request, FALSE);
|
|
|
|
if (!out_message) {
|
|
format = (FMT) procedure->prc_output_msg->nod_arg[e_msg_format];
|
|
out_msg_length = format->fmt_length;
|
|
temp_buffer =
|
|
FB_NEW_RPT(*tdbb->tdbb_default, out_msg_length + DOUBLE_ALIGN - 1) str();
|
|
out_msg =
|
|
(SCHAR *) FB_ALIGN((U_IPTR) temp_buffer->str_data, DOUBLE_ALIGN);
|
|
}
|
|
|
|
/* Save the old pool */
|
|
|
|
old_pool = tdbb->tdbb_default;
|
|
tdbb->tdbb_default = proc_request->req_pool;
|
|
|
|
/* Catch errors so we can unwind cleanly */
|
|
|
|
try {
|
|
|
|
transaction = request->req_transaction;
|
|
save_point_number = transaction->tra_save_point->sav_number;
|
|
|
|
proc_request->req_timestamp = request->req_timestamp;
|
|
EXE_start(tdbb, proc_request, transaction);
|
|
if (in_message)
|
|
EXE_send(tdbb, proc_request, 0, in_msg_length,
|
|
reinterpret_cast < UCHAR * >(in_msg));
|
|
|
|
EXE_receive(tdbb, proc_request, 1, out_msg_length,
|
|
reinterpret_cast < UCHAR * >(out_msg));
|
|
|
|
/* Clean up all savepoints started during execution of the
|
|
procedure */
|
|
|
|
if (transaction != tdbb->tdbb_database->dbb_sys_trans) {
|
|
SAV save_point;
|
|
|
|
for (save_point = transaction->tra_save_point;
|
|
save_point && save_point_number < save_point->sav_number;
|
|
save_point = transaction->tra_save_point)
|
|
VIO_verb_cleanup(tdbb, transaction);
|
|
}
|
|
|
|
} // try
|
|
catch (...) {
|
|
tdbb->tdbb_default = old_pool;
|
|
tdbb->tdbb_request = request;
|
|
EXE_unwind(tdbb, proc_request);
|
|
proc_request->req_flags &= ~(req_in_use | req_proc_fetch);
|
|
proc_request->req_timestamp = 0;
|
|
delete temp_buffer;
|
|
Firebird::status_exception::raise(-1);
|
|
}
|
|
|
|
tdbb->tdbb_default = old_pool;
|
|
EXE_unwind(tdbb, proc_request);
|
|
tdbb->tdbb_request = request;
|
|
|
|
temp = node->nod_arg[e_esp_outputs];
|
|
if (temp) {
|
|
JRD_NOD *ptr, *end;
|
|
|
|
for (ptr = temp->nod_arg, end = ptr + temp->nod_count; ptr < end;
|
|
ptr++)
|
|
EXE_assignment(tdbb, *ptr);
|
|
}
|
|
|
|
delete temp_buffer;
|
|
proc_request->req_attachment = NULL;
|
|
proc_request->req_flags &= ~(req_in_use | req_proc_fetch);
|
|
proc_request->req_timestamp = 0;
|
|
}
|
|
|
|
|
|
static REQ execute_triggers(TDBB tdbb,
|
|
TRIG_VEC* triggers,
|
|
REC old_rec,
|
|
REC new_rec)
|
|
{
|
|
/**************************************
|
|
*
|
|
* e x e c u t e _ t r i g g e r s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Execute group of triggers. Return pointer to failing trigger
|
|
* if any blow up.
|
|
*
|
|
**************************************/
|
|
|
|
volatile REQ trigger = NULL;
|
|
|
|
//DEV_BLKCHK(*triggers, type_vec);
|
|
DEV_BLKCHK(old_rec, type_rec);
|
|
DEV_BLKCHK(new_rec, type_rec);
|
|
|
|
if (!*triggers) {
|
|
return NULL;
|
|
}
|
|
|
|
SET_TDBB(tdbb);
|
|
|
|
TRA transaction = tdbb->tdbb_request->req_transaction;
|
|
TRIG_VEC vector = *triggers;
|
|
REQ result = NULL;
|
|
|
|
try
|
|
{
|
|
for (trig_vec::iterator ptr = vector->begin(); ptr != vector->end(); ++ptr)
|
|
{
|
|
ptr->compile(tdbb);
|
|
trigger = EXE_find_request(tdbb, ptr->request, FALSE);
|
|
trigger->req_rpb[0].rpb_record = old_rec;
|
|
trigger->req_rpb[1].rpb_record = new_rec;
|
|
trigger->req_timestamp = tdbb->tdbb_request->req_timestamp;
|
|
EXE_start(tdbb, trigger, transaction);
|
|
trigger->req_attachment = NULL;
|
|
trigger->req_flags &= ~req_in_use;
|
|
trigger->req_timestamp = 0;
|
|
if (trigger->req_operation == req::req_unwind) {
|
|
result = trigger;
|
|
break;
|
|
}
|
|
trigger = (REQ)NULL_PTR;
|
|
}
|
|
|
|
if (vector != *triggers) {
|
|
MET_release_triggers(tdbb, &vector);
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
catch (std::exception&)
|
|
{
|
|
if (vector != *triggers) {
|
|
MET_release_triggers(tdbb, &vector);
|
|
}
|
|
if (!trigger) {
|
|
throw; // trigger probally fails to compile
|
|
//Firebird::status_exception::raise(-1);
|
|
}
|
|
return trigger;
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef PC_ENGINE
|
|
static JRD_NOD find(TDBB tdbb, register JRD_NOD node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f i n d
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Find the given key value in a stream.
|
|
* Assume that the stream is open.
|
|
*
|
|
**************************************/
|
|
|
|
SET_TDBB(tdbb);
|
|
REQ request = tdbb->tdbb_request;
|
|
BLKCHK(node, type_nod);
|
|
|
|
if (request->req_operation == req::req_evaluate)
|
|
{
|
|
RSB rsb = *((RSB *) node->nod_arg[e_find_rsb]);
|
|
|
|
USHORT operator_ =
|
|
(USHORT) MOV_get_long(EVL_expr( tdbb,
|
|
node->nod_arg
|
|
[e_find_operator]),
|
|
0);
|
|
if (operator_ != blr_eql &&
|
|
operator_ != blr_leq &&
|
|
operator_ != blr_lss &&
|
|
operator_ != blr_geq &&
|
|
operator_ != blr_gtr)
|
|
{
|
|
ERR_post(gds_invalid_operator, 0);
|
|
}
|
|
|
|
USHORT direction = (USHORT) MOV_get_long(EVL_expr(tdbb,
|
|
node->nod_arg
|
|
[e_find_direction]),
|
|
0);
|
|
if (direction != blr_backward &&
|
|
direction != blr_forward &&
|
|
direction != blr_backward_starting &&
|
|
direction != blr_forward_starting)
|
|
{
|
|
ERR_post(gds_invalid_direction, 0);
|
|
}
|
|
|
|
/* try to find the record; the position is defined to be on a crack
|
|
regardless of whether we are at BOF or EOF; also be sure to perpetuate
|
|
the forced crack (bug #7024) */
|
|
|
|
if (!RSE_find_record( tdbb,
|
|
rsb,
|
|
operator_,
|
|
direction,
|
|
node->nod_arg[e_find_args]))
|
|
{
|
|
if (EXE_crack(tdbb, rsb, irsb_bof | irsb_eof | irsb_crack))
|
|
{
|
|
if (EXE_crack(tdbb, rsb, irsb_forced_crack)) {
|
|
EXE_mark_crack(tdbb, rsb, irsb_crack | irsb_forced_crack);
|
|
} else if (EXE_crack(tdbb, rsb, irsb_bof)) {
|
|
EXE_mark_crack(tdbb, rsb, irsb_bof);
|
|
} else if (EXE_crack(tdbb, rsb, irsb_eof)) {
|
|
EXE_mark_crack(tdbb, rsb, irsb_eof);
|
|
} else {
|
|
EXE_mark_crack(tdbb, rsb, irsb_crack);
|
|
}
|
|
}
|
|
}
|
|
request->req_operation = req::req_return;
|
|
}
|
|
|
|
return node->nod_parent;
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef PC_ENGINE
|
|
static JRD_NOD find_dbkey(TDBB tdbb, register JRD_NOD node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f i n d _ d b k e y
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Find the given dbkey in a navigational stream,
|
|
* resetting the position of the stream to that record.
|
|
*
|
|
**************************************/
|
|
|
|
SET_TDBB(tdbb);
|
|
REQ request = tdbb->tdbb_request;
|
|
BLKCHK(node, type_nod);
|
|
|
|
if (request->req_operation == req::req_evaluate)
|
|
{
|
|
RSB rsb = *((RSB *) node->nod_arg[e_find_dbkey_rsb]);
|
|
|
|
if (!RSE_find_dbkey(tdbb,
|
|
rsb,
|
|
node->nod_arg[e_find_dbkey_dbkey],
|
|
node->nod_arg[e_find_dbkey_version]))
|
|
{
|
|
EXE_mark_crack(tdbb, rsb, irsb_crack);
|
|
}
|
|
|
|
request->req_operation = req::req_return;
|
|
}
|
|
|
|
return node->nod_parent;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef PC_ENGINE
|
|
static LCK implicit_record_lock(TRA transaction, RPB * rpb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* i m p l i c i t _ r e c o r d _ l o c k
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* An update to a record is being attempted and
|
|
* record locking has been initiated. Take out
|
|
* an implicit record lock to prevent updating
|
|
* a record that someone has explicitly locked.
|
|
*
|
|
**************************************/
|
|
|
|
TDBB tdbb = GET_THREAD_DATA;
|
|
|
|
DEV_BLKCHK(transaction, type_tra);
|
|
|
|
REL relation = rpb->rpb_relation;
|
|
LCK record_locking = relation->rel_record_locking;
|
|
|
|
/* occasionally we should check whether we really still need to
|
|
do record locking; this is defined as RECORD_LOCK_CHECK_INTERVAL--
|
|
if we can get a PR on the record locking lock there is no need
|
|
to do implicit locking anymore */
|
|
|
|
if ((record_locking->lck_physical == LCK_none) &&
|
|
!(relation->rel_lock_total % RECORD_LOCK_CHECK_INTERVAL) &&
|
|
LCK_lock_non_blocking(tdbb, record_locking, LCK_PR, FALSE))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
LCK lock = RLCK_lock_record_implicit(transaction, rpb, LCK_SW, 0, 0);
|
|
if (!lock) {
|
|
ERR_post(gds_record_lock, 0);
|
|
}
|
|
|
|
return lock;
|
|
}
|
|
#endif
|
|
|
|
|
|
static JRD_NOD looper(TDBB tdbb, REQ request, JRD_NOD in_node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* l o o p e r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Cycle thru request execution tree. Return next node for
|
|
* execution on stall or request complete.
|
|
*
|
|
**************************************/
|
|
|
|
STA impure;
|
|
SLONG sav_number;
|
|
SAV savepoint;
|
|
SSHORT which_erase_trig = 0;
|
|
SSHORT which_sto_trig = 0;
|
|
SSHORT which_mod_trig = 0;
|
|
volatile JRD_NOD top_node = 0;
|
|
volatile JRD_NOD prev_node;
|
|
TRA transaction;
|
|
|
|
/* If an error happens during the backout of a savepoint, then the transaction
|
|
must be marked 'dead' because that is the only way to clean up after a
|
|
failed backout. The easiest way to do this is to kill the application
|
|
by calling bugcheck.
|
|
To facilitate catching errors during VIO_verb_cleanup, the following
|
|
define is used. */
|
|
#define VERB_CLEANUP \
|
|
try { \
|
|
VIO_verb_cleanup (tdbb, transaction); \
|
|
} \
|
|
catch (std::exception&) { \
|
|
if (dbb->dbb_flags & DBB_bugcheck) { \
|
|
Firebird::status_exception::raise(tdbb->tdbb_status_vector[1]); \
|
|
} \
|
|
BUGCHECK (290); /* msg 290 error during savepoint backout */ \
|
|
}
|
|
|
|
if (!(transaction = request->req_transaction)) {
|
|
ERR_post(gds_req_no_trans, 0);
|
|
}
|
|
|
|
SET_TDBB(tdbb);
|
|
DBB dbb = tdbb->tdbb_database;
|
|
BLKCHK(in_node, type_nod);
|
|
|
|
// Save the old pool and request to restore on exit
|
|
|
|
JrdMemoryPool* old_pool = tdbb->tdbb_default;
|
|
tdbb->tdbb_default = request->req_pool;
|
|
|
|
REQ old_request = tdbb->tdbb_request;
|
|
tdbb->tdbb_request = request;
|
|
tdbb->tdbb_transaction = transaction;
|
|
|
|
SLONG save_point_number = (transaction->tra_save_point) ?
|
|
transaction->tra_save_point->sav_number : 0;
|
|
|
|
volatile JRD_NOD node = in_node;
|
|
|
|
// Catch errors so we can unwind cleanly
|
|
|
|
SSHORT error_pending = FALSE;
|
|
|
|
// Execute stuff until we drop
|
|
|
|
request->req_records_affected = 0;
|
|
|
|
while (node && !(request->req_flags & req_stall))
|
|
{
|
|
try {
|
|
|
|
#ifdef MULTI_THREAD
|
|
|
|
if (request->req_operation == req::req_evaluate &&
|
|
(--tdbb->tdbb_quantum < 0) && !tdbb->tdbb_inhibit)
|
|
(void) JRD_reschedule(tdbb, 0, TRUE);
|
|
|
|
#endif
|
|
#if defined(DEBUG_GDS_ALLOC) && FALSE
|
|
int node_type = node->nod_type;
|
|
#endif
|
|
|
|
switch (node->nod_type) {
|
|
case nod_asn_list:
|
|
if (request->req_operation == req::req_evaluate) {
|
|
volatile JRD_NOD *ptr, *end;
|
|
|
|
for (ptr = node->nod_arg, end = ptr + node->nod_count;
|
|
ptr < end; ptr++)
|
|
EXE_assignment(tdbb, *ptr);
|
|
request->req_operation = req::req_return;
|
|
}
|
|
node = node->nod_parent;
|
|
break;
|
|
|
|
case nod_assignment:
|
|
if (request->req_operation == req::req_evaluate)
|
|
EXE_assignment(tdbb, node);
|
|
node = node->nod_parent;
|
|
break;
|
|
|
|
case nod_dcl_variable:
|
|
{
|
|
VLU variable;
|
|
|
|
variable = (VLU) ((SCHAR *) request + node->nod_impure);
|
|
variable->vlu_desc = *(DSC *) (node->nod_arg + e_dcl_desc);
|
|
variable->vlu_desc.dsc_flags = 0;
|
|
variable->vlu_desc.dsc_address =
|
|
(UCHAR *) & variable->vlu_misc;
|
|
if (variable->vlu_desc.dsc_dtype <= dtype_varying
|
|
&& !variable->vlu_string) {
|
|
variable->vlu_string =
|
|
FB_NEW_RPT(*tdbb->tdbb_default,
|
|
variable->vlu_desc.dsc_length) str();
|
|
variable->vlu_string->str_length =
|
|
variable->vlu_desc.dsc_length;
|
|
variable->vlu_desc.dsc_address =
|
|
variable->vlu_string->str_data;
|
|
}
|
|
request->req_operation = req::req_return;
|
|
node = node->nod_parent;
|
|
}
|
|
break;
|
|
|
|
case nod_erase:
|
|
if ((request->req_operation == req::req_return) &&
|
|
(node->nod_arg[e_erase_sub_erase]))
|
|
{
|
|
if (!top_node) {
|
|
top_node = node;
|
|
which_erase_trig = PRE_TRIG;
|
|
}
|
|
prev_node = node;
|
|
node = erase(tdbb, node, which_erase_trig);
|
|
if (which_erase_trig == PRE_TRIG) {
|
|
node = prev_node->nod_arg[e_erase_sub_erase];
|
|
node->nod_parent = prev_node;
|
|
}
|
|
if (top_node == prev_node && which_erase_trig == POST_TRIG) {
|
|
top_node = NULL;
|
|
which_erase_trig = ALL_TRIGS;
|
|
}
|
|
else
|
|
request->req_operation = req::req_evaluate;
|
|
}
|
|
else {
|
|
prev_node = node;
|
|
node = erase(tdbb, node, ALL_TRIGS);
|
|
if (!(prev_node->nod_arg[e_erase_sub_erase]) &&
|
|
which_erase_trig == PRE_TRIG)
|
|
{
|
|
which_erase_trig = POST_TRIG;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case nod_writelock:
|
|
writelock(tdbb, node);
|
|
node = node->nod_parent;
|
|
request->req_operation = req::req_return;
|
|
break;
|
|
|
|
case nod_exec_proc:
|
|
if (request->req_operation == req::req_unwind) {
|
|
node = node->nod_parent;
|
|
break;
|
|
}
|
|
execute_procedure(tdbb, node);
|
|
node = node->nod_parent;
|
|
request->req_operation = req::req_return;
|
|
break;
|
|
|
|
case nod_for:
|
|
switch (request->req_operation) {
|
|
case req::req_evaluate:
|
|
RSE_open(tdbb, (RSB) node->nod_arg[e_for_rsb]);
|
|
case req::req_return:
|
|
if (node->nod_arg[e_for_stall]) {
|
|
node = node->nod_arg[e_for_stall];
|
|
break;
|
|
}
|
|
case req::req_sync:
|
|
if (RSE_get_record(tdbb, (RSB) node->nod_arg[e_for_rsb],
|
|
#ifdef SCROLLABLE_CURSORS
|
|
RSE_get_next))
|
|
#else
|
|
RSE_get_forward))
|
|
#endif
|
|
{
|
|
node = node->nod_arg[e_for_statement];
|
|
request->req_operation = req::req_evaluate;
|
|
break;
|
|
}
|
|
request->req_operation = req::req_return;
|
|
default:
|
|
RSE_close(tdbb, (RSB) node->nod_arg[e_for_rsb]);
|
|
node = node->nod_parent;
|
|
}
|
|
break;
|
|
|
|
case nod_abort:
|
|
switch (request->req_operation) {
|
|
case req::req_evaluate:
|
|
{
|
|
XCP xcp_node = reinterpret_cast<XCP>(node->nod_arg[0]);
|
|
if (xcp_node)
|
|
{
|
|
/* XCP is defined,
|
|
so throw an exception */
|
|
set_error(tdbb, xcp_node, node->nod_arg[1]);
|
|
}
|
|
else if (request->req_last_xcp.xcp_type != 0)
|
|
{
|
|
/* XCP is undefined, but there was a known exception before,
|
|
so re-initiate it */
|
|
struct xcp last_error;
|
|
last_error.xcp_count = 1;
|
|
last_error.xcp_rpt[0].xcp_type = request->req_last_xcp.xcp_type;
|
|
last_error.xcp_rpt[0].xcp_code = request->req_last_xcp.xcp_code;
|
|
last_error.xcp_rpt[0].xcp_msg = request->req_last_xcp.xcp_msg;
|
|
request->req_last_xcp.xcp_type = 0;
|
|
request->req_last_xcp.xcp_msg = 0;
|
|
set_error(tdbb, &last_error, node->nod_arg[1]);
|
|
}
|
|
else
|
|
{
|
|
/* XCP is undefined and there weren't any exceptions before,
|
|
so just do nothing */
|
|
request->req_operation = req::req_return;
|
|
}
|
|
}
|
|
|
|
default:
|
|
node = node->nod_parent;
|
|
}
|
|
break;
|
|
|
|
case nod_user_savepoint:
|
|
if (transaction != dbb->dbb_sys_trans) {
|
|
// Use the savepoint created by EXE_start
|
|
transaction->tra_save_point->sav_flags |= SAV_user;
|
|
strcpy(transaction->tra_save_point->sav_name, (TEXT*)node->nod_arg[e_sav_name]);
|
|
}
|
|
node = node->nod_parent;
|
|
request->req_operation = req::req_return;
|
|
break;
|
|
|
|
case nod_undo_savepoint:
|
|
|
|
if (transaction != dbb->dbb_sys_trans) {
|
|
// Skip the savepoint created by EXE_start
|
|
savepoint = transaction->tra_save_point->sav_next;
|
|
|
|
// Find savepoint to undo
|
|
while(TRUE) {
|
|
if (!savepoint || !(savepoint->sav_flags & SAV_user))
|
|
ERR_post(gds_invalid_savepoint,
|
|
gds_arg_number, (SLONG) node->nod_arg[e_sav_name], 0);
|
|
|
|
if (!strcmp((TEXT*)node->nod_arg[e_sav_name],(TEXT*)savepoint->sav_name))
|
|
break;
|
|
|
|
savepoint = savepoint->sav_next;
|
|
}
|
|
sav_number = savepoint->sav_number;
|
|
|
|
// Actually undo the savepoint
|
|
while ( transaction->tra_save_point &&
|
|
transaction->tra_save_point->sav_number >= sav_number )
|
|
{
|
|
transaction->tra_save_point->sav_verb_count++;
|
|
VERB_CLEANUP;
|
|
}
|
|
|
|
// Now set the savepoint again to allow to return to it later
|
|
VIO_start_save_point(tdbb, transaction);
|
|
transaction->tra_save_point->sav_flags |= SAV_user;
|
|
strcpy(transaction->tra_save_point->sav_name, (TEXT*)node->nod_arg[e_sav_name]);
|
|
}
|
|
node = node->nod_parent;
|
|
request->req_operation = req::req_return;
|
|
break;
|
|
|
|
case nod_start_savepoint:
|
|
switch (request->req_operation) {
|
|
case req::req_evaluate:
|
|
/* Start a save point */
|
|
|
|
if (transaction != dbb->dbb_sys_trans)
|
|
VIO_start_save_point(tdbb, transaction);
|
|
|
|
default:
|
|
node = node->nod_parent;
|
|
request->req_operation = req::req_return;
|
|
}
|
|
break;
|
|
|
|
case nod_end_savepoint:
|
|
switch (request->req_operation) {
|
|
case req::req_evaluate:
|
|
case req::req_unwind:
|
|
/* If any requested modify/delete/insert
|
|
ops have completed, forget them */
|
|
if (transaction != dbb->dbb_sys_trans) {
|
|
/* If an error is still pending when the savepoint is
|
|
supposed to end, then the application didn't handle the
|
|
error and the savepoint should be undone. */
|
|
if (error_pending) {
|
|
++transaction->tra_save_point->sav_verb_count;
|
|
}
|
|
VERB_CLEANUP;
|
|
}
|
|
|
|
default:
|
|
node = node->nod_parent;
|
|
request->req_operation = req::req_return;
|
|
}
|
|
break;
|
|
|
|
case nod_handler:
|
|
switch (request->req_operation) {
|
|
case req::req_evaluate:
|
|
node = node->nod_arg[0];
|
|
break;
|
|
|
|
case req::req_unwind:
|
|
if (!request->req_label)
|
|
request->req_operation = req::req_return;
|
|
|
|
default:
|
|
node = node->nod_parent;
|
|
}
|
|
break;
|
|
|
|
case nod_block:
|
|
switch (request->req_operation) {
|
|
SLONG count;
|
|
SAV save_point;
|
|
|
|
case req::req_evaluate:
|
|
if (transaction != dbb->dbb_sys_trans) {
|
|
VIO_start_save_point(tdbb, transaction);
|
|
save_point = transaction->tra_save_point;
|
|
count = save_point->sav_number;
|
|
MOVE_FAST(&count,
|
|
(SCHAR *) request + node->nod_impure,
|
|
sizeof(SLONG));
|
|
}
|
|
node = node->nod_arg[e_blk_action];
|
|
break;
|
|
|
|
case req::req_unwind:
|
|
{
|
|
volatile JRD_NOD *ptr, *end;
|
|
|
|
if (request->req_flags & req_leave)
|
|
{
|
|
/** Although the req_operation is set to req_unwind
|
|
it is not an error case if req_leave bit is set.
|
|
req_leave bit indicates that we hit an EXIT
|
|
statement in the procedure code.
|
|
Do not perform the error handling stuff.
|
|
**/
|
|
node = node->nod_parent;
|
|
break;
|
|
}
|
|
if (transaction != dbb->dbb_sys_trans)
|
|
{
|
|
MOVE_FAST((SCHAR *) request + node->nod_impure,
|
|
&count, sizeof(SLONG));
|
|
/* Since there occurred an error (req_unwind), undo all savepoints
|
|
up to, but not including, the savepoint of this block. The
|
|
savepoint of this block will be dealt with below. */
|
|
for (save_point = transaction->tra_save_point;
|
|
save_point && count < save_point->sav_number;
|
|
save_point = transaction->tra_save_point) {
|
|
++transaction->tra_save_point->sav_verb_count;
|
|
VERB_CLEANUP;
|
|
}
|
|
}
|
|
|
|
volatile JRD_NOD handlers = node->nod_arg[e_blk_handlers];
|
|
if (handlers)
|
|
{
|
|
ULONG prev_req_error_handler;
|
|
|
|
node = node->nod_parent;
|
|
for (ptr = handlers->nod_arg,
|
|
end = ptr + handlers->nod_count; ptr < end;
|
|
ptr++)
|
|
{
|
|
const XCP xcp_node =
|
|
reinterpret_cast<XCP>((*ptr)->nod_arg[e_err_conditions]);
|
|
if (test_and_fixup_error(tdbb, xcp_node, request))
|
|
{
|
|
request->req_operation = req::req_evaluate;
|
|
node = (*ptr)->nod_arg[e_err_action];
|
|
error_pending = FALSE;
|
|
|
|
/* On entering looper old_request etc. are saved.
|
|
On recursive calling we will loose the actual old
|
|
request for that invocation of looper. Avoid this. */
|
|
|
|
tdbb->tdbb_default = old_pool;
|
|
tdbb->tdbb_request = old_request;
|
|
|
|
/* Save the previous state of req_error_handler
|
|
bit. We need to restore it later. This is
|
|
necessary if the error handler is deeply
|
|
nested. */
|
|
|
|
prev_req_error_handler =
|
|
request->req_flags & req_error_handler;
|
|
request->req_flags |= req_error_handler;
|
|
node = looper(tdbb, request, node);
|
|
request->req_flags &= ~(req_error_handler);
|
|
request->req_flags |= prev_req_error_handler;
|
|
|
|
/* Note: Previously the above call
|
|
"node = looper (tdbb, request, node);"
|
|
never returned back till the node tree
|
|
was executed completely. Now that the looper
|
|
has changed its behaviour such that it
|
|
returns back after handling error. This
|
|
makes it necessary that the jmpbuf be reset
|
|
so that looper can proceede with the
|
|
preocessing of execution tree. If this is
|
|
not done then anymore errors will take the
|
|
engine out of looper there by abruptly
|
|
terminating the processing. */
|
|
|
|
tdbb->tdbb_default = request->req_pool;
|
|
tdbb->tdbb_request = request;
|
|
|
|
/* The error is dealt with by the application, cleanup
|
|
this block's savepoint. */
|
|
|
|
if (transaction != dbb->dbb_sys_trans)
|
|
{
|
|
for (save_point = transaction->tra_save_point;
|
|
save_point &&
|
|
count <= save_point->sav_number;
|
|
save_point = transaction->tra_save_point)
|
|
{
|
|
VERB_CLEANUP;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
node = node->nod_parent;
|
|
}
|
|
|
|
/* If the application didn't have an error handler, then
|
|
the error will still be pending. Undo the block by
|
|
using its savepoint. */
|
|
|
|
if (error_pending && transaction != dbb->dbb_sys_trans) {
|
|
++transaction->tra_save_point->sav_verb_count;
|
|
VERB_CLEANUP;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case req::req_return:
|
|
if (transaction != dbb->dbb_sys_trans) {
|
|
MOVE_FAST((SCHAR *) request + node->nod_impure,
|
|
&count, sizeof(SLONG));
|
|
for (save_point = transaction->tra_save_point;
|
|
save_point && count <= save_point->sav_number;
|
|
save_point = transaction->tra_save_point)
|
|
VERB_CLEANUP;
|
|
}
|
|
default:
|
|
node = node->nod_parent;
|
|
}
|
|
break;
|
|
|
|
case nod_error_handler:
|
|
if (request->req_flags & req_error_handler && !error_pending) {
|
|
return node;
|
|
}
|
|
node = node->nod_parent;
|
|
node = node->nod_parent;
|
|
if (request->req_operation == req::req_unwind)
|
|
node = node->nod_parent;
|
|
request->req_last_xcp.xcp_type = 0;
|
|
break;
|
|
|
|
case nod_label:
|
|
switch (request->req_operation) {
|
|
case req::req_evaluate:
|
|
node = node->nod_arg[e_lbl_statement];
|
|
break;
|
|
|
|
case req::req_unwind:
|
|
if ((request->req_label == (USHORT) node->nod_arg[e_lbl_label]) &&
|
|
(request->req_flags & req_leave)) {
|
|
request->req_flags &= ~req_leave;
|
|
request->req_operation = req::req_return;
|
|
}
|
|
|
|
default:
|
|
node = node->nod_parent;
|
|
}
|
|
break;
|
|
|
|
case nod_leave:
|
|
request->req_flags |= req_leave;
|
|
request->req_operation = req::req_unwind;
|
|
request->req_label = (USHORT) node->nod_arg[0];
|
|
node = node->nod_parent;
|
|
break;
|
|
|
|
case nod_list:
|
|
impure = (STA) ((SCHAR *) request + node->nod_impure);
|
|
switch (request->req_operation) {
|
|
case req::req_evaluate:
|
|
impure->sta_state = 0;
|
|
case req::req_return:
|
|
case req::req_sync:
|
|
if (impure->sta_state < node->nod_count) {
|
|
request->req_operation = req::req_evaluate;
|
|
node = node->nod_arg[impure->sta_state++];
|
|
break;
|
|
}
|
|
request->req_operation = req::req_return;
|
|
default:
|
|
node = node->nod_parent;
|
|
}
|
|
break;
|
|
|
|
case nod_loop:
|
|
switch (request->req_operation) {
|
|
case req::req_evaluate:
|
|
case req::req_return:
|
|
node = node->nod_arg[0];
|
|
request->req_operation = req::req_evaluate;
|
|
break;
|
|
|
|
default:
|
|
node = node->nod_parent;
|
|
}
|
|
break;
|
|
|
|
case nod_if:
|
|
if (request->req_operation == req::req_evaluate)
|
|
if (EVL_boolean(tdbb, node->nod_arg[e_if_boolean])) {
|
|
node = node->nod_arg[e_if_true];
|
|
break;
|
|
}
|
|
else if (node->nod_arg[e_if_false]) {
|
|
node = node->nod_arg[e_if_false];
|
|
break;
|
|
}
|
|
else
|
|
request->req_operation = req::req_return;
|
|
node = node->nod_parent;
|
|
break;
|
|
|
|
case nod_modify:
|
|
impure = (STA) ((SCHAR *) request + node->nod_impure);
|
|
if ((request->req_operation == req::req_return) &&
|
|
(!impure->sta_state) && (node->nod_arg[e_mod_sub_mod])) {
|
|
if (!top_node) {
|
|
top_node = node;
|
|
which_mod_trig = PRE_TRIG;
|
|
}
|
|
prev_node = node;
|
|
node = modify(tdbb, node, which_mod_trig);
|
|
if (which_mod_trig == PRE_TRIG) {
|
|
node = prev_node->nod_arg[e_mod_sub_mod];
|
|
node->nod_parent = prev_node;
|
|
}
|
|
if (top_node == prev_node && which_mod_trig == POST_TRIG) {
|
|
top_node = NULL;
|
|
which_mod_trig = ALL_TRIGS;
|
|
}
|
|
else {
|
|
request->req_operation = req::req_evaluate;
|
|
}
|
|
}
|
|
else {
|
|
prev_node = node;
|
|
node = modify(tdbb, node, ALL_TRIGS);
|
|
if (!(prev_node->nod_arg[e_mod_sub_mod]) &&
|
|
which_mod_trig == PRE_TRIG)
|
|
{
|
|
which_mod_trig = POST_TRIG;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case nod_nop:
|
|
request->req_operation = req::req_return;
|
|
node = node->nod_parent;
|
|
break;
|
|
|
|
case nod_receive:
|
|
node = receive_msg(tdbb, node);
|
|
break;
|
|
|
|
case nod_exec_sql:
|
|
if (request->req_operation == req::req_unwind) {
|
|
node = node->nod_parent;
|
|
break;
|
|
}
|
|
exec_sql(tdbb, request, EVL_expr(tdbb, node->nod_arg[0]));
|
|
if (request->req_operation == req::req_evaluate)
|
|
request->req_operation = req::req_return;
|
|
node = node->nod_parent;
|
|
break;
|
|
|
|
case nod_post:
|
|
DFW_post_work(transaction, dfw_post_event,
|
|
EVL_expr(tdbb, node->nod_arg[0]), 0);
|
|
|
|
/* for an autocommit transaction, events can be posted
|
|
* without any updates */
|
|
|
|
if (transaction->tra_flags & TRA_autocommit)
|
|
transaction->tra_flags |= TRA_perform_autocommit;
|
|
case nod_message:
|
|
if (request->req_operation == req::req_evaluate)
|
|
request->req_operation = req::req_return;
|
|
node = node->nod_parent;
|
|
break;
|
|
|
|
case nod_stall:
|
|
node = stall(tdbb, node);
|
|
break;
|
|
|
|
case nod_select:
|
|
node = selct(tdbb, node);
|
|
break;
|
|
|
|
case nod_send:
|
|
node = send_msg(tdbb, node);
|
|
break;
|
|
|
|
case nod_store:
|
|
impure = (STA) ((SCHAR *) request + node->nod_impure);
|
|
if ((request->req_operation == req::req_return) &&
|
|
(!impure->sta_state) && (node->nod_arg[e_sto_sub_store])) {
|
|
if (!top_node) {
|
|
top_node = node;
|
|
which_sto_trig = PRE_TRIG;
|
|
}
|
|
prev_node = node;
|
|
node = store(tdbb, node, which_sto_trig);
|
|
if (which_sto_trig == PRE_TRIG) {
|
|
node = prev_node->nod_arg[e_sto_sub_store];
|
|
node->nod_parent = prev_node;
|
|
}
|
|
if (top_node == prev_node && which_sto_trig == POST_TRIG) {
|
|
top_node = NULL;
|
|
which_sto_trig = ALL_TRIGS;
|
|
}
|
|
else
|
|
request->req_operation = req::req_evaluate;
|
|
}
|
|
else {
|
|
prev_node = node;
|
|
node = store(tdbb, node, ALL_TRIGS);
|
|
if (!(prev_node->nod_arg[e_sto_sub_store]) &&
|
|
which_sto_trig == PRE_TRIG)
|
|
which_sto_trig = POST_TRIG;
|
|
}
|
|
|
|
break;
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
case nod_seek:
|
|
node = seek_rse(tdbb, request, node);
|
|
break;
|
|
#endif
|
|
|
|
#ifdef PC_ENGINE
|
|
case nod_stream:
|
|
node = stream(tdbb, node);
|
|
break;
|
|
|
|
case nod_find:
|
|
node = find(tdbb, node);
|
|
break;
|
|
|
|
case nod_find_dbkey:
|
|
case nod_find_dbkey_version:
|
|
node = find_dbkey(tdbb, node);
|
|
break;
|
|
|
|
case nod_set_index:
|
|
node = set_index(tdbb, node);
|
|
break;
|
|
|
|
case nod_set_bookmark:
|
|
node = set_bookmark(tdbb, node);
|
|
break;
|
|
|
|
case nod_release_bookmark:
|
|
node = release_bookmark(tdbb, node);
|
|
break;
|
|
|
|
case nod_end_range:
|
|
node = RNG_end(node);
|
|
break;
|
|
|
|
case nod_delete_range:
|
|
node = RNG_delete(node);
|
|
break;
|
|
|
|
case nod_delete_ranges:
|
|
if (request->req_operation == req::req_evaluate) {
|
|
RNG_delete_ranges(request);
|
|
request->req_operation = req::req_return;
|
|
}
|
|
node = node->nod_parent;
|
|
break;
|
|
|
|
case nod_range_relation:
|
|
node = RNG_add_relation(node);
|
|
break;
|
|
|
|
case nod_release_lock:
|
|
if (request->req_operation == req::req_evaluate) {
|
|
DSC *desc;
|
|
|
|
desc = EVL_expr(tdbb, node->nod_arg[e_rellock_lock]);
|
|
#if SIZEOF_VOID_P != 8
|
|
RLCK_release_lock(*(LCK *) desc->dsc_address);
|
|
#else
|
|
{
|
|
ATT attachment;
|
|
LCK lock;
|
|
ULONG slot;
|
|
VEC vector;
|
|
|
|
attachment = tdbb->tdbb_attachment;
|
|
|
|
lock = NULL;
|
|
slot = *(ULONG *) desc->dsc_address;
|
|
if ((vector = attachment->att_lck_quick_ref) &&
|
|
slot < vector->vec_count)
|
|
lock = (LCK) vector->vec_object[slot];
|
|
RLCK_release_lock(lock);
|
|
vector->vec_object[slot] = NULL;
|
|
}
|
|
#endif
|
|
request->req_operation = req::req_return;
|
|
}
|
|
node = node->nod_parent;
|
|
break;
|
|
|
|
case nod_release_locks:
|
|
if (request->req_operation == req::req_evaluate) {
|
|
RLCK_release_locks(request->req_attachment);
|
|
request->req_operation = req::req_return;
|
|
}
|
|
node = node->nod_parent;
|
|
break;
|
|
|
|
case nod_force_crack:
|
|
if (request->req_operation == req::req_evaluate) {
|
|
RSE_MARK_CRACK(tdbb, *(RSB *) node->nod_arg[1],
|
|
irsb_crack | irsb_forced_crack);
|
|
request->req_operation = req::req_return;
|
|
}
|
|
node = node->nod_parent;
|
|
break;
|
|
|
|
case nod_reset_stream:
|
|
if (request->req_operation == req::req_evaluate) {
|
|
RSE_reset_position(tdbb,
|
|
*(RSB *) node->nod_arg[e_reset_from_rsb],
|
|
request->req_rpb +
|
|
(USHORT) node->nod_arg[e_reset_to_stream]);
|
|
request->req_operation = req::req_return;
|
|
}
|
|
node = node->nod_parent;
|
|
break;
|
|
#endif
|
|
|
|
case nod_set_generator:
|
|
if (request->req_operation == req::req_evaluate) {
|
|
DSC *desc;
|
|
|
|
desc = EVL_expr(tdbb, node->nod_arg[e_gen_value]);
|
|
(void) DPM_gen_id(tdbb, (SLONG) node->nod_arg[e_gen_id], 1,
|
|
MOV_get_int64(desc, 0));
|
|
request->req_operation = req::req_return;
|
|
}
|
|
node = node->nod_parent;
|
|
break;
|
|
|
|
case nod_set_generator2:
|
|
if (request->req_operation == req::req_evaluate) {
|
|
DSC *desc;
|
|
|
|
desc = EVL_expr(tdbb, node->nod_arg[e_gen_value]);
|
|
(void) DPM_gen_id(tdbb, (SLONG) node->nod_arg[e_gen_id], 1,
|
|
MOV_get_int64(desc, 0));
|
|
request->req_operation = req::req_return;
|
|
}
|
|
node = node->nod_parent;
|
|
break;
|
|
|
|
default:
|
|
BUGCHECK(168); /* msg 168 looper: action not yet implemented */
|
|
}
|
|
|
|
#ifdef HSDEBUGSTACK
|
|
TestStack();
|
|
#endif
|
|
#if defined(DEBUG_GDS_ALLOC) && defined(PROD_BUILD)
|
|
memory_count++;
|
|
if ((memory_count % memory_debug) == 0) {
|
|
ALL_check_memory();
|
|
}
|
|
#endif
|
|
} // try
|
|
catch (...) {
|
|
// If we already have a pending error, and took another, simply
|
|
// pass the buck.
|
|
if (error_pending == TRUE) {
|
|
Firebird::status_exception::raise(tdbb->tdbb_status_vector[1]);
|
|
}
|
|
|
|
/* If the database is already bug-checked, then get out. */
|
|
if (dbb->dbb_flags & DBB_bugcheck) {
|
|
Firebird::status_exception::raise(tdbb->tdbb_status_vector[1]);
|
|
}
|
|
|
|
/* Since an error happened, the current savepoint needs to be undone. */
|
|
if (transaction != dbb->dbb_sys_trans) {
|
|
++transaction->tra_save_point->sav_verb_count;
|
|
VERB_CLEANUP;
|
|
}
|
|
|
|
error_pending = TRUE;
|
|
request->req_operation = req::req_unwind;
|
|
request->req_label = 0;
|
|
}
|
|
} // while()
|
|
|
|
/* if there is no node, assume we have finished processing the
|
|
request unless we are in the middle of processing an asynchronous message */
|
|
|
|
if (!node
|
|
#ifdef SCROLLABLE_CURSORS
|
|
&& !(request->req_flags & req_async_processing)
|
|
#endif
|
|
)
|
|
{
|
|
request->req_flags &= ~(req_active | req_reserved);
|
|
request->req_timestamp = 0;
|
|
release_blobs(tdbb, request);
|
|
}
|
|
|
|
request->req_next = node;
|
|
tdbb->tdbb_default = old_pool;
|
|
tdbb->tdbb_transaction = (tdbb->tdbb_request = old_request) ?
|
|
old_request->req_transaction : NULL;
|
|
|
|
// in the case of a pending error condition (one which did not
|
|
// result in a exception to the top of looper), we need to
|
|
// delete the last savepoint
|
|
|
|
if (error_pending) {
|
|
if (transaction != dbb->dbb_sys_trans) {
|
|
SAV save_point;
|
|
|
|
for (save_point = transaction->tra_save_point; ((save_point)
|
|
&&
|
|
(save_point_number
|
|
<= save_point->
|
|
sav_number));
|
|
save_point = transaction->tra_save_point) {
|
|
//if (error_pending)
|
|
++transaction->tra_save_point->sav_verb_count;
|
|
VERB_CLEANUP;
|
|
}
|
|
}
|
|
|
|
ERR_punt();
|
|
}
|
|
|
|
// if the request was aborted, assume that we have already
|
|
// longjmp'ed to the top of looper, and therefore that the
|
|
// last savepoint has already been deleted
|
|
|
|
if (request->req_flags & req_abort) {
|
|
ERR_post(gds_req_sync, 0);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
|
|
static JRD_NOD modify(TDBB tdbb, register JRD_NOD node, SSHORT which_trig)
|
|
{
|
|
/**************************************
|
|
*
|
|
* m o d i f y
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Execute a MODIFY statement.
|
|
*
|
|
**************************************/
|
|
DBB dbb;
|
|
register REQ request, trigger;
|
|
STA impure;
|
|
FMT org_format, new_format;
|
|
SSHORT org_stream, new_stream;
|
|
REC org_record, new_record;
|
|
RPB *org_rpb, *new_rpb;
|
|
REL relation;
|
|
TRA transaction;
|
|
#ifdef PC_ENGINE
|
|
RSB rsb = NULL;
|
|
LCK record_locking;
|
|
IRSB irsb;
|
|
#endif
|
|
|
|
SET_TDBB(tdbb);
|
|
dbb = tdbb->tdbb_database;
|
|
BLKCHK(node, type_nod);
|
|
|
|
request = tdbb->tdbb_request;
|
|
transaction = request->req_transaction;
|
|
impure = (STA) ((SCHAR *) request + node->nod_impure);
|
|
|
|
org_stream = (USHORT) node->nod_arg[e_mod_org_stream];
|
|
org_rpb = &request->req_rpb[org_stream];
|
|
relation = org_rpb->rpb_relation;
|
|
|
|
new_stream = (USHORT) node->nod_arg[e_mod_new_stream];
|
|
new_rpb = &request->req_rpb[new_stream];
|
|
|
|
#ifdef PC_ENGINE
|
|
/* for navigational streams, retrieve the rsb */
|
|
|
|
if (node->nod_arg[e_mod_rsb]) {
|
|
rsb = *(RSB *) node->nod_arg[e_mod_rsb];
|
|
irsb = (IRSB) ((UCHAR *) request + rsb->rsb_impure);
|
|
}
|
|
|
|
/* if we are on a crack in a navigational stream, modify is an illegal operation */
|
|
|
|
if (rsb && EXE_crack(tdbb, rsb, irsb_bof | irsb_eof | irsb_crack)) {
|
|
EXE_mark_crack(tdbb, rsb, irsb->irsb_flags);
|
|
request->req_operation = req::req_return;
|
|
return node->nod_parent;
|
|
}
|
|
#endif
|
|
|
|
/* If the stream was sorted, the various fields in the rpb are
|
|
probably junk. Just to make sure that everything is cool,
|
|
refetch and release the record. */
|
|
|
|
if (org_rpb->rpb_stream_flags & RPB_s_refetch) {
|
|
SLONG tid_fetch;
|
|
|
|
tid_fetch = org_rpb->rpb_transaction;
|
|
if ((!DPM_get(tdbb, org_rpb, LCK_read)) ||
|
|
(!VIO_chase_record_version(tdbb,
|
|
org_rpb,
|
|
NULL,
|
|
transaction,
|
|
reinterpret_cast<BLK>(tdbb->tdbb_default))))
|
|
{
|
|
ERR_post(gds_deadlock, gds_arg_gds, gds_update_conflict, 0);
|
|
}
|
|
VIO_data(tdbb, org_rpb,
|
|
reinterpret_cast<BLK>(tdbb->tdbb_request->req_pool));
|
|
|
|
/* If record is present, and the transaction is read committed,
|
|
* make sure the record has not been updated. Also, punt after
|
|
* VIO_data () call which will release the page.
|
|
*/
|
|
|
|
if ((transaction->tra_flags & TRA_read_committed) &&
|
|
(tid_fetch != org_rpb->rpb_transaction))
|
|
{
|
|
ERR_post(gds_deadlock, gds_arg_gds, gds_update_conflict, 0);
|
|
}
|
|
|
|
org_rpb->rpb_stream_flags &= ~RPB_s_refetch;
|
|
}
|
|
|
|
switch (request->req_operation) {
|
|
case req::req_evaluate:
|
|
break;
|
|
|
|
case req::req_return:
|
|
if (impure->sta_state) {
|
|
impure->sta_state = 0;
|
|
org_record = org_rpb->rpb_record;
|
|
new_record = new_rpb->rpb_record;
|
|
MOVE_FASTER(new_record->rec_data, org_record->rec_data,
|
|
new_record->rec_length);
|
|
request->req_operation = req::req_evaluate;
|
|
return node->nod_arg[e_mod_statement];
|
|
}
|
|
|
|
/* CVC: This call made here to clear the record in each NULL field and
|
|
varchar field whose tail may contain garbage. */
|
|
cleanup_rpb(tdbb, new_rpb);
|
|
|
|
#ifdef PC_ENGINE
|
|
/* check to see if record locking has been initiated in this database;
|
|
if so then lock the record for shared write so that normal processing
|
|
will be able to read or write the record but not when an explicit
|
|
lock has been taken out */
|
|
|
|
try
|
|
{
|
|
|
|
LCK_RAII_wrapper implicit_lock;
|
|
|
|
if (!(transaction->tra_flags & TRA_degree3))
|
|
{
|
|
record_locking = RLCK_record_locking(relation);
|
|
if (record_locking->lck_physical != LCK_PR)
|
|
{
|
|
implicit_lock.assign(implicit_record_lock(transaction, org_rpb));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (transaction != dbb->dbb_sys_trans)
|
|
++transaction->tra_save_point->sav_verb_count;
|
|
|
|
if (relation->rel_pre_modify &&
|
|
which_trig != POST_TRIG &&
|
|
(trigger = execute_triggers(tdbb, &relation->rel_pre_modify,
|
|
org_rpb->rpb_record,
|
|
new_rpb->rpb_record)))
|
|
{
|
|
trigger_failure(tdbb, trigger);
|
|
}
|
|
|
|
if (node->nod_arg[e_mod_validate]) {
|
|
validate(tdbb, node->nod_arg[e_mod_validate]);
|
|
}
|
|
|
|
if (relation->rel_file)
|
|
{
|
|
EXT_modify(org_rpb, new_rpb,
|
|
reinterpret_cast<int*>(transaction));
|
|
}
|
|
else if (!relation->rel_view_rse)
|
|
{
|
|
SSHORT bad_index;
|
|
REL bad_relation;
|
|
IDX_E error_code;
|
|
|
|
// Repeat it as many times as underlying record modifies
|
|
while (TRUE) {
|
|
if (VIO_modify(tdbb, org_rpb, new_rpb, transaction)) break;
|
|
if ( !(transaction->tra_flags & TRA_read_committed) ||
|
|
(transaction->tra_flags & TRA_rec_version) ||
|
|
(transaction->tra_flags & TRA_nowait) )
|
|
{
|
|
ERR_post(isc_deadlock, isc_arg_gds, isc_update_conflict, 0);
|
|
}
|
|
org_rpb->rpb_stream_flags |= RPB_s_refetch;
|
|
}
|
|
|
|
error_code = IDX_modify(tdbb,
|
|
org_rpb,
|
|
new_rpb,
|
|
transaction,
|
|
&bad_relation,
|
|
reinterpret_cast<USHORT*>(&bad_index));
|
|
if (error_code) {
|
|
ERR_duplicate_error(error_code, bad_relation, bad_index);
|
|
}
|
|
}
|
|
|
|
if (relation->rel_post_modify &&
|
|
which_trig != PRE_TRIG &&
|
|
(trigger = execute_triggers(tdbb, &relation->rel_post_modify,
|
|
org_rpb->rpb_record,
|
|
new_rpb->rpb_record)))
|
|
{
|
|
trigger_failure(tdbb, trigger);
|
|
}
|
|
|
|
/* now call IDX_modify_check_constrints after all post modify triggers
|
|
have fired. This is required for cascading referential integrity,
|
|
which can be implemented as post_erase triggers */
|
|
|
|
if (!relation->rel_file && !relation->rel_view_rse)
|
|
{
|
|
SSHORT bad_index;
|
|
REL bad_relation;
|
|
IDX_E error_code;
|
|
|
|
if ( (error_code = IDX_modify_check_constraints(tdbb,
|
|
org_rpb,
|
|
new_rpb,
|
|
transaction,
|
|
&bad_relation,
|
|
reinterpret_cast <
|
|
USHORT *
|
|
>(&bad_index))) )
|
|
ERR_duplicate_error(error_code, bad_relation, bad_index);
|
|
}
|
|
|
|
if (transaction != dbb->dbb_sys_trans) {
|
|
--transaction->tra_save_point->sav_verb_count;
|
|
}
|
|
|
|
#ifdef PC_ENGINE
|
|
|
|
} // try
|
|
catch (...) {
|
|
Firebird::status_exception::raise(-1);
|
|
}
|
|
|
|
/* if the stream is navigational, we must position the stream on the new
|
|
record version, but first set the record number */
|
|
|
|
new_rpb->rpb_number = org_rpb->rpb_number;
|
|
if (rsb) {
|
|
RSE_reset_position(tdbb, rsb, new_rpb);
|
|
}
|
|
#endif
|
|
|
|
/* CVC: Increment the counter only if we called VIO/EXT_modify() and
|
|
we were successful. */
|
|
if (!(request->req_view_flags & req_first_modify_return)) {
|
|
request->req_view_flags |= req_first_modify_return;
|
|
if (relation->rel_view_rse) {
|
|
request->req_top_view_modify = relation;
|
|
}
|
|
}
|
|
if (relation == request->req_top_view_modify) {
|
|
if (which_trig == ALL_TRIGS || which_trig == POST_TRIG) {
|
|
request->req_records_updated++;
|
|
request->req_records_affected++;
|
|
}
|
|
}
|
|
else if (relation->rel_file || !relation->rel_view_rse) {
|
|
request->req_records_updated++;
|
|
request->req_records_affected++;
|
|
}
|
|
|
|
if (which_trig != PRE_TRIG) {
|
|
org_record = org_rpb->rpb_record;
|
|
org_rpb->rpb_record = new_rpb->rpb_record;
|
|
new_rpb->rpb_record = org_record;
|
|
}
|
|
|
|
default:
|
|
return node->nod_parent;
|
|
}
|
|
|
|
impure->sta_state = 0;
|
|
RLCK_reserve_relation(tdbb, transaction, relation, TRUE, TRUE);
|
|
|
|
/* Fall thru on evaluate to set up for modify before executing sub-statement.
|
|
This involves finding the appropriate format, making sure a record block
|
|
exists for the stream and is big enough, and copying fields from the
|
|
original record to the new record. */
|
|
|
|
new_format = MET_current(tdbb, new_rpb->rpb_relation);
|
|
new_record = VIO_record(tdbb, new_rpb, new_format, tdbb->tdbb_default);
|
|
new_rpb->rpb_address = new_record->rec_data;
|
|
new_rpb->rpb_length = new_format->fmt_length;
|
|
new_rpb->rpb_format_number = new_format->fmt_version;
|
|
|
|
if (!(org_record = org_rpb->rpb_record)) {
|
|
org_record =
|
|
VIO_record(tdbb, org_rpb, new_format, tdbb->tdbb_default);
|
|
org_format = org_record->rec_format;
|
|
org_rpb->rpb_address = org_record->rec_data;
|
|
org_rpb->rpb_length = org_format->fmt_length;
|
|
org_rpb->rpb_format_number = org_format->fmt_version;
|
|
}
|
|
else
|
|
org_format = org_record->rec_format;
|
|
|
|
/* Copy the original record to the new record. If the format hasn't changed,
|
|
this is a simple move. If the format has changed, each field must be
|
|
fetched and moved separately, remembering to set the missing flag. */
|
|
|
|
if (new_format->fmt_version == org_format->fmt_version)
|
|
MOVE_FASTER(org_record->rec_data, new_rpb->rpb_address,
|
|
new_rpb->rpb_length);
|
|
else {
|
|
SSHORT i;
|
|
DSC org_desc, new_desc;
|
|
|
|
for (i = 0; i < new_format->fmt_count; i++) {
|
|
/* In order to "map a null to a default" value (in EVL_field()),
|
|
* the relation block is referenced.
|
|
* Reference: Bug 10116, 10424
|
|
*/
|
|
CLEAR_NULL(new_record, i);
|
|
if (EVL_field(new_rpb->rpb_relation, new_record, i, &new_desc)) {
|
|
if (EVL_field
|
|
(org_rpb->rpb_relation, org_record, i,
|
|
&org_desc)) MOV_move(&org_desc, &new_desc);
|
|
else {
|
|
SET_NULL(new_record, i);
|
|
if (new_desc.dsc_dtype) {
|
|
UCHAR *p;
|
|
USHORT n;
|
|
|
|
p = new_desc.dsc_address;
|
|
n = new_desc.dsc_length;
|
|
do
|
|
*p++ = 0;
|
|
while (--n);
|
|
}
|
|
} /* if (org_record) */
|
|
} /* if (new_record) */
|
|
} /* for (fmt_count) */
|
|
}
|
|
|
|
if (node->nod_arg[e_mod_map_view]) {
|
|
impure->sta_state = 1;
|
|
return node->nod_arg[e_mod_map_view];
|
|
}
|
|
|
|
return node->nod_arg[e_mod_statement];
|
|
}
|
|
|
|
static void writelock(TDBB tdbb, register JRD_NOD node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* w r i t e l o c k
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Set write lock by making record owned by this transaction.
|
|
* Current implementation is absolutely not perfect.
|
|
* It basically works as modify, but doesn't call triggers
|
|
* better implementation would require modification in VIO code
|
|
*
|
|
**************************************/
|
|
DBB dbb;
|
|
register REQ request, trigger;
|
|
// STA impure;
|
|
FMT org_format, new_format;
|
|
SSHORT org_stream;
|
|
REC org_record, new_record;
|
|
RPB *org_rpb;
|
|
rpb new_rpb;
|
|
REL relation;
|
|
TRA transaction;
|
|
|
|
SET_TDBB(tdbb);
|
|
dbb = tdbb->tdbb_database;
|
|
BLKCHK(node, type_nod);
|
|
|
|
request = tdbb->tdbb_request;
|
|
transaction = request->req_transaction;
|
|
|
|
// impure = (STA) ((SCHAR *) request + node->nod_impure);
|
|
|
|
org_stream = (USHORT) node->nod_arg[e_writelock_stream];
|
|
org_rpb = &request->req_rpb[org_stream];
|
|
relation = org_rpb->rpb_relation;
|
|
|
|
/* If the stream was sorted, the various fields in the rpb are
|
|
probably junk. Just to make sure that everything is cool,
|
|
refetch and release the record. */
|
|
|
|
if (org_rpb->rpb_stream_flags & RPB_s_refetch) {
|
|
SLONG tid_fetch;
|
|
|
|
tid_fetch = org_rpb->rpb_transaction;
|
|
if ((!DPM_get(tdbb, org_rpb, LCK_read)) ||
|
|
(!VIO_chase_record_version(tdbb,
|
|
org_rpb,
|
|
NULL,
|
|
transaction,
|
|
reinterpret_cast<BLK>(tdbb->tdbb_default))))
|
|
{
|
|
ERR_post(gds_deadlock, gds_arg_gds, gds_update_conflict, 0);
|
|
}
|
|
VIO_data(tdbb, org_rpb,
|
|
reinterpret_cast<BLK>(tdbb->tdbb_request->req_pool));
|
|
|
|
/* If record is present, and the transaction is read committed,
|
|
* make sure the record has not been updated. Also, punt after
|
|
* VIO_data () call which will release the page.
|
|
*/
|
|
|
|
if ((transaction->tra_flags & TRA_read_committed) &&
|
|
(tid_fetch != org_rpb->rpb_transaction))
|
|
{
|
|
ERR_post(gds_deadlock, gds_arg_gds, gds_update_conflict, 0);
|
|
}
|
|
|
|
org_rpb->rpb_stream_flags &= ~RPB_s_refetch;
|
|
}
|
|
|
|
memset(&new_rpb,0,sizeof(new_rpb));
|
|
|
|
new_rpb.rpb_relation = relation;
|
|
new_format = MET_current(tdbb, relation);
|
|
new_record = VIO_record(tdbb, &new_rpb, new_format, tdbb->tdbb_default);
|
|
new_rpb.rpb_address = new_record->rec_data;
|
|
new_rpb.rpb_length = new_format->fmt_length;
|
|
new_rpb.rpb_format_number = new_format->fmt_version;
|
|
|
|
if (!(org_record = org_rpb->rpb_record)) {
|
|
org_record =
|
|
VIO_record(tdbb, org_rpb, new_format, tdbb->tdbb_default);
|
|
org_format = org_record->rec_format;
|
|
org_rpb->rpb_address = org_record->rec_data;
|
|
org_rpb->rpb_length = org_format->fmt_length;
|
|
org_rpb->rpb_format_number = org_format->fmt_version;
|
|
}
|
|
else
|
|
org_format = org_record->rec_format;
|
|
|
|
/* Copy the original record to the new record. If the format hasn't changed,
|
|
this is a simple move. If the format has changed, each field must be
|
|
fetched and moved separately, remembering to set the missing flag. */
|
|
|
|
if (new_format->fmt_version == org_format->fmt_version)
|
|
MOVE_FASTER(org_record->rec_data, new_rpb.rpb_address,
|
|
new_rpb.rpb_length);
|
|
else {
|
|
SSHORT i;
|
|
DSC org_desc, new_desc;
|
|
|
|
for (i = 0; i < new_format->fmt_count; i++) {
|
|
/* In order to "map a null to a default" value (in EVL_field()),
|
|
* the relation block is referenced.
|
|
* Reference: Bug 10116, 10424
|
|
*/
|
|
CLEAR_NULL(new_record, i);
|
|
if (EVL_field(new_rpb.rpb_relation, new_record, i, &new_desc)) {
|
|
if (EVL_field
|
|
(org_rpb->rpb_relation, org_record, i,
|
|
&org_desc)) MOV_move(&org_desc, &new_desc);
|
|
else {
|
|
SET_NULL(new_record, i);
|
|
if (new_desc.dsc_dtype) {
|
|
UCHAR *p;
|
|
USHORT n;
|
|
|
|
p = new_desc.dsc_address;
|
|
n = new_desc.dsc_length;
|
|
do
|
|
*p++ = 0;
|
|
while (--n);
|
|
}
|
|
} /* if (org_record) */
|
|
} /* if (new_record) */
|
|
} /* for (fmt_count) */
|
|
}
|
|
|
|
if (!relation->rel_view_rse && !relation->rel_file)
|
|
{
|
|
SSHORT bad_index;
|
|
REL bad_relation;
|
|
IDX_E error_code;
|
|
|
|
RLCK_reserve_relation(tdbb, transaction, relation, TRUE, TRUE);
|
|
|
|
// Repeat it as many times as underlying record modifies
|
|
while (TRUE) {
|
|
if (VIO_modify(tdbb, org_rpb, &new_rpb, transaction)) break;
|
|
if ( !(transaction->tra_flags & TRA_read_committed) ||
|
|
(transaction->tra_flags & TRA_rec_version) ||
|
|
(transaction->tra_flags & TRA_nowait) )
|
|
{
|
|
ERR_post(isc_deadlock, isc_arg_gds, isc_update_conflict, 0);
|
|
}
|
|
org_rpb->rpb_stream_flags |= RPB_s_refetch;
|
|
}
|
|
error_code = IDX_modify(tdbb,
|
|
org_rpb,
|
|
&new_rpb,
|
|
transaction,
|
|
&bad_relation,
|
|
reinterpret_cast<USHORT*>(&bad_index));
|
|
if (error_code) {
|
|
ERR_duplicate_error(error_code, bad_relation, bad_index);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static JRD_NOD receive_msg(TDBB tdbb, register JRD_NOD node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* r e c e i v e _ m s g
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Execute a RECEIVE statement. This can be entered either
|
|
* with "req_evaluate" (ordinary receive statement) or
|
|
* "req_proceed" (select statement). In the latter case,
|
|
* the statement isn't every formalled evaluated.
|
|
*
|
|
**************************************/
|
|
register REQ request;
|
|
|
|
SET_TDBB(tdbb);
|
|
request = tdbb->tdbb_request;
|
|
BLKCHK(node, type_nod);
|
|
|
|
switch (request->req_operation) {
|
|
case req::req_evaluate:
|
|
request->req_operation = req::req_receive;
|
|
request->req_message = node->nod_arg[e_send_message];
|
|
request->req_flags |= req_stall;
|
|
return node;
|
|
|
|
case req::req_proceed:
|
|
request->req_operation = req::req_evaluate;
|
|
return (node->nod_arg[e_send_statement]);
|
|
|
|
default:
|
|
return (node->nod_parent);
|
|
}
|
|
}
|
|
|
|
|
|
static void release_blobs(TDBB tdbb, REQ request)
|
|
{
|
|
/**************************************
|
|
*
|
|
* r e l e a s e _ b l o b s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Release temporary blobs assigned by this request.
|
|
*
|
|
**************************************/
|
|
TRA transaction;
|
|
BLB *blob;
|
|
ARR *array;
|
|
|
|
SET_TDBB(tdbb);
|
|
DEV_BLKCHK(request, type_req);
|
|
|
|
if ( (transaction = request->req_transaction) ) {
|
|
DEV_BLKCHK(transaction, type_tra);
|
|
|
|
/* Release blobs assigned by this request */
|
|
|
|
for (blob = &transaction->tra_blobs; *blob;) {
|
|
DEV_BLKCHK(*blob, type_blb);
|
|
if ((*blob)->blb_request == request)
|
|
BLB_cancel(tdbb, *blob);
|
|
else
|
|
blob = &(*blob)->blb_next;
|
|
}
|
|
|
|
/* Release arrays assigned by this request */
|
|
|
|
for (array = &transaction->tra_arrays; *array;) {
|
|
DEV_BLKCHK(*array, type_arr);
|
|
if ((*array)->arr_request == request)
|
|
BLB_release_array(*array);
|
|
else
|
|
array = &(*array)->arr_next;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef PC_ENGINE
|
|
static JRD_NOD release_bookmark(TDBB tdbb, JRD_NOD node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* r e l e a s e _ b o o k m a r k
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Deallocate the passed bookmark.
|
|
*
|
|
**************************************/
|
|
REQ request;
|
|
|
|
SET_TDBB(tdbb);
|
|
request = tdbb->tdbb_request;
|
|
BLKCHK(node, type_nod);
|
|
|
|
if (request->req_operation == req::req_evaluate) {
|
|
BKM_release(node->nod_arg[e_relmark_id]);
|
|
request->req_operation = req::req_return;
|
|
}
|
|
|
|
return node->nod_parent;
|
|
}
|
|
#endif
|
|
|
|
|
|
static void release_proc_save_points(REQ request)
|
|
{
|
|
/**************************************
|
|
*
|
|
* r e l e a s e _ p r o c _ s a v e _ p o i n t s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Release temporary blobs assigned by this request.
|
|
*
|
|
**************************************/
|
|
TRA transaction;
|
|
SAV sav_point, temp_sav_point;
|
|
|
|
/* Release savepoints assigned by this request */
|
|
|
|
if ((transaction = request->req_transaction) &&
|
|
(sav_point = request->req_proc_sav_point)) {
|
|
for (temp_sav_point = sav_point; temp_sav_point->sav_next;
|
|
temp_sav_point = temp_sav_point->sav_next);
|
|
temp_sav_point->sav_next = transaction->tra_save_free;
|
|
transaction->tra_save_free = sav_point;
|
|
}
|
|
request->req_proc_sav_point = NULL;
|
|
}
|
|
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
static JRD_NOD seek_rse(TDBB tdbb, REQ request, JRD_NOD node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s e e k _ r s e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Execute a nod_seek, which specifies
|
|
* a direction and offset in which to
|
|
* scroll a record selection expression.
|
|
*
|
|
**************************************/
|
|
USHORT direction;
|
|
SLONG offset;
|
|
RSE rse;
|
|
|
|
SET_TDBB(tdbb);
|
|
DEV_BLKCHK(node, type_nod);
|
|
|
|
if (request->req_operation == req::req_proceed) {
|
|
/* get input arguments */
|
|
|
|
direction = MOV_get_long(EVL_expr(tdbb,
|
|
node->nod_arg[e_seek_direction]),
|
|
0);
|
|
offset =
|
|
MOV_get_long(EVL_expr(tdbb, node->nod_arg[e_seek_offset]), 0);
|
|
|
|
rse = (RSE) node->nod_arg[e_seek_rse];
|
|
|
|
seek_rsb(tdbb, request, rse->rse_rsb, direction, offset);
|
|
|
|
request->req_operation = req::req_return;
|
|
}
|
|
|
|
return node->nod_parent;
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
static void seek_rsb(
|
|
TDBB tdbb,
|
|
REQ request, RSB rsb, USHORT direction, SLONG offset)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s e e k _ r s b
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Allow scrolling through a stream as defined
|
|
* by the input rsb. Handles cracks, refresh
|
|
* ranges, and multiple seeking. Uses RSE_get_record ()
|
|
* to do the actual work.
|
|
*
|
|
**************************************/
|
|
USHORT crack_flag = 0;
|
|
IRSB impure;
|
|
IRSB next_impure;
|
|
|
|
SET_TDBB(tdbb);
|
|
DEV_BLKCHK(rsb, type_rsb);
|
|
impure = (IRSB) ((UCHAR *) request + rsb->rsb_impure);
|
|
|
|
/* look past any boolean to the actual stream */
|
|
|
|
if (rsb->rsb_type == rsb_boolean) {
|
|
seek_rsb(tdbb, request, rsb->rsb_next, direction, offset);
|
|
|
|
/* set the backwards flag */
|
|
|
|
next_impure = (IRSB) ((UCHAR *) request + rsb->rsb_next->rsb_impure);
|
|
|
|
if (next_impure->irsb_flags & irsb_last_backwards)
|
|
impure->irsb_flags |= irsb_last_backwards;
|
|
else
|
|
impure->irsb_flags &= ~irsb_last_backwards;
|
|
return;
|
|
}
|
|
|
|
/* do simple boundary checking for bof and eof */
|
|
|
|
switch (direction) {
|
|
case blr_forward:
|
|
if (impure->irsb_flags & irsb_eof)
|
|
ERR_post(isc_stream_eof, 0);
|
|
break;
|
|
|
|
case blr_backward:
|
|
if (impure->irsb_flags & irsb_bof)
|
|
ERR_post(isc_stream_bof, 0);
|
|
break;
|
|
|
|
case blr_bof_forward:
|
|
case blr_eof_backward:
|
|
break;
|
|
|
|
default:
|
|
BUGCHECK(232);
|
|
}
|
|
|
|
/* the actual offset to seek may be one less because the next time
|
|
through the blr_for loop we will seek one record--flag the fact
|
|
that a fetch is required on this stream in case it doesn't happen
|
|
(for example when GPRE generates BLR which does not stall prior to
|
|
the blr_for, as DSQL does) */
|
|
|
|
if (offset > 0)
|
|
switch (direction) {
|
|
case blr_forward:
|
|
case blr_bof_forward:
|
|
if (!(impure->irsb_flags & irsb_last_backwards)) {
|
|
offset--;
|
|
if (!(impure->irsb_flags & irsb_bof))
|
|
request->req_flags |= req_fetch_required;
|
|
}
|
|
break;
|
|
|
|
case blr_backward:
|
|
case blr_eof_backward:
|
|
if (impure->irsb_flags & irsb_last_backwards) {
|
|
offset--;
|
|
if (!(impure->irsb_flags & irsb_eof))
|
|
request->req_flags |= req_fetch_required;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* now do the actual seek */
|
|
|
|
switch (direction) {
|
|
case blr_forward: /* go forward from the current location */
|
|
|
|
#ifdef PC_ENGINE
|
|
if ((offset == 1) && request->req_begin_ranges)
|
|
impure->irsb_flags |= irsb_refresh;
|
|
#endif
|
|
|
|
/* the rsb_backwards flag is used to indicate the direction to seek in;
|
|
this is sticky in the sense that after the user has seek'ed in the
|
|
backward direction, the next retrieval from a blr_for loop will also
|
|
be in the backward direction--this allows us to continue scrolling
|
|
without constantly sending messages to the engine */
|
|
|
|
impure->irsb_flags &= ~irsb_last_backwards;
|
|
|
|
while (offset) {
|
|
offset--;
|
|
if (!(RSE_get_record(tdbb, rsb, RSE_get_next)))
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case blr_backward: /* go backward from the current location */
|
|
|
|
#ifdef PC_ENGINE
|
|
if ((offset == 1) && request->req_begin_ranges)
|
|
impure->irsb_flags |= irsb_refresh;
|
|
#endif
|
|
|
|
impure->irsb_flags |= irsb_last_backwards;
|
|
|
|
while (offset) {
|
|
offset--;
|
|
if (!(RSE_get_record(tdbb, rsb, RSE_get_next)))
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case blr_bof_forward: /* go forward from the beginning of the stream */
|
|
|
|
RSE_close(tdbb, rsb);
|
|
RSE_open(tdbb, rsb);
|
|
|
|
impure->irsb_flags &= ~irsb_last_backwards;
|
|
|
|
while (offset) {
|
|
offset--;
|
|
if (!(RSE_get_record(tdbb, rsb, RSE_get_next)))
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case blr_eof_backward: /* go backward from the end of the stream */
|
|
|
|
RSE_close(tdbb, rsb);
|
|
RSE_open(tdbb, rsb);
|
|
|
|
/* if this is a stream type which uses bof and eof flags,
|
|
reverse the sense of bof and eof in this case */
|
|
|
|
if (impure->irsb_flags & irsb_bof) {
|
|
impure->irsb_flags &= ~irsb_bof;
|
|
impure->irsb_flags |= irsb_eof;
|
|
}
|
|
|
|
impure->irsb_flags |= irsb_last_backwards;
|
|
|
|
while (offset) {
|
|
offset--;
|
|
if (!(RSE_get_record(tdbb, rsb, RSE_get_next)))
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
BUGCHECK(232);
|
|
}
|
|
|
|
#ifdef PC_ENGINE
|
|
impure->irsb_flags &= ~irsb_refresh;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
|
|
static JRD_NOD selct(TDBB tdbb, register JRD_NOD node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s e l e c t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Execute a SELECT statement. This is more than a little
|
|
* obscure. We first set up the SELECT statement as the
|
|
* "message" and stall on receive (waiting for user send).
|
|
* EXE_send will then loop thru the sub-statements of select
|
|
* looking for the appropriate RECEIVE statement. When (or if)
|
|
* it finds it, it will set it up the next statement to be
|
|
* executed. The RECEIVE, then, will be entered with the
|
|
* operation "req_proceed."
|
|
*
|
|
**************************************/
|
|
register REQ request;
|
|
|
|
SET_TDBB(tdbb);
|
|
request = tdbb->tdbb_request;
|
|
BLKCHK(node, type_nod);
|
|
|
|
switch (request->req_operation) {
|
|
case req::req_evaluate:
|
|
request->req_message = node;
|
|
request->req_operation = req::req_receive;
|
|
request->req_flags |= req_stall;
|
|
return node;
|
|
|
|
default:
|
|
return node->nod_parent;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static JRD_NOD send_msg(TDBB tdbb, register JRD_NOD node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s e n d _ m s g
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Execute a SEND statement.
|
|
*
|
|
**************************************/
|
|
register REQ request;
|
|
|
|
SET_TDBB(tdbb);
|
|
request = tdbb->tdbb_request;
|
|
BLKCHK(node, type_nod);
|
|
|
|
switch (request->req_operation) {
|
|
case req::req_evaluate:
|
|
return (node->nod_arg[e_send_statement]);
|
|
|
|
case req::req_return:
|
|
request->req_operation = req::req_send;
|
|
request->req_message = node->nod_arg[e_send_message];
|
|
request->req_flags |= req_stall;
|
|
return node;
|
|
|
|
case req::req_proceed:
|
|
request->req_operation = req::req_return;
|
|
return node->nod_parent;
|
|
|
|
default:
|
|
return (node->nod_parent);
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef PC_ENGINE
|
|
static JRD_NOD set_bookmark(TDBB tdbb, JRD_NOD node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s e t _ b o o k m a r k
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Set a stream to the location of the
|
|
* specified bookmark.
|
|
*
|
|
**************************************/
|
|
REQ request;
|
|
BKM bookmark;
|
|
USHORT stream;
|
|
RPB *rpb;
|
|
RSB rsb;
|
|
IRSB impure;
|
|
|
|
|
|
SET_TDBB(tdbb);
|
|
request = tdbb->tdbb_request;
|
|
BLKCHK(node, type_nod);
|
|
|
|
if (request->req_operation == req::req_evaluate) {
|
|
bookmark = BKM_lookup(node->nod_arg[e_setmark_id]);
|
|
stream = (USHORT) node->nod_arg[e_setmark_stream];
|
|
rpb = &request->req_rpb[stream];
|
|
rsb = *((RSB *) node->nod_arg[e_setmark_rsb]);
|
|
impure = (IRSB) ((UCHAR *) request + rsb->rsb_impure);
|
|
|
|
/* check if the bookmark was at beginning or end of file
|
|
and flag the rsb accordingly */
|
|
|
|
RSE_MARK_CRACK(tdbb, rsb, 0);
|
|
if (bookmark->bkm_flags & bkm_bof)
|
|
RSE_MARK_CRACK(tdbb, rsb, irsb_bof);
|
|
else if (bookmark->bkm_flags & bkm_eof)
|
|
RSE_MARK_CRACK(tdbb, rsb, irsb_eof);
|
|
else if (bookmark->bkm_flags & bkm_crack) {
|
|
RSE_MARK_CRACK(tdbb, rsb, irsb_crack);
|
|
if (bookmark->bkm_flags & bkm_forced_crack)
|
|
RSE_MARK_CRACK(tdbb, rsb, irsb_forced_crack);
|
|
}
|
|
|
|
if (!RSE_set_bookmark(tdbb, rsb, rpb, bookmark))
|
|
EXE_mark_crack(tdbb, rsb,
|
|
impure->irsb_flags & (irsb_crack | irsb_eof |
|
|
irsb_bof));
|
|
|
|
request->req_operation = req::req_return;
|
|
}
|
|
|
|
return node->nod_parent;
|
|
}
|
|
#endif
|
|
|
|
|
|
static void set_error(TDBB tdbb, XCP condition, JRD_NOD node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s e t _ e r r o r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Set status vector according to specified error condition
|
|
* and jump to handle error accordingly.
|
|
*
|
|
**************************************/
|
|
register REQ request;
|
|
TEXT name[32], relation_name[32], *s, *r;
|
|
TEXT message[XCP_MESSAGE_LENGTH + 1], temp[XCP_MESSAGE_LENGTH + 1];
|
|
USHORT length = 0;
|
|
|
|
SET_TDBB(tdbb);
|
|
|
|
if (condition->xcp_rpt[0].xcp_msg)
|
|
{
|
|
/* pick up message from already initiated exception */
|
|
length = MIN(condition->xcp_rpt[0].xcp_msg->str_length, sizeof(message) - 1);
|
|
memcpy(message, condition->xcp_rpt[0].xcp_msg->str_data, length);
|
|
delete condition->xcp_rpt[0].xcp_msg;
|
|
condition->xcp_rpt[0].xcp_msg = 0;
|
|
}
|
|
else if (node)
|
|
{
|
|
const char* string = 0;
|
|
/* evaluate exception message and convert it to string */
|
|
length = MOV_make_string(EVL_expr(tdbb, node),
|
|
ttype_none,
|
|
&string,
|
|
reinterpret_cast<VARY*>(temp),
|
|
sizeof(temp));
|
|
length = MIN(length, sizeof(message) - 1);
|
|
|
|
/* dimitr: or should we throw an error here, i.e.
|
|
replace the above assignment with the following lines:
|
|
|
|
if (length > sizeof(message) - 1)
|
|
ERR_post(gds_imp_exc, gds_arg_gds, gds_blktoobig, 0);
|
|
*/
|
|
|
|
memcpy(message, string, length);
|
|
}
|
|
message[length] = 0;
|
|
|
|
request = tdbb->tdbb_request;
|
|
|
|
switch (condition->xcp_rpt[0].xcp_type) {
|
|
case xcp_sql_code:
|
|
ERR_post(gds_sqlerr,
|
|
gds_arg_number, (SLONG) condition->xcp_rpt[0].xcp_code, 0);
|
|
|
|
case xcp_gds_code:
|
|
if (condition->xcp_rpt[0].xcp_code == gds_check_constraint) {
|
|
MET_lookup_cnstrt_for_trigger(tdbb, name, relation_name,
|
|
request->req_trg_name);
|
|
// CONST CAST
|
|
s = (name[0]) ? name : (TEXT*)"";
|
|
r = (relation_name[0]) ? relation_name : (TEXT*)"";
|
|
ERR_post(condition->xcp_rpt[0].xcp_code,
|
|
gds_arg_string, ERR_cstring(s),
|
|
gds_arg_string, ERR_cstring(r), 0);
|
|
}
|
|
else
|
|
ERR_post(condition->xcp_rpt[0].xcp_code, 0);
|
|
|
|
case xcp_xcp_code:
|
|
MET_lookup_exception(tdbb, condition->xcp_rpt[0].xcp_code,
|
|
name, temp);
|
|
if (message[0])
|
|
s = message;
|
|
else if (temp[0])
|
|
s = temp;
|
|
else if (name[0])
|
|
s = name;
|
|
else
|
|
s = NULL;
|
|
if (s)
|
|
ERR_post(gds_except,
|
|
gds_arg_number, (SLONG) condition->xcp_rpt[0].xcp_code,
|
|
gds_arg_gds, gds_random, gds_arg_string, ERR_cstring(s),
|
|
0);
|
|
else
|
|
ERR_post(gds_except, gds_arg_number,
|
|
(SLONG) condition->xcp_rpt[0].xcp_code, 0);
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef PC_ENGINE
|
|
static JRD_NOD set_index(TDBB tdbb, register JRD_NOD node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s e t _ i n d e x
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Execute a SET INDEX statement.
|
|
*
|
|
**************************************/
|
|
register REQ request;
|
|
USHORT stream, id;
|
|
RPB *rpb;
|
|
REL relation;
|
|
IDX idx;
|
|
|
|
SET_TDBB(tdbb);
|
|
request = tdbb->tdbb_request;
|
|
BLKCHK(node, type_nod);
|
|
|
|
if (request->req_operation == req::req_evaluate) {
|
|
stream = (USHORT) node->nod_arg[e_index_stream];
|
|
|
|
rpb = &request->req_rpb[stream];
|
|
relation = rpb->rpb_relation;
|
|
|
|
/* if id is non-zero, get the index definition;
|
|
otherwise it indicates revert to natural order */
|
|
|
|
id = MOV_get_long(EVL_expr(tdbb, node->nod_arg[e_index_index]), 0);
|
|
if (id && BTR_lookup(tdbb, relation, id - 1, &idx))
|
|
ERR_post(gds_indexnotdefined, gds_arg_string, relation->rel_name,
|
|
gds_arg_number, (SLONG) id, 0);
|
|
|
|
/* generate a new rsb in place of the old */
|
|
|
|
RSE_close(tdbb, *(RSB *) node->nod_arg[e_index_rsb]);
|
|
OPT_set_index(tdbb, request, (RSB *) node->nod_arg[e_index_rsb],
|
|
relation, id ? &idx : (IDX *) 0);
|
|
RSE_open(tdbb, *(RSB *) node->nod_arg[e_index_rsb]);
|
|
|
|
request->req_operation = req::req_return;
|
|
}
|
|
|
|
return node->nod_parent;
|
|
}
|
|
#endif
|
|
|
|
|
|
static JRD_NOD stall(TDBB tdbb, register JRD_NOD node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s t a l l
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Execute a stall statement.
|
|
* This is like a blr_receive, except that there is no
|
|
* need for a gds__send () from the user (i.e. EXE_send () in the engine).
|
|
* A gds__receive () will unblock the user.
|
|
*
|
|
**************************************/
|
|
register REQ request;
|
|
|
|
SET_TDBB(tdbb);
|
|
request = tdbb->tdbb_request;
|
|
BLKCHK(node, type_nod);
|
|
|
|
switch (request->req_operation) {
|
|
case req::req_sync:
|
|
return node->nod_parent;
|
|
|
|
case req::req_proceed:
|
|
request->req_operation = req::req_return;
|
|
return node->nod_parent;
|
|
|
|
default:
|
|
request->req_message = node;
|
|
request->req_operation = req::req_return;
|
|
request->req_flags |= req_stall;
|
|
return node;
|
|
}
|
|
}
|
|
|
|
|
|
static JRD_NOD store(TDBB tdbb, register JRD_NOD node, SSHORT which_trig)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s t o r e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Execute a STORE statement.
|
|
*
|
|
**************************************/
|
|
|
|
REQ trigger;
|
|
FMT format;
|
|
SSHORT n;
|
|
USHORT length;
|
|
REC record;
|
|
DSC *desc;
|
|
register UCHAR *p;
|
|
|
|
SET_TDBB(tdbb);
|
|
DBB dbb = tdbb->tdbb_database;
|
|
BLKCHK(node, type_nod);
|
|
|
|
REQ request = tdbb->tdbb_request;
|
|
TRA transaction = request->req_transaction;
|
|
STA impure = (STA) ((SCHAR *) request + node->nod_impure);
|
|
SSHORT stream = (USHORT) node->nod_arg[e_sto_relation]->nod_arg[e_rel_stream];
|
|
RPB* rpb = &request->req_rpb[stream];
|
|
REL relation = rpb->rpb_relation;
|
|
|
|
switch (request->req_operation) {
|
|
case req::req_evaluate:
|
|
impure->sta_state = 0;
|
|
RLCK_reserve_relation(tdbb, transaction, relation, TRUE, TRUE);
|
|
break;
|
|
|
|
case req::req_return:
|
|
if (impure->sta_state)
|
|
return node->nod_parent;
|
|
record = rpb->rpb_record;
|
|
format = record->rec_format;
|
|
|
|
if (transaction != dbb->dbb_sys_trans)
|
|
++transaction->tra_save_point->sav_verb_count;
|
|
|
|
if (relation->rel_pre_store &&
|
|
(which_trig != POST_TRIG) &&
|
|
(trigger = execute_triggers(tdbb, &relation->rel_pre_store,
|
|
0, record)))
|
|
{
|
|
trigger_failure(tdbb, trigger);
|
|
}
|
|
|
|
if (node->nod_arg[e_sto_validate]) {
|
|
validate(tdbb, node->nod_arg[e_sto_validate]);
|
|
}
|
|
|
|
/* For optimum on-disk record compression, zero all unassigned
|
|
fields. In addition, zero the tail of assigned varying fields
|
|
so that previous remnants don't defeat compression efficiency. */
|
|
|
|
/* CVC: The code that was here was moved to its own routine: cleanup_rpb()
|
|
and replaced by the call shown above. */
|
|
|
|
cleanup_rpb(tdbb, rpb);
|
|
|
|
if (relation->rel_file) {
|
|
EXT_store(rpb, reinterpret_cast < int *>(transaction));
|
|
}
|
|
else if (!relation->rel_view_rse)
|
|
{
|
|
SSHORT bad_index;
|
|
REL bad_relation;
|
|
|
|
VIO_store(tdbb, rpb, transaction);
|
|
IDX_E error_code = IDX_store(tdbb,
|
|
rpb,
|
|
transaction,
|
|
&bad_relation,
|
|
reinterpret_cast<USHORT*>(&bad_index));
|
|
if (error_code) {
|
|
ERR_duplicate_error(error_code, bad_relation, bad_index);
|
|
}
|
|
}
|
|
|
|
if (relation->rel_post_store &&
|
|
(which_trig != PRE_TRIG) &&
|
|
(trigger = execute_triggers(tdbb, &relation->rel_post_store,
|
|
0, record)))
|
|
{
|
|
trigger_failure(tdbb, trigger);
|
|
}
|
|
|
|
/* CVC: Increment the counter only if we called VIO/EXT_store() and
|
|
we were successful. */
|
|
if (!(request->req_view_flags & req_first_store_return)) {
|
|
request->req_view_flags |= req_first_store_return;
|
|
if (relation->rel_view_rse) {
|
|
request->req_top_view_store = relation;
|
|
}
|
|
}
|
|
if (relation == request->req_top_view_store) {
|
|
if (which_trig == ALL_TRIGS || which_trig == POST_TRIG) {
|
|
request->req_records_inserted++;
|
|
request->req_records_affected++;
|
|
}
|
|
}
|
|
else if (relation->rel_file || !relation->rel_view_rse) {
|
|
request->req_records_inserted++;
|
|
request->req_records_affected++;
|
|
}
|
|
|
|
if (transaction != dbb->dbb_sys_trans) {
|
|
--transaction->tra_save_point->sav_verb_count;
|
|
}
|
|
|
|
if (node->nod_arg[e_sto_statement2]) {
|
|
impure->sta_state = 1;
|
|
request->req_operation = req::req_evaluate;
|
|
return node->nod_arg[e_sto_statement2];
|
|
}
|
|
|
|
default:
|
|
return node->nod_parent;
|
|
}
|
|
|
|
/* Fall thru on evaluate to set up for store before executing sub-statement.
|
|
This involves finding the appropriate format, making sure a record block
|
|
exists for the stream and is big enough, and initialize all null flags
|
|
to "missing." */
|
|
|
|
format = MET_current(tdbb, relation);
|
|
record = VIO_record(tdbb, rpb, format, tdbb->tdbb_default);
|
|
|
|
rpb->rpb_address = record->rec_data;
|
|
rpb->rpb_length = format->fmt_length;
|
|
rpb->rpb_format_number = format->fmt_version;
|
|
|
|
/* CVC: This small block added by Ann Harrison to
|
|
start with a clean empty buffer and so avoid getting
|
|
new record buffer with misleading information. Fixes
|
|
bug with incorrect blob sharing during insertion in
|
|
a stored procedure. */
|
|
|
|
p = record->rec_data;
|
|
{
|
|
UCHAR *data_end = p + rpb->rpb_length;
|
|
while (p < data_end)
|
|
*p++ = 0;
|
|
}
|
|
|
|
/* Initialize all fields to missing */
|
|
|
|
p = record->rec_data;
|
|
n = (format->fmt_count + 7) >> 3;
|
|
if (n) {
|
|
do {
|
|
*p++ = 0xff;
|
|
} while (--n);
|
|
}
|
|
|
|
return node->nod_arg[e_sto_statement];
|
|
}
|
|
|
|
|
|
#ifdef PC_ENGINE
|
|
static JRD_NOD stream(TDBB tdbb, register JRD_NOD node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s t r e a m
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Execute a STREAM statement.
|
|
*
|
|
**************************************/
|
|
register REQ request;
|
|
RSB rsb;
|
|
|
|
SET_TDBB(tdbb);
|
|
request = tdbb->tdbb_request;
|
|
BLKCHK(node, type_nod);
|
|
|
|
rsb = ((RSE) node)->rse_rsb;
|
|
|
|
switch (request->req_operation) {
|
|
case req::req_evaluate:
|
|
RSE_open(tdbb, rsb);
|
|
request->req_operation = req::req_return;
|
|
|
|
case req::req_return:
|
|
node = node->nod_parent;
|
|
break;
|
|
|
|
default:
|
|
RSE_close(tdbb, rsb);
|
|
node = node->nod_parent;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
#endif
|
|
|
|
|
|
static BOOLEAN test_and_fixup_error(TDBB tdbb, XCP conditions, REQ request)
|
|
{
|
|
/**************************************
|
|
*
|
|
* t e s t _ a n d _ f i x u p _ e r r o r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Test for match of current state with list of error conditions.
|
|
* Fix type and code of the exception.
|
|
*
|
|
**************************************/
|
|
SSHORT i, sqlcode;
|
|
STATUS *status_vector;
|
|
|
|
SET_TDBB(tdbb);
|
|
status_vector = tdbb->tdbb_status_vector;
|
|
sqlcode = gds__sqlcode(status_vector);
|
|
|
|
const SLONG XCP_SQLCODE = -836;
|
|
|
|
delete request->req_last_xcp.xcp_msg;
|
|
request->req_last_xcp.xcp_msg = 0;
|
|
|
|
for (i = 0; i < conditions->xcp_count; i++)
|
|
{
|
|
switch (conditions->xcp_rpt[i].xcp_type)
|
|
{
|
|
case xcp_sql_code:
|
|
if (sqlcode == conditions->xcp_rpt[i].xcp_code)
|
|
{
|
|
if ((sqlcode != XCP_SQLCODE) || (status_vector[1] != gds_except))
|
|
{
|
|
request->req_last_xcp.xcp_type = xcp_sql_code;
|
|
request->req_last_xcp.xcp_code = sqlcode;
|
|
}
|
|
else
|
|
{
|
|
request->req_last_xcp.xcp_type = xcp_xcp_code;
|
|
request->req_last_xcp.xcp_code = status_vector[3];
|
|
}
|
|
status_vector[0] = 0;
|
|
status_vector[1] = 0;
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case xcp_gds_code:
|
|
if (status_vector[1] == conditions->xcp_rpt[i].xcp_code)
|
|
{
|
|
request->req_last_xcp.xcp_type = xcp_gds_code;
|
|
request->req_last_xcp.xcp_code = status_vector[1];
|
|
status_vector[0] = 0;
|
|
status_vector[1] = 0;
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case xcp_xcp_code:
|
|
if ((status_vector[1] == gds_except) &&
|
|
(status_vector[3] == conditions->xcp_rpt[i].xcp_code))
|
|
{
|
|
request->req_last_xcp.xcp_type = xcp_xcp_code;
|
|
request->req_last_xcp.xcp_code = status_vector[3];
|
|
TEXT *msg = reinterpret_cast<TEXT*>(status_vector[7]);
|
|
assign_xcp_message(tdbb, &request->req_last_xcp.xcp_msg, msg);
|
|
status_vector[0] = 0;
|
|
status_vector[1] = 0;
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case xcp_default:
|
|
if (sqlcode && (sqlcode != XCP_SQLCODE))
|
|
{
|
|
request->req_last_xcp.xcp_type = xcp_sql_code;
|
|
request->req_last_xcp.xcp_code = sqlcode;
|
|
}
|
|
else
|
|
{
|
|
if (status_vector[1] != gds_except)
|
|
{
|
|
request->req_last_xcp.xcp_type = xcp_gds_code;
|
|
request->req_last_xcp.xcp_code = status_vector[1];
|
|
}
|
|
else
|
|
{
|
|
request->req_last_xcp.xcp_type = xcp_xcp_code;
|
|
request->req_last_xcp.xcp_code = status_vector[3];
|
|
TEXT *msg = reinterpret_cast<TEXT*>(status_vector[7]);
|
|
assign_xcp_message(tdbb, &request->req_last_xcp.xcp_msg, msg);
|
|
}
|
|
}
|
|
status_vector[0] = 0;
|
|
status_vector[1] = 0;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static void trigger_failure(TDBB tdbb, REQ trigger)
|
|
{
|
|
/**************************************
|
|
*
|
|
* t r i g g e r _ f a i l u r e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Trigger failed, report error.
|
|
*
|
|
**************************************/
|
|
|
|
SET_TDBB(tdbb);
|
|
EXE_unwind(tdbb, trigger);
|
|
|
|
trigger->req_attachment = NULL;
|
|
trigger->req_flags &= ~req_in_use;
|
|
trigger->req_timestamp = 0;
|
|
|
|
if (trigger->req_flags & req_leave)
|
|
{
|
|
trigger->req_flags &= ~req_leave;
|
|
const TEXT* msg;
|
|
if (trigger->req_trg_name &&
|
|
(msg = MET_trigger_msg(tdbb,
|
|
trigger->req_trg_name,
|
|
trigger->req_label)))
|
|
{
|
|
if (trigger->req_flags & req_sys_trigger)
|
|
{
|
|
STATUS code = PAR_symbol_to_gdscode(msg);
|
|
if (code)
|
|
{
|
|
ERR_post(gds_integ_fail,
|
|
gds_arg_number, (SLONG) trigger->req_label,
|
|
gds_arg_gds, code, 0);
|
|
}
|
|
}
|
|
ERR_post(gds_integ_fail,
|
|
gds_arg_number, (SLONG) trigger->req_label,
|
|
gds_arg_gds, gds_random, gds_arg_string, msg, 0);
|
|
}
|
|
else
|
|
{
|
|
ERR_post(gds_integ_fail, gds_arg_number,
|
|
(SLONG) trigger->req_label, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ERR_punt();
|
|
}
|
|
}
|
|
|
|
|
|
static void validate(TDBB tdbb, JRD_NOD list)
|
|
{
|
|
/**************************************
|
|
*
|
|
* v a l i d a t e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Execute a list of validation expressions.
|
|
*
|
|
**************************************/
|
|
|
|
SET_TDBB(tdbb);
|
|
BLKCHK(list, type_nod);
|
|
|
|
JRD_NOD *ptr1, *ptr2;
|
|
|
|
for (ptr1 = list->nod_arg, ptr2 = ptr1 + list->nod_count;
|
|
ptr1 < ptr2; ptr1++)
|
|
{
|
|
if (!EVL_boolean(tdbb, (*ptr1)->nod_arg[e_val_boolean]))
|
|
{
|
|
/* Validation error -- report result */
|
|
|
|
JRD_NOD node;
|
|
VEC vector;
|
|
REL relation;
|
|
REQ request;
|
|
FLD field;
|
|
const char* value;
|
|
TEXT temp[128];
|
|
CONST TEXT* name;
|
|
USHORT length, stream, id;
|
|
|
|
node = (*ptr1)->nod_arg[e_val_value];
|
|
request = tdbb->tdbb_request;
|
|
length = MOV_make_string(EVL_expr(tdbb, node),
|
|
ttype_dynamic,
|
|
&value,
|
|
reinterpret_cast<VARY*>(temp),
|
|
sizeof(temp));
|
|
|
|
if (request->req_flags & req_null ||
|
|
request->req_flags & req_clone_data_from_default_clause)
|
|
{
|
|
value = "*** null ***";
|
|
}
|
|
else if (!length)
|
|
{
|
|
value = "";
|
|
}
|
|
else
|
|
{
|
|
value = ERR_string(value, length);
|
|
}
|
|
|
|
if (node->nod_type == nod_field)
|
|
{
|
|
stream = (USHORT) node->nod_arg[e_fld_stream];
|
|
id = (USHORT) node->nod_arg[e_fld_id];
|
|
relation = request->req_rpb[stream].rpb_relation;
|
|
|
|
if ((vector = relation->rel_fields) &&
|
|
id < vector->count() &&
|
|
(field = (FLD) (*vector)[id]))
|
|
{
|
|
name = field->fld_name;
|
|
}
|
|
}
|
|
|
|
if (!name)
|
|
{
|
|
name = "*** unknown ***";
|
|
}
|
|
|
|
ERR_post(gds_not_valid, gds_arg_string, name,
|
|
gds_arg_string, value, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // extern "C"
|