8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-26 10:43:03 +01:00
firebird-mirror/src/jrd/exe.cpp
2009-02-05 20:41:47 +00:00

4167 lines
110 KiB
C++

/*
* PROGRAM: JRD Access Method
* MODULE: exe.cpp
* 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
* 2003.10.05 Dmitry Yemanov: Added support for explicit cursors in PSQL
* Adriano dos Santos Fernandes
*
*/
#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/extds/ExtDS.h"
#include "../jrd/tra.h"
#include "gen/iberror.h"
#include "../jrd/ods.h"
#include "../jrd/btr.h"
#include "../jrd/rse.h"
#include "../jrd/lck.h"
#include "../jrd/intl.h"
#include "../jrd/sbm.h"
#include "../jrd/blb.h"
#include "../jrd/blr.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/intl_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/tra_proto.h"
#include "../jrd/vio_proto.h"
#include "../jrd/isc_s_proto.h"
#include "../jrd/execute_statement.h"
#include "../dsql/dsql_proto.h"
#include "../jrd/rpb_chain.h"
#include "../jrd/VirtualTable.h"
#include "../jrd/trace/TraceManager.h"
#include "../jrd/trace/TraceJrdHelpers.h"
#include "../dsql/Nodes.h"
using namespace Jrd;
using namespace Firebird;
// AffectedRows class implementation
AffectedRows::AffectedRows()
{
clear();
}
void AffectedRows::clear()
{
writeFlag = false;
fetchedRows = modifiedRows = 0;
}
void AffectedRows::bumpFetched()
{
fetchedRows++;
}
void AffectedRows::bumpModified(bool increment)
{
if (increment) {
modifiedRows++;
}
else {
writeFlag = true;
}
}
bool AffectedRows::isReadOnly() const
{
return !writeFlag;
}
bool AffectedRows::hasCursor() const
{
return (fetchedRows > 0);
}
int AffectedRows::getCount() const
{
return writeFlag ? modifiedRows : fetchedRows;
}
// StatusXcp class implementation
StatusXcp::StatusXcp()
{
clear();
}
void StatusXcp::clear()
{
fb_utils::init_status(status);
}
void StatusXcp::init(const ISC_STATUS* vector)
{
memcpy(status, vector, sizeof(ISC_STATUS_ARRAY));
}
void StatusXcp::copyTo(ISC_STATUS* vector) const
{
memcpy(vector, status, sizeof(ISC_STATUS_ARRAY));
}
bool StatusXcp::success() const
{
return (status[1] == FB_SUCCESS);
}
SLONG StatusXcp::as_gdscode() const
{
return status[1];
}
SLONG StatusXcp::as_sqlcode() const
{
return gds__sqlcode(status);
}
static void cleanup_rpb(thread_db*, record_param*);
static jrd_nod* erase(thread_db*, jrd_nod*, SSHORT);
static void execute_looper(thread_db*, jrd_req*, jrd_tra*, jrd_req::req_s);
static void exec_sql(thread_db*, jrd_req*, DSC *);
static void execute_procedure(thread_db*, jrd_nod*);
static jrd_nod* execute_statement(thread_db*, jrd_req*, jrd_nod*);
static jrd_req* execute_triggers(thread_db*, trig_vec**, record_param*, record_param*,
jrd_req::req_ta, SSHORT);
static void get_string(thread_db*, jrd_req*, jrd_nod*, Firebird::string&);
static void looper_seh(thread_db*, jrd_req*, jrd_nod*);
static jrd_nod* modify(thread_db*, jrd_nod*, SSHORT);
static jrd_nod* receive_msg(thread_db*, jrd_nod*);
static void release_blobs(thread_db*, jrd_req*);
static void release_proc_save_points(jrd_req*);
#ifdef SCROLLABLE_CURSORS
static jrd_nod* seek_rse(thread_db*, jrd_req*, jrd_nod*);
static void seek_rsb(thread_db*, jrd_req*, RecordSource*, USHORT, SLONG);
#endif
static jrd_nod* selct(thread_db*, jrd_nod*);
static jrd_nod* send_msg(thread_db*, jrd_nod*);
static void set_error(thread_db*, const xcp_repeat*, jrd_nod*);
static jrd_nod* stall(thread_db*, jrd_nod*);
static jrd_nod* store(thread_db*, jrd_nod*, SSHORT);
static bool test_and_fixup_error(thread_db*, const PsqlException*, jrd_req*);
static void trigger_failure(thread_db*, jrd_req*);
static void validate(thread_db*, jrd_nod*);
inline void verb_cleanup(thread_db*, jrd_tra*);
inline void PreModifyEraseTriggers(thread_db*, trig_vec**, SSHORT, record_param*,
record_param*, jrd_req::req_ta);
static void stuff_stack_trace(const jrd_req*);
/* macro definitions */
#if (defined SUPERSERVER) && (defined WIN_NT || defined SOLARIS_MT)
const int MAX_CLONES = 750;
#elif defined (HP10) && defined (SUPERSERVER)
const int MAX_CLONES = 110;
#else
const int MAX_CLONES = 1000;
#endif
const int ALL_TRIGS = 0;
const int PRE_TRIG = 1;
const int POST_TRIG = 2;
const size_t MAX_STACK_TRACE = 2048;
/* 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 */
const int RECORD_LOCK_CHECK_INTERVAL = 10;
void EXE_assignment(thread_db* tdbb, jrd_nod* node)
{
/**************************************
*
* E X E _ a s s i g n m e n t
*
**************************************
*
* Functional description
* Perform an assignment
*
**************************************/
DEV_BLKCHK(node, type_nod);
SET_TDBB(tdbb);
jrd_req* request = tdbb->getRequest();
BLKCHK(node, type_nod);
// Get descriptors of src field/parameter/variable, etc.
request->req_flags &= ~req_null;
dsc* from_desc = EVL_expr(tdbb, node->nod_arg[e_asgn_from]);
EXE_assignment(tdbb, node->nod_arg[e_asgn_to], from_desc, (request->req_flags & req_null),
node->nod_arg[e_asgn_missing], node->nod_arg[e_asgn_missing2]);
request->req_operation = jrd_req::req_return;
}
void EXE_assignment(thread_db* tdbb, jrd_nod* to, dsc* from_desc, bool from_null,
jrd_nod* missing_node, jrd_nod* missing2_node)
{
/**************************************
*
* E X E _ a s s i g n m e n t
*
**************************************
*
* Functional description
* Perform an assignment
*
**************************************/
DEV_BLKCHK(node, type_nod);
SET_TDBB(tdbb);
jrd_req* request = tdbb->getRequest();
// Get descriptors of receiving and sending fields/parameters, variables, etc.
dsc* missing = NULL;
if (missing_node) {
missing = EVL_expr(tdbb, missing_node);
}
// Get descriptor of target field/parameter/variable, etc.
DSC* to_desc = EVL_assign_to(tdbb, to);
request->req_flags &= ~req_null;
// NS: If we are assigning to NULL, we finished.
// This functionality is currently used to allow calling UDF routines
// without assigning resulting value anywhere.
if (!to_desc)
return;
SSHORT null = from_null ? -1 : 0;
if (!null && missing && MOV_compare(missing, from_desc) == 0) {
null = -1;
}
USHORT* impure_flags = NULL;
switch (to->nod_type)
{
case nod_variable:
if (to->nod_arg[e_var_info])
{
EVL_validate(tdbb,
Item(nod_variable, (IPTR) to->nod_arg[e_var_id]),
reinterpret_cast<const ItemInfo*>(to->nod_arg[e_var_info]),
from_desc, null == -1);
}
impure_flags = &((impure_value*) ((SCHAR *) request +
to->nod_arg[e_var_variable]->nod_impure))->vlu_flags;
break;
case nod_argument:
if (to->nod_arg[e_arg_info])
{
EVL_validate(tdbb,
Item(nod_argument, (IPTR) to->nod_arg[e_arg_message]->nod_arg[e_msg_number],
(IPTR) to->nod_arg[e_arg_number]),
reinterpret_cast<const ItemInfo*>(to->nod_arg[e_arg_info]),
from_desc, null == -1);
}
impure_flags = (USHORT*) ((UCHAR *) request +
(IPTR) to->nod_arg[e_arg_message]->nod_arg[e_msg_impure_flags] +
(sizeof(USHORT) * (IPTR) to->nod_arg[e_arg_number]));
break;
}
if (impure_flags != NULL)
*impure_flags |= VLU_checked;
// If the value is non-missing, move/convert it. Otherwise fill the
// field with appropriate nulls.
dsc temp;
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(tdbb, &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;
}
}
// Validate range for datetime values
if (DTYPE_IS_DATE(from_desc->dsc_dtype))
{
switch (from_desc->dsc_dtype)
{
case dtype_sql_date:
if (!Firebird::TimeStamp::isValidDate(*(GDS_DATE*) from_desc->dsc_address))
{
ERR_post(Arg::Gds(isc_date_range_exceeded));
}
break;
case dtype_sql_time:
if (!Firebird::TimeStamp::isValidTime(*(GDS_TIME*) from_desc->dsc_address))
{
ERR_post(Arg::Gds(isc_time_range_exceeded));
}
break;
case dtype_timestamp:
if (!Firebird::TimeStamp::isValidTimeStamp(*(GDS_TIMESTAMP*) from_desc->dsc_address))
{
ERR_post(Arg::Gds(isc_datetime_range_exceeded));
}
break;
default:
fb_assert(false);
}
}
if (DTYPE_IS_BLOB_OR_QUAD(from_desc->dsc_dtype) || DTYPE_IS_BLOB_OR_QUAD(to_desc->dsc_dtype))
{
// ASF: Don't let MOV_move call BLB_move because MOV
// will not pass the destination field to BLB_move.
BLB_move(tdbb, from_desc, to_desc, to);
}
else if (!DSC_EQUIV(from_desc, to_desc, false))
MOV_move(tdbb, 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
memcpy(to_desc->dsc_address, from_desc->dsc_address, from_desc->dsc_length);
to_desc->dsc_flags &= ~DSC_null;
}
else if (missing2_node && (missing = EVL_expr(tdbb, missing2_node)))
{
MOV_move(tdbb, missing, to_desc);
to_desc->dsc_flags |= DSC_null;
}
else
{
USHORT l = to_desc->dsc_length;
UCHAR* 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) {
const CHARSET_ID chid = DSC_GET_CHARSET(to_desc);
/*
CVC: I don't know if we have to check for dynamic-127 charset here.
If that is needed, the line above should be replaced by the commented code here.
CHARSET_ID chid = INTL_TTYPE(to_desc);
if (chid == ttype_dynamic)
chid = INTL_charset(tdbb, chid);
*/
const char pad = chid == ttype_binary ? '\0' : ' ';
memset(p, pad, l);
}
break;
case dtype_cstring:
*p = 0;
break;
case dtype_varying:
*(SSHORT *) p = 0;
break;
default:
memset(p, 0, 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)
{
const SSHORT id = (USHORT)(IPTR) to->nod_arg[e_fld_id];
Record* record = request->req_rpb[(int) (IPTR) 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)
{
USHORT minlen;
switch (to_desc->dsc_dtype)
{
case dtype_text:
minlen = 1;
break;
case dtype_cstring:
minlen = 2;
break;
case dtype_varying:
minlen = 3;
break;
}
if (to_desc->dsc_length <= minlen)
{
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(tdbb, &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(tdbb, &temp, to_desc);
}
}
}
void EXE_execute_db_triggers(thread_db* tdbb, jrd_tra* transaction, jrd_req::req_ta trigger_action)
{
/**************************************
*
* E X E _ e x e c u t e _ d b _ t r i g g e r s
*
**************************************
*
* Functional description
* Execute database triggers
*
**************************************/
// do nothing if user doesn't want database triggers
if (tdbb->getAttachment()->att_flags & ATT_no_db_triggers)
return;
int type = 0;
switch (trigger_action)
{
case jrd_req::req_trigger_connect:
type = DB_TRIGGER_CONNECT;
break;
case jrd_req::req_trigger_disconnect:
type = DB_TRIGGER_DISCONNECT;
break;
case jrd_req::req_trigger_trans_start:
type = DB_TRIGGER_TRANS_START;
break;
case jrd_req::req_trigger_trans_commit:
type = DB_TRIGGER_TRANS_COMMIT;
break;
case jrd_req::req_trigger_trans_rollback:
type = DB_TRIGGER_TRANS_ROLLBACK;
break;
default:
fb_assert(false);
return;
}
jrd_req* trigger = NULL;
if (tdbb->getDatabase()->dbb_triggers[type])
{
jrd_tra* old_transaction = tdbb->getTransaction();
tdbb->setTransaction(transaction);
try
{
trigger = execute_triggers(tdbb, &tdbb->getDatabase()->dbb_triggers[type],
NULL, NULL, trigger_action, ALL_TRIGS);
tdbb->setTransaction(old_transaction);
}
catch (...)
{
tdbb->setTransaction(old_transaction);
throw;
}
}
if (trigger)
trigger_failure(tdbb, trigger);
}
jrd_req* EXE_find_request(thread_db* tdbb, jrd_req* request, bool 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.
*
**************************************/
DEV_BLKCHK(request, type_req);
SET_TDBB(tdbb);
Database* const dbb = tdbb->getDatabase();
Attachment* const attachment = tdbb->getAttachment();
/* 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 */
Database::CheckoutLockGuard guard(dbb, dbb->dbb_exe_clone_mutex);
jrd_req* clone = NULL;
USHORT count = 0;
if (!(request->req_flags & req_in_use))
clone = request;
else
{
if (request->req_attachment == attachment)
count++;
/* Request exists and is in use. Search clones for one in use by
this attachment. If not found, return first inactive request. */
vec<jrd_req*>* vector = request->req_sub_requests;
const USHORT clones = vector ? (vector->count() - 1) : 0;
USHORT n;
for (n = 1; n <= clones; n++)
{
jrd_req* next = CMP_clone_request(tdbb, request, n, validate);
if (next->req_attachment == attachment) {
if (!(next->req_flags & req_in_use)) {
clone = next;
break;
}
count++;
}
else if (!(next->req_flags & req_in_use) && !clone)
clone = next;
}
if (count > MAX_CLONES) {
ERR_post(Arg::Gds(isc_req_max_clones_exceeded));
}
if (!clone)
clone = CMP_clone_request(tdbb, request, n, validate);
}
clone->req_attachment = attachment;
clone->req_stats.reset();
clone->req_base_stats.reset();
clone->req_flags |= req_in_use;
return clone;
}
void EXE_receive(thread_db* tdbb,
jrd_req* request,
USHORT msg,
USHORT length,
UCHAR* buffer,
bool top_level)
{
/**************************************
*
* 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.
*
**************************************/
SET_TDBB(tdbb);
DEV_BLKCHK(request, type_req);
if (--tdbb->tdbb_quantum < 0)
JRD_reschedule(tdbb, 0, true);
jrd_tra* transaction = request->req_transaction;
if (!(request->req_flags & req_active)) {
ERR_post(Arg::Gds(isc_req_sync));
}
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. */
Savepoint* const 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, jrd_req::req_sync);
if (!(request->req_flags & req_active) || request->req_operation != jrd_req::req_send)
{
ERR_post(Arg::Gds(isc_req_sync));
}
const jrd_nod* message = request->req_message;
const Format* format = (Format*) message->nod_arg[e_msg_format];
if (msg != (USHORT)(IPTR) message->nod_arg[e_msg_number])
ERR_post(Arg::Gds(isc_req_sync));
if (length != format->fmt_length) {
ERR_post(Arg::Gds(isc_port_len) << Arg::Num(length) << Arg::Num(format->fmt_length));
}
memcpy(buffer, (SCHAR*) request + message->nod_impure, length);
// ASF: temporary blobs returned to the client should not be released
// with the request, but in the transaction end.
if (top_level)
{
for (int i = 0; i < format->fmt_count; ++i)
{
const DSC* desc = &format->fmt_desc[i];
if (desc->isBlob())
{
const bid* id = (bid*)
((UCHAR*)request + message->nod_impure + (ULONG)(IPTR)desc->dsc_address);
if (transaction->tra_blobs->locate(id->bid_temp_id()))
{
BlobIndex* current = &transaction->tra_blobs->current();
if (current->bli_request &&
current->bli_request->req_blobs.locate(id->bid_temp_id()))
{
current->bli_request->req_blobs.fastRemove();
current->bli_request = NULL;
}
}
}
}
}
execute_looper(tdbb, request, transaction, jrd_req::req_proceed);
} //try
catch (const Firebird::Exception&)
{
if (request->req_flags & req_proc_fetch)
{
Savepoint* const 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);
}
throw;
}
if (request->req_flags & req_proc_fetch) {
Savepoint* const 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(thread_db* tdbb, jrd_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.
*
**************************************/
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 */
/* find the top-level rsb in the request and seek it */
for (SLONG i = request->req_fors.getCount() - 1; i >= 0; i--)
{
RecordSource* rsb = request->req_fors[i];
if (rsb) {
seek_rsb(tdbb, request, rsb, direction, offset);
break;
}
}
}
#endif
void EXE_send(thread_db* tdbb,
jrd_req* request,
USHORT msg,
USHORT length,
const 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.
*
**************************************/
SET_TDBB(tdbb);
DEV_BLKCHK(request, type_req);
if (--tdbb->tdbb_quantum < 0)
JRD_reschedule(tdbb, 0, true);
if (!(request->req_flags & req_active))
ERR_post(Arg::Gds(isc_req_sync));
jrd_nod* message;
jrd_nod* node;
#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 */
jrd_nod* save_next = NULL;
jrd_nod* save_message = NULL;
jrd_req::req_s save_operation = jrd_req::req_evaluate;
if ((message = request->req_async_message) && (node = message->nod_arg[e_send_message]) &&
(msg == (USHORT)(ULONG) 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 = jrd_req::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 != jrd_req::req_receive)
ERR_post(Arg::Gds(isc_req_sync));
node = request->req_message;
#ifdef SCROLLABLE_CURSORS
}
#endif
jrd_tra* transaction = request->req_transaction;
switch (node->nod_type)
{
case nod_message:
message = node;
break;
case nod_select:
{
jrd_nod** ptr = node->nod_arg;
for (const jrd_nod* const* const end = ptr + node->nod_count; ptr < end; ptr++)
{
message = (*ptr)->nod_arg[e_send_message];
if ((USHORT)(IPTR) message->nod_arg[e_msg_number] == msg) {
request->req_next = *ptr;
break;
}
}
}
break;
default:
BUGCHECK(167); /* msg 167 invalid SEND request */
}
const Format* format = (Format*) message->nod_arg[e_msg_format];
if (msg != (USHORT)(IPTR) message->nod_arg[e_msg_number])
ERR_post(Arg::Gds(isc_req_sync));
if (length != format->fmt_length) {
ERR_post(Arg::Gds(isc_port_len) << Arg::Num(length) << Arg::Num(format->fmt_length));
}
memcpy((SCHAR*) request + message->nod_impure, buffer, length);
for (USHORT i = 0; i < format->fmt_count; ++i)
{
const DSC* desc = &format->fmt_desc[i];
// ASF: I'll not test for dtype_cstring because usage is only internal
if (desc->dsc_dtype == dtype_text || desc->dsc_dtype == dtype_varying)
{
const UCHAR* p = (UCHAR*)request + message->nod_impure + (ULONG)(IPTR)desc->dsc_address;
USHORT len;
switch (desc->dsc_dtype)
{
case dtype_text:
len = desc->dsc_length;
break;
case dtype_varying:
len = reinterpret_cast<const vary*>(p)->vary_length;
p += sizeof(USHORT);
break;
}
CharSet* charSet = INTL_charset_lookup(tdbb, DSC_GET_CHARSET(desc));
if (!charSet->wellFormed(len, p))
ERR_post(Arg::Gds(isc_malformed_string));
}
else if (desc->isBlob())
{
if (desc->getCharSet() != CS_NONE && desc->getCharSet() != CS_BINARY)
{
const Jrd::bid* bid = (Jrd::bid*) ((UCHAR*)request +
message->nod_impure + (ULONG)(IPTR)desc->dsc_address);
if (!bid->isEmpty())
{
AutoBlb blob(tdbb, BLB_open(tdbb, transaction/*tdbb->getTransaction()*/, bid));
BLB_check_well_formed(tdbb, desc, blob.getBlb());
}
}
}
}
execute_looper(tdbb, request, transaction, jrd_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(thread_db* tdbb, jrd_req* request, jrd_tra* transaction)
{
/**************************************
*
* E X E _ s t a r t
*
**************************************
*
* Functional description
* Start an execution running.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
BLKCHK(request, type_req);
BLKCHK(transaction, type_tra);
if (request->req_flags & req_active)
ERR_post(Arg::Gds(isc_req_sync) << Arg::Gds(isc_reqinuse));
if (transaction->tra_flags & TRA_prepared)
ERR_post(Arg::Gds(isc_req_no_trans));
/* 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);
Lock* lock = transaction->tra_cancel_lock;
if (lock && lock->lck_logical == LCK_none)
LCK_lock(tdbb, lock, LCK_SR, LCK_WAIT);
TRA_attach_request(transaction, request);
request->req_flags &= REQ_FLAGS_INIT_MASK;
request->req_flags |= req_active;
request->req_flags &= ~req_reserved;
request->req_operation = jrd_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;
request->req_records_affected.clear();
/* 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
request->req_timestamp.validate();
// Set all invariants to not computed.
jrd_nod **ptr, **end;
for (ptr = request->req_invariants.begin(), end = request->req_invariants.end(); ptr < end; ++ptr)
{
impure_value* impure = (impure_value*) ((SCHAR *) request + (*ptr)->nod_impure);
impure->vlu_flags = 0;
}
if (request->req_sql_text)
{
tdbb->bumpStats(RuntimeStatistics::STMT_EXECUTES);
}
// Start a save point if not in middle of one
if (transaction && (transaction != dbb->dbb_sys_trans)) {
VIO_start_save_point(tdbb, transaction);
}
request->req_src_line = 0;
request->req_src_column = 0;
looper_seh(tdbb, request, request->req_top_node);
// 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(thread_db* tdbb, jrd_req* request)
{
/**************************************
*
* E X E _ u n w i n d
*
**************************************
*
* Functional description
* Unwind a request, maybe active, maybe not. This is particularly
* simple since nothing really needs to be done.
*
**************************************/
DEV_BLKCHK(request, type_req);
SET_TDBB(tdbb);
if (request->req_flags & req_active)
{
if (request->req_fors.getCount() || request->req_exec_sta.getCount() || request->req_ext_stmt)
{
Jrd::ContextPoolHolder context(tdbb, request->req_pool);
jrd_req* old_request = tdbb->getRequest();
jrd_tra* old_transaction = tdbb->getTransaction();
try {
tdbb->setRequest(request);
tdbb->setTransaction(request->req_transaction);
RecordSource** ptr = request->req_fors.begin();
for (const RecordSource* const* const end = request->req_fors.end(); ptr < end; ptr++)
{
if (*ptr)
RSE_close(tdbb, *ptr);
}
for (size_t i = 0; i < request->req_exec_sta.getCount(); ++i)
{
jrd_nod* node = request->req_exec_sta[i];
ExecuteStatement* impure =(ExecuteStatement*) ((char*) request + node->nod_impure);
impure->close(tdbb);
}
while (request->req_ext_stmt) {
request->req_ext_stmt->close(tdbb);
}
}
catch (const Firebird::Exception&)
{
tdbb->setRequest(old_request);
tdbb->setTransaction(old_transaction);
throw;
}
tdbb->setRequest(old_request);
tdbb->setTransaction(old_transaction);
}
release_blobs(tdbb, request);
}
if (request->req_proc_sav_point && (request->req_flags & req_proc_fetch))
release_proc_save_points(request);
TRA_detach_request(request);
request->req_flags &= ~(req_active | req_proc_fetch | req_reserved);
request->req_flags |= req_abort | req_stall;
request->req_timestamp.invalidate();
request->req_proc_inputs = NULL;
request->req_proc_caller = NULL;
}
/* CVC: Moved to its own routine, originally in store(). */
static void cleanup_rpb(thread_db* tdbb, record_param* 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.
*
**************************************/
Record* record = rpb->rpb_record;
const Format* format = record->rec_format;
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 (USHORT n = 0; n < format->fmt_count; n++)
{
const dsc* desc = &format->fmt_desc[n];
if (!desc->dsc_address)
continue;
UCHAR* const p = record->rec_data + (IPTR) desc->dsc_address;
if (TEST_NULL(record, n))
{
USHORT length = desc->dsc_length;
if (length) {
memset(p, 0, length);
}
}
else if (desc->dsc_dtype == dtype_varying)
{
vary* varying = reinterpret_cast<vary*>(p);
USHORT length = desc->dsc_length - sizeof(USHORT);
if (length > varying->vary_length)
{
char* trail = varying->vary_string + varying->vary_length;
length -= varying->vary_length;
memset(trail, 0, length);
}
}
}
}
inline void PreModifyEraseTriggers(thread_db* tdbb,
trig_vec** trigs,
SSHORT which_trig,
record_param* rpb,
record_param* rec,
jrd_req::req_ta op)
{
/******************************************************
*
* P r e M o d i f y E r a s e T r i g g e r s
*
******************************************************
*
* Functional description
* Perform operation's pre-triggers,
* storing active rpb in chain.
*
******************************************************/
if (! tdbb->getTransaction()->tra_rpblist) {
tdbb->getTransaction()->tra_rpblist =
FB_NEW(*tdbb->getTransaction()->tra_pool) traRpbList(*tdbb->getTransaction()->tra_pool);
}
const int rpblevel = tdbb->getTransaction()->tra_rpblist->PushRpb(rpb);
jrd_req* trigger = NULL;
if ((*trigs) && (which_trig != POST_TRIG)) {
trigger = execute_triggers(tdbb, trigs, rpb, rec, op, PRE_TRIG);
}
tdbb->getTransaction()->tra_rpblist->PopRpb(rpb, rpblevel);
if (trigger) {
trigger_failure(tdbb, trigger);
}
}
static jrd_nod* erase(thread_db* tdbb, jrd_nod* node, SSHORT which_trig)
{
/**************************************
*
* e r a s e
*
**************************************
*
* Functional description
* Perform erase operation.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
BLKCHK(node, type_nod);
jrd_req* request = tdbb->getRequest();
jrd_tra* transaction = request->req_transaction;
record_param* rpb = &request->req_rpb[(int) (IPTR) node->nod_arg[e_erase_stream]];
jrd_rel* relation = rpb->rpb_relation;
if (rpb->rpb_number.isBof() || (!relation->rel_view_rse && !rpb->rpb_number.isValid()))
{
ERR_post(Arg::Gds(isc_no_cur_rec));
}
switch (request->req_operation)
{
case jrd_req::req_evaluate:
{
request->req_records_affected.bumpModified(false);
if (!node->nod_arg[e_erase_statement])
break;
const Format* format = MET_current(tdbb, rpb->rpb_relation);
Record* record = VIO_record(tdbb, rpb, format, tdbb->getDefaultPool());
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 jrd_req::req_return:
break;
default:
return node->nod_parent;
}
request->req_operation = jrd_req::req_return;
RLCK_reserve_relation(tdbb, transaction, relation, 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) {
VIO_refetch_record(tdbb, rpb, transaction);
rpb->rpb_stream_flags &= ~RPB_s_refetch;
}
if (transaction != dbb->dbb_sys_trans)
++transaction->tra_save_point->sav_verb_count;
/* Handle pre-operation trigger */
PreModifyEraseTriggers(tdbb, &relation->rel_pre_erase, which_trig, rpb, NULL,
jrd_req::req_trigger_delete);
if (relation->rel_file) {
EXT_erase(rpb, transaction);
}
else if (relation->isVirtual()) {
VirtualTable::erase(tdbb, rpb);
}
else if (!relation->rel_view_rse) {
VIO_erase(tdbb, rpb, transaction);
}
/* Handle post operation trigger */
jrd_req* trigger;
if (relation->rel_post_erase && which_trig != PRE_TRIG &&
(trigger = execute_triggers(tdbb, &relation->rel_post_erase,
rpb, NULL, jrd_req::req_trigger_delete, POST_TRIG)))
{
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 && !relation->isVirtual())
{
jrd_rel* bad_relation = 0;
USHORT bad_index;
const 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.bumpModified(true);
}
}
else if (relation->rel_file || !relation->rel_view_rse) {
request->req_records_deleted++;
request->req_records_affected.bumpModified(true);
}
if (transaction != dbb->dbb_sys_trans) {
--transaction->tra_save_point->sav_verb_count;
}
rpb->rpb_number.setValid(false);
return node->nod_parent;
}
static void execute_looper(thread_db* tdbb,
jrd_req* request,
jrd_tra* transaction, jrd_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.
*
**************************************/
DEV_BLKCHK(request, type_req);
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
/* 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;
EXE_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(thread_db* tdbb, jrd_req* request, DSC* dsc)
{
/**************************************
*
* e x e c _ s q l
*
**************************************
*
* Functional description
* Execute a string as SQL operator.
*
**************************************/
SET_TDBB(tdbb);
ExecuteStatement::execute(tdbb, request, dsc);
}
static void execute_procedure(thread_db* 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.
*
**************************************/
SET_TDBB(tdbb);
BLKCHK(node, type_nod);
jrd_req* request = tdbb->getRequest();
jrd_nod* temp = node->nod_arg[e_esp_inputs];
if (temp) {
jrd_nod** ptr;
jrd_nod** end;
for (ptr = temp->nod_arg, end = ptr + temp->nod_count; ptr < end; ptr++)
{
EXE_assignment(tdbb, *ptr);
}
}
USHORT in_msg_length = 0;
UCHAR* in_msg = NULL;
jrd_nod* in_message = node->nod_arg[e_esp_in_msg];
if (in_message) {
const Format* format = (Format*) in_message->nod_arg[e_msg_format];
in_msg_length = format->fmt_length;
in_msg = (UCHAR*) request + in_message->nod_impure;
}
USHORT out_msg_length = 0;
UCHAR* out_msg = NULL;
jrd_nod* out_message = node->nod_arg[e_esp_out_msg];
if (out_message) {
const Format* format = (Format*) out_message->nod_arg[e_msg_format];
out_msg_length = format->fmt_length;
out_msg = (UCHAR*) request + out_message->nod_impure;
}
jrd_prc* procedure = (jrd_prc*) node->nod_arg[e_esp_procedure];
jrd_req* proc_request = EXE_find_request(tdbb, procedure->prc_request, false);
// trace procedure execution start
TraceProcExecute trace(tdbb, proc_request, request, node->nod_arg[e_esp_inputs]);
Firebird::Array<UCHAR> temp_buffer;
if (!out_message) {
const Format* format = (Format*) procedure->prc_output_msg->nod_arg[e_msg_format];
out_msg_length = format->fmt_length;
out_msg = temp_buffer.getBuffer(out_msg_length + FB_DOUBLE_ALIGN - 1);
out_msg = (UCHAR*) FB_ALIGN((U_IPTR) out_msg, FB_DOUBLE_ALIGN);
}
/* Catch errors so we can unwind cleanly */
try {
// Save the old pool
Jrd::ContextPoolHolder context(tdbb, proc_request->req_pool);
jrd_tra* transaction = request->req_transaction;
const SLONG 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, in_msg);
}
EXE_receive(tdbb, proc_request, 1, out_msg_length, out_msg);
/* Clean up all savepoints started during execution of the
procedure */
if (transaction != tdbb->getDatabase()->dbb_sys_trans) {
for (const Savepoint* 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 (const Firebird::Exception& ex)
{
const bool no_priv = (ex.stuff_exception(tdbb->tdbb_status_vector) == isc_no_priv);
trace.finish(false, no_priv ? res_unauthorized : res_failed);
tdbb->setRequest(request);
EXE_unwind(tdbb, proc_request);
proc_request->req_attachment = NULL;
proc_request->req_flags &= ~(req_in_use | req_proc_fetch);
proc_request->req_timestamp.invalidate();
throw;
}
// trace procedure execution finish
trace.finish(false, res_successful);
EXE_unwind(tdbb, proc_request);
tdbb->setRequest(request);
temp = node->nod_arg[e_esp_outputs];
if (temp) {
jrd_nod** ptr;
jrd_nod** end;
for (ptr = temp->nod_arg, end = ptr + temp->nod_count; ptr < end; ptr++)
{
EXE_assignment(tdbb, *ptr);
}
}
proc_request->req_attachment = NULL;
proc_request->req_flags &= ~(req_in_use | req_proc_fetch);
proc_request->req_timestamp.invalidate();
}
static jrd_nod* execute_statement(thread_db* tdbb, jrd_req* request, jrd_nod* node)
{
SET_TDBB(tdbb);
BLKCHK(node, type_nod);
EDS::Statement** stmt_ptr = (EDS::Statement**) ((char*) request + node->nod_impure);
EDS::Statement* stmt = *stmt_ptr;
const int inputs = (SSHORT)(IPTR) node->nod_arg[node->nod_count + e_exec_stmt_extra_inputs];
const int outputs = (SSHORT)(IPTR) node->nod_arg[node->nod_count + e_exec_stmt_extra_outputs];
jrd_nod** node_inputs = inputs ?
node->nod_arg + e_exec_stmt_fixed_count + e_exec_stmt_extra_inputs : NULL;
jrd_nod** node_outputs = outputs ?
node->nod_arg + e_exec_stmt_fixed_count + inputs : NULL;
jrd_nod* node_proc_block = node->nod_arg[e_exec_stmt_proc_block];
if (request->req_operation == jrd_req::req_evaluate)
{
fb_assert(*stmt_ptr == 0);
const EDS::ParamNames* inputs_names =
(EDS::ParamNames*) node->nod_arg[node->nod_count + e_exec_stmt_extra_input_names];
const jrd_nod* tra_node = node->nod_arg[node->nod_count + e_exec_stmt_extra_tran];
const EDS::TraScope tra_scope = tra_node ? (EDS::TraScope)(IPTR) tra_node : EDS::traCommon;
const jrd_nod* privs_node = node->nod_arg[node->nod_count + e_exec_stmt_extra_privs];
const bool caller_privs = (privs_node != NULL);
Firebird::string sSql;
get_string(tdbb, request, node->nod_arg[e_exec_stmt_stmt_sql], sSql);
Firebird::string sDataSrc;
get_string(tdbb, request, node->nod_arg[e_exec_stmt_data_src], sDataSrc);
Firebird::string sUser;
get_string(tdbb, request, node->nod_arg[e_exec_stmt_user], sUser);
Firebird::string sPwd;
get_string(tdbb, request, node->nod_arg[e_exec_stmt_password], sPwd);
EDS::Connection* conn = EDS::Manager::getConnection(tdbb, sDataSrc, sUser, sPwd, tra_scope);
stmt = conn->createStatement(sSql);
EDS::Transaction* tran = EDS::Transaction::getTransaction(tdbb, stmt->getConnection(), tra_scope);
stmt->bindToRequest(request, stmt_ptr);
stmt->setCallerPrivileges(caller_privs);
const Firebird::string* const * inp_names = inputs_names ? inputs_names->begin() : NULL;
stmt->prepare(tdbb, tran, sSql, inputs_names != NULL);
if (stmt->isSelectable())
stmt->open(tdbb, tran, inputs, inp_names, node_inputs, !node_proc_block);
else
stmt->execute(tdbb, tran, inputs, inp_names, node_inputs, outputs, node_outputs);
request->req_operation = jrd_req::req_return;
} // jrd_req::req_evaluate
if (request->req_operation == jrd_req::req_return || request->req_operation == jrd_req::req_sync)
{
fb_assert(stmt);
if (stmt->isSelectable())
{
if (stmt->fetch(tdbb, outputs, node_outputs))
{
request->req_operation = jrd_req::req_evaluate;
return node_proc_block;
}
request->req_operation = jrd_req::req_return;
}
}
if (stmt) {
stmt->close(tdbb);
}
return node->nod_parent;
}
static jrd_req* execute_triggers(thread_db* tdbb,
trig_vec** triggers,
record_param* old_rpb,
record_param* new_rpb,
jrd_req::req_ta trigger_action, SSHORT which_trig)
{
/**************************************
*
* 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.
*
**************************************/
if (!*triggers) {
return NULL;
}
SET_TDBB(tdbb);
jrd_tra* transaction =
(tdbb->getRequest() ? tdbb->getRequest()->req_transaction : tdbb->getTransaction());
trig_vec* vector = *triggers;
jrd_req* result = NULL;
Record* const old_rec = old_rpb ? old_rpb->rpb_record : NULL;
Record* const new_rec = new_rpb ? new_rpb->rpb_record : NULL;
Record* null_rec = NULL;
if (!old_rec && !new_rec)
{
// this is a database trigger
}
else if (!old_rec || !new_rec)
{
const Record* record = old_rec ? old_rec : new_rec;
fb_assert(record && record->rec_format);
// copy the record
null_rec = FB_NEW_RPT(record->rec_pool, record->rec_length) Record(record->rec_pool);
null_rec->rec_length = record->rec_length;
null_rec->rec_format = record->rec_format;
// zero the record buffer
memset(null_rec->rec_data, 0, record->rec_length);
// initialize all fields to missing
const SSHORT n = (record->rec_format->fmt_count + 7) >> 3;
memset(null_rec->rec_data, 0xFF, n);
}
jrd_req* trigger = NULL;
const Firebird::TimeStamp timestamp(Firebird::TimeStamp::getCurrentTimeStamp());
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 ? old_rec : null_rec;
trigger->req_rpb[1].rpb_record = new_rec ? new_rec : null_rec;
if (old_rec && trigger_action != jrd_req::req_trigger_insert)
{
trigger->req_rpb[0].rpb_number = old_rpb->rpb_number;
trigger->req_rpb[0].rpb_number.setValid(true);
}
else
trigger->req_rpb[0].rpb_number.setValid(false);
if (new_rec && !(which_trig == PRE_TRIG && trigger_action == jrd_req::req_trigger_insert))
{
if (which_trig == PRE_TRIG && trigger_action == jrd_req::req_trigger_update)
new_rpb->rpb_number = old_rpb->rpb_number;
trigger->req_rpb[1].rpb_number = new_rpb->rpb_number;
trigger->req_rpb[1].rpb_number.setValid(true);
}
else
trigger->req_rpb[1].rpb_number.setValid(false);
if (tdbb->getRequest())
trigger->req_timestamp = tdbb->getRequest()->req_timestamp;
else
trigger->req_timestamp = timestamp;
trigger->req_trigger_action = trigger_action;
TraceTrigExecute trace(tdbb, trigger, which_trig);
EXE_start(tdbb, trigger, transaction);
trigger->req_attachment = NULL;
trigger->req_flags &= ~req_in_use;
trigger->req_timestamp.invalidate();
const bool ok = (trigger->req_operation != jrd_req::req_unwind);
trace.finish(ok ? res_successful : res_failed);
if (!ok)
{
result = trigger;
break;
}
trigger = NULL;
}
delete null_rec;
if (vector != *triggers) {
MET_release_triggers(tdbb, &vector);
}
return result;
}
catch (const Firebird::Exception& ex)
{
delete null_rec;
if (vector != *triggers) {
MET_release_triggers(tdbb, &vector);
}
if (!trigger) {
throw; // trigger probally fails to compile
}
Firebird::stuff_exception(tdbb->tdbb_status_vector, ex);
return trigger;
}
}
static void get_string(thread_db* tdbb, jrd_req* request, jrd_nod* node, Firebird::string& str)
{
MoveBuffer buffer;
UCHAR* p = NULL;
SSHORT len = 0;
const dsc* dsc = node ? EVL_expr(tdbb, node) : NULL;
if (dsc && !(request->req_flags & req_null)) {
len = MOV_make_string2(tdbb, dsc, dsc->getTextType(), &p, buffer);
}
str = Firebird::string((char*) p, len);
str.trim();
}
static void stuff_stack_trace(const jrd_req* request)
{
Firebird::string sTrace;
bool isEmpty = true;
for (const jrd_req* req = request; req; req = req->req_caller)
{
Firebird::string name;
if (req->req_trg_name.length()) {
name = "At trigger '";
name += req->req_trg_name.c_str();
}
else if (req->req_procedure) {
name = "At procedure '";
name += req->req_procedure->prc_name.c_str();
}
if (! name.isEmpty())
{
name.trim();
if (sTrace.length() + name.length() + 2 > MAX_STACK_TRACE)
break;
if (isEmpty) {
isEmpty = false;
sTrace += name + "'";
}
else {
sTrace += "\n" + name + "'";
}
if (req->req_src_line)
{
Firebird::string src_info;
src_info.printf(" line: %u, col: %u", req->req_src_line, req->req_src_column);
if (sTrace.length() + src_info.length() > MAX_STACK_TRACE)
break;
sTrace += src_info;
}
}
}
if (!isEmpty)
ERR_post_nothrow(Arg::Gds(isc_stack_trace) << Arg::Str(sTrace));
}
jrd_nod* EXE_looper(thread_db* tdbb, jrd_req* request, jrd_nod* in_node)
{
/**************************************
*
* E X E _ l o o p e r
*
**************************************
*
* Functional description
* Cycle thru request execution tree. Return next node for
* execution on stall or request complete.
*
**************************************/
SSHORT which_erase_trig = 0;
SSHORT which_sto_trig = 0;
SSHORT which_mod_trig = 0;
jrd_nod* top_node = 0;
jrd_nod* prev_node;
jrd_tra* transaction = request->req_transaction;
if (!transaction) {
ERR_post(Arg::Gds(isc_req_no_trans));
}
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
BLKCHK(in_node, type_nod);
// Save the old pool and request to restore on exit
MemoryPool* old_pool = tdbb->getDefaultPool();
Jrd::ContextPoolHolder context(tdbb, request->req_pool);
jrd_req* old_request = tdbb->getRequest();
tdbb->setRequest(request);
jrd_tra* old_transaction = tdbb->getTransaction();
tdbb->setTransaction(transaction);
if (in_node->nod_type != nod_stmt_expr)
{
fb_assert(request->req_caller == NULL);
request->req_caller = old_request;
}
else
request->req_operation = jrd_req::req_evaluate;
const SLONG save_point_number = (transaction->tra_save_point) ?
transaction->tra_save_point->sav_number : 0;
jrd_nod* node = in_node;
// Catch errors so we can unwind cleanly
bool error_pending = false;
bool catch_disabled = false;
tdbb->tdbb_flags &= ~(TDBB_stack_trace_done | TDBB_sys_error);
// Execute stuff until we drop
while (node && !(request->req_flags & req_stall))
{
try {
if (request->req_operation == jrd_req::req_evaluate && (--tdbb->tdbb_quantum < 0))
{
JRD_reschedule(tdbb, 0, true);
}
switch (node->nod_type)
{
case nod_asn_list:
if (request->req_operation == jrd_req::req_evaluate) {
jrd_nod** ptr = node->nod_arg;
for (const jrd_nod* const* const end = ptr + node->nod_count; ptr < end; ptr++)
{
EXE_assignment(tdbb, *ptr);
}
request->req_operation = jrd_req::req_return;
}
node = node->nod_parent;
break;
case nod_assignment:
if (request->req_operation == jrd_req::req_evaluate)
EXE_assignment(tdbb, node);
node = node->nod_parent;
break;
case nod_dcl_variable:
{
impure_value* variable = (impure_value*) ((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)
{
const USHORT len = variable->vlu_desc.dsc_length;
variable->vlu_string = FB_NEW_RPT(*tdbb->getDefaultPool(), len) VaryingString();
variable->vlu_string->str_length = len;
variable->vlu_desc.dsc_address = variable->vlu_string->str_data;
}
request->req_operation = jrd_req::req_return;
node = node->nod_parent;
}
break;
case nod_erase:
if (request->req_operation == jrd_req::req_unwind) {
node = node->nod_parent;
}
else if ((request->req_operation == jrd_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 = jrd_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_exec_proc:
if (request->req_operation == jrd_req::req_unwind) {
node = node->nod_parent;
break;
}
execute_procedure(tdbb, node);
node = node->nod_parent;
request->req_operation = jrd_req::req_return;
break;
case nod_for:
switch (request->req_operation)
{
case jrd_req::req_evaluate:
RSE_open(tdbb, (RecordSource*) node->nod_arg[e_for_rsb]);
request->req_records_affected.clear();
case jrd_req::req_return:
if (node->nod_arg[e_for_stall]) {
node = node->nod_arg[e_for_stall];
break;
}
case jrd_req::req_sync:
if (RSE_get_record(tdbb, (RecordSource*) 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 = jrd_req::req_evaluate;
break;
}
request->req_operation = jrd_req::req_return;
default:
RSE_close(tdbb, (RecordSource*) node->nod_arg[e_for_rsb]);
node = node->nod_parent;
}
break;
case nod_dcl_cursor:
if (request->req_operation == jrd_req::req_evaluate) {
const USHORT number = (USHORT) (IPTR) node->nod_arg[e_dcl_cursor_number];
// set up the cursors vector
request->req_cursors = vec<RecordSource*>::newVector(*request->req_pool,
request->req_cursors, number + 1);
// store RecordSource in the vector
(*request->req_cursors)[number] = (RecordSource*) node->nod_arg[e_dcl_cursor_rsb];
request->req_operation = jrd_req::req_return;
}
node = node->nod_parent;
break;
case nod_cursor_stmt:
{
const UCHAR op = (UCHAR) (IPTR) node->nod_arg[e_cursor_stmt_op];
const USHORT number = (USHORT) (IPTR) node->nod_arg[e_cursor_stmt_number];
// get RecordSource and the impure area
fb_assert(request->req_cursors && number < request->req_cursors->count());
RecordSource* rsb = (*request->req_cursors)[number];
IRSB impure = (IRSB) ((UCHAR*) tdbb->getRequest() + rsb->rsb_impure);
switch (op)
{
case blr_cursor_open:
if (request->req_operation == jrd_req::req_evaluate) {
// check cursor state
if (impure->irsb_flags & irsb_open) {
ERR_post(Arg::Gds(isc_cursor_already_open));
}
// open cursor
RSE_open(tdbb, rsb);
request->req_operation = jrd_req::req_return;
}
node = node->nod_parent;
break;
case blr_cursor_close:
if (request->req_operation == jrd_req::req_evaluate) {
// check cursor state
if (!(impure->irsb_flags & irsb_open)) {
ERR_post(Arg::Gds(isc_cursor_not_open));
}
// close cursor
RSE_close(tdbb, rsb);
request->req_operation = jrd_req::req_return;
}
node = node->nod_parent;
break;
case blr_cursor_fetch:
switch (request->req_operation)
{
case jrd_req::req_evaluate:
// check cursor state
if (!(impure->irsb_flags & irsb_open)) {
ERR_post(Arg::Gds(isc_cursor_not_open));
}
if (impure->irsb_flags & irsb_eof) {
ERR_post(Arg::Gds(isc_stream_eof));
}
request->req_records_affected.clear();
// perform preliminary navigation, if specified
if (node->nod_arg[e_cursor_stmt_seek]) {
node = node->nod_arg[e_cursor_stmt_seek];
break;
}
// fetch one record
if (RSE_get_record(tdbb, rsb,
#ifdef SCROLLABLE_CURSORS
RSE_get_next))
#else
RSE_get_forward))
#endif
{
node = node->nod_arg[e_cursor_stmt_into];
request->req_operation = jrd_req::req_evaluate;
break;
}
request->req_operation = jrd_req::req_return;
default:
node = node->nod_parent;
}
break;
}
}
break;
case nod_abort:
switch (request->req_operation)
{
case jrd_req::req_evaluate:
{
PsqlException* xcp_node = reinterpret_cast<PsqlException*>(node->nod_arg[e_xcp_desc]);
if (xcp_node)
{
/* PsqlException is defined,
so throw an exception */
set_error(tdbb, &xcp_node->xcp_rpt[0], node->nod_arg[e_xcp_msg]);
}
else if (!request->req_last_xcp.success())
{
/* PsqlException is undefined, but there was a known exception before,
so re-initiate it */
set_error(tdbb, NULL, NULL);
}
else
{
/* PsqlException is undefined and there weren't any exceptions before,
so just do nothing */
request->req_operation = jrd_req::req_return;
}
}
default:
node = node->nod_parent;
}
break;
case nod_user_savepoint:
switch (request->req_operation)
{
case jrd_req::req_evaluate:
if (transaction != dbb->dbb_sys_trans)
{
const UCHAR operation = (UCHAR) (IPTR) node->nod_arg[e_sav_operation];
const TEXT* node_savepoint_name = (TEXT*) node->nod_arg[e_sav_name];
// Skip the savepoint created by EXE_start
Savepoint* savepoint = transaction->tra_save_point->sav_next;
Savepoint* previous = transaction->tra_save_point;
// Find savepoint
bool found = false;
while (true)
{
if (!savepoint || !(savepoint->sav_flags & SAV_user))
break;
if (!strcmp(node_savepoint_name, savepoint->sav_name)) {
found = true;
break;
}
previous = savepoint;
savepoint = savepoint->sav_next;
}
if (!found && operation != blr_savepoint_set) {
ERR_post(Arg::Gds(isc_invalid_savepoint) << Arg::Str(node_savepoint_name));
}
switch (operation)
{
case blr_savepoint_set:
// Release the savepoint
if (found) {
Savepoint* const current = transaction->tra_save_point;
transaction->tra_save_point = savepoint;
verb_cleanup(tdbb, transaction);
previous->sav_next = transaction->tra_save_point;
transaction->tra_save_point = current;
}
// Use the savepoint created by EXE_start
transaction->tra_save_point->sav_flags |= SAV_user;
strcpy(transaction->tra_save_point->sav_name, node_savepoint_name);
break;
case blr_savepoint_release_single:
{
// Release the savepoint
Savepoint* const current = transaction->tra_save_point;
transaction->tra_save_point = savepoint;
verb_cleanup(tdbb, transaction);
previous->sav_next = transaction->tra_save_point;
transaction->tra_save_point = current;
break;
}
case blr_savepoint_release:
{
const SLONG sav_number = savepoint->sav_number;
// Release the savepoint and all subsequent ones
while (transaction->tra_save_point &&
transaction->tra_save_point->sav_number >= sav_number)
{
verb_cleanup(tdbb, transaction);
}
// Restore the savepoint initially created by EXE_start
VIO_start_save_point(tdbb, transaction);
break;
}
case blr_savepoint_undo:
{
const SLONG sav_number = savepoint->sav_number;
// 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(tdbb, transaction);
}
// 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, node_savepoint_name);
break;
}
default:
BUGCHECK(232);
break;
}
}
default:
node = node->nod_parent;
request->req_operation = jrd_req::req_return;
}
break;
case nod_start_savepoint:
switch (request->req_operation)
{
case jrd_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 = jrd_req::req_return;
}
break;
case nod_end_savepoint:
switch (request->req_operation)
{
case jrd_req::req_evaluate:
case jrd_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(tdbb, transaction);
}
default:
node = node->nod_parent;
request->req_operation = jrd_req::req_return;
}
break;
case nod_handler:
switch (request->req_operation)
{
case jrd_req::req_evaluate:
node = node->nod_arg[0];
break;
case jrd_req::req_unwind:
if (!request->req_label)
request->req_operation = jrd_req::req_return;
default:
node = node->nod_parent;
}
break;
case nod_block:
switch (request->req_operation)
{
SLONG count;
case jrd_req::req_evaluate:
if (transaction != dbb->dbb_sys_trans) {
VIO_start_save_point(tdbb, transaction);
const Savepoint* save_point = transaction->tra_save_point;
count = save_point->sav_number;
memcpy((SCHAR*) request + node->nod_impure, &count, sizeof(SLONG));
}
node = node->nod_arg[e_blk_action];
break;
case jrd_req::req_unwind:
{
if (request->req_flags & req_leave)
{
// Although the req_operation is set to req_unwind,
// it's not an error case if req_leave bit is set.
// req_leave bit indicates that we hit an EXIT or
// BREAK/LEAVE statement in the SP/trigger code.
// Do not perform the error handling stuff.
if (transaction != dbb->dbb_sys_trans) {
memcpy(&count, (SCHAR*) request + node->nod_impure, sizeof(SLONG));
for (const Savepoint* save_point = transaction->tra_save_point;
save_point && count <= save_point->sav_number;
save_point = transaction->tra_save_point)
{
verb_cleanup(tdbb, transaction);
}
}
node = node->nod_parent;
break;
}
if (transaction != dbb->dbb_sys_trans)
{
memcpy(&count, (SCHAR*) request + node->nod_impure, 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 (const Savepoint* 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(tdbb, transaction);
}
}
jrd_nod* handlers = node->nod_arg[e_blk_handlers];
if (handlers)
{
node = node->nod_parent;
jrd_nod** ptr = handlers->nod_arg;
for (const jrd_nod* const* const end = ptr + handlers->nod_count;
ptr < end; ptr++)
{
const PsqlException* xcp_node =
reinterpret_cast<PsqlException*>((*ptr)->nod_arg[e_err_conditions]);
if (test_and_fixup_error(tdbb, xcp_node, request))
{
request->req_operation = jrd_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. */
{
Jrd::ContextPoolHolder contextLooper(tdbb, old_pool);
tdbb->setRequest(old_request);
fb_assert(request->req_caller == old_request);
request->req_caller = NULL;
/* 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. */
const ULONG prev_req_error_handler =
request->req_flags & req_error_handler;
request->req_flags |= req_error_handler;
node = EXE_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
processing of execution tree. If this is
not done then anymore errors will take the
engine out of looper there by abruptly
terminating the processing. */
catch_disabled = false;
tdbb->setRequest(request);
fb_assert(request->req_caller == NULL);
request->req_caller = old_request;
}
/* The error is dealt with by the application, cleanup
this block's savepoint. */
if (transaction != dbb->dbb_sys_trans)
{
for (const Savepoint* save_point = transaction->tra_save_point;
save_point && count <= save_point->sav_number;
save_point = transaction->tra_save_point)
{
verb_cleanup(tdbb, transaction);
}
}
}
}
}
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)
{
for (const Savepoint* 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(tdbb, transaction);
}
}
}
break;
case jrd_req::req_return:
if (transaction != dbb->dbb_sys_trans)
{
memcpy(&count, (SCHAR*) request + node->nod_impure, sizeof(SLONG));
for (const Savepoint* save_point = transaction->tra_save_point;
save_point && count <= save_point->sav_number;
save_point = transaction->tra_save_point)
{
verb_cleanup(tdbb, transaction);
}
}
default:
node = node->nod_parent;
}
break;
case nod_error_handler:
if (request->req_flags & req_error_handler && !error_pending)
{
fb_assert(request->req_caller == old_request);
request->req_caller = NULL;
return node;
}
node = node->nod_parent;
node = node->nod_parent;
if (request->req_operation == jrd_req::req_unwind) {
node = node->nod_parent;
}
request->req_last_xcp.clear();
break;
case nod_label:
switch (request->req_operation)
{
case jrd_req::req_evaluate:
node = node->nod_arg[e_lbl_statement];
break;
case jrd_req::req_unwind:
if ((request->req_label == (USHORT)(IPTR) node->nod_arg[e_lbl_label]) &&
(request->req_flags & (req_leave | req_error_handler)))
{
request->req_flags &= ~req_leave;
request->req_operation = jrd_req::req_return;
}
default:
node = node->nod_parent;
}
break;
case nod_leave:
request->req_flags |= req_leave;
request->req_operation = jrd_req::req_unwind;
request->req_label = (USHORT)(IPTR) node->nod_arg[0];
node = node->nod_parent;
break;
case nod_list:
{
impure_state* impure = (impure_state*) ((SCHAR *) request + node->nod_impure);
switch (request->req_operation)
{
case jrd_req::req_evaluate:
impure->sta_state = 0;
case jrd_req::req_return:
case jrd_req::req_sync:
if (impure->sta_state < node->nod_count) {
request->req_operation = jrd_req::req_evaluate;
node = node->nod_arg[impure->sta_state++];
break;
}
request->req_operation = jrd_req::req_return;
default:
node = node->nod_parent;
}
}
break;
case nod_loop:
switch (request->req_operation)
{
case jrd_req::req_evaluate:
case jrd_req::req_return:
node = node->nod_arg[0];
request->req_operation = jrd_req::req_evaluate;
break;
default:
node = node->nod_parent;
}
break;
case nod_if:
if (request->req_operation == jrd_req::req_evaluate)
{
if (EVL_boolean(tdbb, node->nod_arg[e_if_boolean])) {
node = node->nod_arg[e_if_true];
request->req_operation = jrd_req::req_evaluate;
break;
}
if (node->nod_arg[e_if_false]) {
node = node->nod_arg[e_if_false];
request->req_operation = jrd_req::req_evaluate;
break;
}
request->req_operation = jrd_req::req_return;
}
node = node->nod_parent;
break;
case nod_modify:
{
impure_state* impure = (impure_state*) ((SCHAR *) request + node->nod_impure);
if (request->req_operation == jrd_req::req_unwind) {
node = node->nod_parent;
}
else if ((request->req_operation == jrd_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 = jrd_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 = jrd_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 == jrd_req::req_unwind) {
node = node->nod_parent;
break;
}
exec_sql(tdbb, request, EVL_expr(tdbb, node->nod_arg[0]));
if (request->req_operation == jrd_req::req_evaluate)
request->req_operation = jrd_req::req_return;
node = node->nod_parent;
break;
case nod_exec_into:
{
ExecuteStatement* impure = (ExecuteStatement*) ((SCHAR*) request + node->nod_impure);
switch (request->req_operation)
{
case jrd_req::req_evaluate:
impure->open(tdbb, node->nod_arg[0], node->nod_count - 2, !node->nod_arg[1]);
// fall into ???
case jrd_req::req_return:
case jrd_req::req_sync:
if (impure->fetch(tdbb, &node->nod_arg[2])) {
request->req_operation = jrd_req::req_evaluate;
node = node->nod_arg[1];
break;
}
request->req_operation = jrd_req::req_return;
default:
// if have active opened request - close it
impure->close(tdbb);
node = node->nod_parent;
}
}
break;
case nod_exec_stmt:
node = execute_statement(tdbb, request, node);
break;
case nod_post:
{
DeferredWork* work =
DFW_post_work(transaction, dfw_post_event, EVL_expr(tdbb, node->nod_arg[0]), 0);
if (node->nod_arg[1])
DFW_post_work_arg(transaction, work, EVL_expr(tdbb, node->nod_arg[1]), 0);
}
// for an autocommit transaction, events can be posted
// without any updates
if (transaction->tra_flags & TRA_autocommit)
transaction->tra_flags |= TRA_perform_autocommit;
if (request->req_operation == jrd_req::req_evaluate)
request->req_operation = jrd_req::req_return;
node = node->nod_parent;
break;
case nod_message:
if (request->req_operation == jrd_req::req_evaluate)
{
const Format* format = (Format*) node->nod_arg[e_msg_format];
USHORT* impure_flags =
(USHORT*) ((UCHAR*) request + (IPTR) node->nod_arg[e_msg_impure_flags]);
memset(impure_flags, 0, sizeof(USHORT) * format->fmt_count);
request->req_operation = jrd_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_state* impure = (impure_state*) ((SCHAR *) request + node->nod_impure);
if ((request->req_operation == jrd_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 = jrd_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
case nod_set_generator:
case nod_set_generator2:
if (request->req_operation == jrd_req::req_evaluate) {
dsc* desc = EVL_expr(tdbb, node->nod_arg[e_gen_value]);
DPM_gen_id(tdbb, (IPTR) node->nod_arg[e_gen_id], true, MOV_get_int64(desc, 0));
request->req_operation = jrd_req::req_return;
}
node = node->nod_parent;
break;
case nod_src_info:
if (request->req_operation == jrd_req::req_evaluate) {
request->req_src_line = (USHORT) (IPTR) node->nod_arg[e_src_info_line];
request->req_src_column = (USHORT) (IPTR) node->nod_arg[e_src_info_col];
//request->req_operation = jrd_req::req_return;
node = node->nod_arg[e_src_info_node];
}
else
node = node->nod_parent;
break;
case nod_init_variable:
if (request->req_operation == jrd_req::req_evaluate)
{
const ItemInfo* itemInfo =
reinterpret_cast<const ItemInfo*>(node->nod_arg[e_init_var_info]);
if (itemInfo)
{
jrd_nod* var_node = node->nod_arg[e_init_var_variable];
DSC* to_desc = &((impure_value*) ((SCHAR *) request + var_node->nod_impure))->vlu_desc;
to_desc->dsc_flags |= DSC_null;
MapFieldInfo::ValueType fieldInfo;
if (itemInfo->fullDomain &&
request->req_map_field_info.get(itemInfo->field, fieldInfo) &&
fieldInfo.defaultValue)
{
dsc* value = EVL_expr(tdbb, fieldInfo.defaultValue);
if (value && !(request->req_flags & req_null))
{
to_desc->dsc_flags &= ~DSC_null;
MOV_move(tdbb, value, to_desc);
}
}
}
request->req_operation = jrd_req::req_return;
}
node = node->nod_parent;
break;
case nod_class_node_jrd:
node = reinterpret_cast<StmtNode*>(node->nod_arg[0])->execute(tdbb, request);
break;
case nod_stmt_expr:
if (request->req_operation == jrd_req::req_evaluate)
node = node->nod_arg[e_stmt_expr_stmt];
else
node = NULL;
break;
default:
BUGCHECK(168); /* msg 168 looper: action not yet implemented */
}
} // try
catch (const Firebird::Exception& ex)
{
Firebird::stuff_exception(tdbb->tdbb_status_vector, ex);
request->adjustCallerStats();
// Skip this handling for errors coming from the nested looper calls,
// as they're already handled properly. The only need is to undo
// our own savepoints.
if (catch_disabled)
{
if (transaction != dbb->dbb_sys_trans) {
for (const Savepoint* save_point = transaction->tra_save_point;
((save_point) && (save_point_number <= save_point->sav_number));
save_point = transaction->tra_save_point)
{
++transaction->tra_save_point->sav_verb_count;
verb_cleanup(tdbb, transaction);
}
}
ERR_punt();
}
// If the database is already bug-checked, then get out
if (dbb->dbb_flags & DBB_bugcheck) {
Firebird::status_exception::raise(tdbb->tdbb_status_vector);
}
// Since an error happened, the current savepoint needs to be undone
if (transaction != dbb->dbb_sys_trans && transaction->tra_save_point)
{
++transaction->tra_save_point->sav_verb_count;
verb_cleanup(tdbb, transaction);
}
error_pending = true;
catch_disabled = true;
request->req_operation = jrd_req::req_unwind;
request->req_label = 0;
if (!(tdbb->tdbb_flags & TDBB_stack_trace_done) && !(tdbb->tdbb_flags & TDBB_sys_error))
{
stuff_stack_trace(request);
tdbb->tdbb_flags |= TDBB_stack_trace_done;
}
}
} // while()
request->adjustCallerStats();
fb_assert(request->req_auto_trans.getCount() == 0);
// If there is no node, assume we have finished processing the
// request unless we are in the middle of processing an
// asynchronous message
if (in_node->nod_type != nod_stmt_expr && !node
#ifdef SCROLLABLE_CURSORS
&& !(request->req_flags & req_async_processing)
#endif
)
{
// Close active cursors
if (request->req_cursors)
{
for (vec<RecordSource*>::iterator ptr = request->req_cursors->begin(),
end = request->req_cursors->end(); ptr < end; ++ptr)
{
if (*ptr)
RSE_close(tdbb, *ptr);
}
}
request->req_flags &= ~(req_active | req_reserved);
request->req_timestamp.invalidate();
release_blobs(tdbb, request);
}
request->req_next = node;
tdbb->setTransaction(old_transaction);
tdbb->setRequest(old_request);
if (in_node->nod_type != nod_stmt_expr)
{
fb_assert(request->req_caller == old_request);
request->req_caller = 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) {
for (const Savepoint* save_point = transaction->tra_save_point;
((save_point) && (save_point_number <= save_point->sav_number));
save_point = transaction->tra_save_point)
{
++transaction->tra_save_point->sav_verb_count;
verb_cleanup(tdbb, transaction);
}
}
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(Arg::Gds(isc_req_sync));
}
return node;
}
// Start looper under Windows SEH (Structured Exception Handling) control
static void looper_seh(thread_db* tdbb, jrd_req* request, jrd_nod* in_node)
{
#ifdef WIN_NT
START_CHECK_FOR_EXCEPTIONS(NULL);
#endif
// TODO:
// 1. Try to fix the problem with MSVC C++ runtime library, making
// even C++ exceptions that are implemented in terms of Win32 SEH
// getting catched by the SEH handler below.
// 2. Check if it really is correct that only Win32 catches CPU
// exceptions (such as SEH) here. Shouldn't any platform capable
// of handling signals use this stuff?
// (see jrd/ibsetjmp.h for implementation of these macros)
EXE_looper(tdbb, request, request->req_top_node);
#ifdef WIN_NT
END_CHECK_FOR_EXCEPTIONS(NULL);
#endif
}
static jrd_nod* modify(thread_db* tdbb, jrd_nod* node, SSHORT which_trig)
{
/**************************************
*
* m o d i f y
*
**************************************
*
* Functional description
* Execute a MODIFY statement.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
BLKCHK(node, type_nod);
jrd_req* request = tdbb->getRequest();
jrd_tra* transaction = request->req_transaction;
impure_state* impure = (impure_state*) ((SCHAR *) request + node->nod_impure);
const SSHORT org_stream = (USHORT)(IPTR) node->nod_arg[e_mod_org_stream];
record_param* org_rpb = &request->req_rpb[org_stream];
jrd_rel* relation = org_rpb->rpb_relation;
if (org_rpb->rpb_number.isBof() || (!relation->rel_view_rse && !org_rpb->rpb_number.isValid()))
{
ERR_post(Arg::Gds(isc_no_cur_rec));
}
const SSHORT new_stream = (USHORT)(IPTR) node->nod_arg[e_mod_new_stream];
record_param* new_rpb = &request->req_rpb[new_stream];
/* 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) {
VIO_refetch_record(tdbb, org_rpb, transaction);
org_rpb->rpb_stream_flags &= ~RPB_s_refetch;
}
switch (request->req_operation)
{
case jrd_req::req_evaluate:
request->req_records_affected.bumpModified(false);
break;
case jrd_req::req_return:
if (impure->sta_state == 1)
{
impure->sta_state = 0;
Record* org_record = org_rpb->rpb_record;
const Record* new_record = new_rpb->rpb_record;
memcpy(org_record->rec_data, new_record->rec_data, new_record->rec_length);
request->req_operation = jrd_req::req_evaluate;
return node->nod_arg[e_mod_statement];
}
if (impure->sta_state == 0)
{
/* 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);
if (transaction != dbb->dbb_sys_trans)
++transaction->tra_save_point->sav_verb_count;
PreModifyEraseTriggers(tdbb, &relation->rel_pre_modify, which_trig, org_rpb, new_rpb,
jrd_req::req_trigger_update);
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, transaction);
}
else if (relation->isVirtual()) {
VirtualTable::modify(tdbb, org_rpb, new_rpb);
}
else if (!relation->rel_view_rse)
{
USHORT bad_index;
jrd_rel* bad_relation = 0;
VIO_modify(tdbb, org_rpb, new_rpb, transaction);
const IDX_E error_code =
IDX_modify(tdbb, org_rpb, new_rpb, transaction, &bad_relation, &bad_index);
if (error_code) {
ERR_duplicate_error(error_code, bad_relation, bad_index);
}
}
new_rpb->rpb_number = org_rpb->rpb_number;
new_rpb->rpb_number.setValid(true);
jrd_req* trigger;
if (relation->rel_post_modify && which_trig != PRE_TRIG &&
(trigger = execute_triggers(tdbb, &relation->rel_post_modify,
org_rpb, new_rpb, jrd_req::req_trigger_update, POST_TRIG)))
{
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 && !relation->isVirtual())
{
USHORT bad_index;
jrd_rel* bad_relation = 0;
const IDX_E error_code =
IDX_modify_check_constraints(tdbb, org_rpb, new_rpb, transaction,
&bad_relation, &bad_index);
if (error_code) {
ERR_duplicate_error(error_code, bad_relation, bad_index);
}
}
if (transaction != dbb->dbb_sys_trans) {
--transaction->tra_save_point->sav_verb_count;
}
/* 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.bumpModified(true);
}
}
else if (relation->rel_file || !relation->rel_view_rse) {
request->req_records_updated++;
request->req_records_affected.bumpModified(true);
}
if (node->nod_arg[e_mod_statement2]) {
impure->sta_state = 2;
request->req_operation = jrd_req::req_evaluate;
return node->nod_arg[e_mod_statement2];
}
}
if (which_trig != PRE_TRIG) {
Record* 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);
/* 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. */
const Format* new_format = MET_current(tdbb, new_rpb->rpb_relation);
Record* new_record = VIO_record(tdbb, new_rpb, new_format, tdbb->getDefaultPool());
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;
const Format* org_format;
Record* org_record = org_rpb->rpb_record;
if (!org_record)
{
org_record = VIO_record(tdbb, org_rpb, new_format, tdbb->getDefaultPool());
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) {
memcpy(new_rpb->rpb_address, org_record->rec_data, new_rpb->rpb_length);
}
else
{
DSC org_desc, new_desc;
for (SSHORT 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(tdbb, &org_desc, &new_desc);
}
else {
SET_NULL(new_record, i);
if (new_desc.dsc_dtype) {
UCHAR* p = new_desc.dsc_address;
USHORT n = new_desc.dsc_length;
memset(p, 0, n);
}
} /* if (org_record) */
} /* if (new_record) */
} /* for (fmt_count) */
}
new_rpb->rpb_number = org_rpb->rpb_number;
new_rpb->rpb_number.setValid(true);
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 jrd_nod* receive_msg(thread_db* tdbb, 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.
*
**************************************/
SET_TDBB(tdbb);
jrd_req* request = tdbb->getRequest();
BLKCHK(node, type_nod);
switch (request->req_operation)
{
case jrd_req::req_evaluate:
request->req_operation = jrd_req::req_receive;
request->req_message = node->nod_arg[e_send_message];
request->req_flags |= req_stall;
return node;
case jrd_req::req_proceed:
request->req_operation = jrd_req::req_evaluate;
return (node->nod_arg[e_send_statement]);
default:
return (node->nod_parent);
}
}
static void release_blobs(thread_db* tdbb, jrd_req* request)
{
/**************************************
*
* r e l e a s e _ b l o b s
*
**************************************
*
* Functional description
* Release temporary blobs assigned by this request.
*
**************************************/
SET_TDBB(tdbb);
DEV_BLKCHK(request, type_req);
jrd_tra* transaction = request->req_transaction;
if (transaction)
{
DEV_BLKCHK(transaction, type_tra);
/* Release blobs bound to this request */
if (request->req_blobs.getFirst())
{
while (true)
{
const ULONG blob_temp_id = request->req_blobs.current();
if (transaction->tra_blobs->locate(blob_temp_id))
{
BlobIndex *current = &transaction->tra_blobs->current();
if (current->bli_materialized)
{
request->req_blobs.fastRemove();
current->bli_request = NULL;
}
else
{
// Blob was created by request, is accounted for internal needs,
// but is not materialized. Get rid of it.
BLB_cancel(tdbb, current->bli_blob_object);
// Since the routine above modifies req_blobs
// we need to reestablish accessor position
}
if (request->req_blobs.locate(Firebird::locGreat, blob_temp_id))
continue;
break;
}
// Blob accounting inconsistent, only detected in DEV_BUILD.
fb_assert(false);
if (!request->req_blobs.getNext())
break;
}
}
request->req_blobs.clear();
/* Release arrays assigned by this request */
for (ArrayField** array = &transaction->tra_arrays; *array;)
{
DEV_BLKCHK(*array, type_arr);
if ((*array)->arr_request == request)
BLB_release_array(*array);
else
array = &(*array)->arr_next;
}
}
}
static void release_proc_save_points(jrd_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.
*
**************************************/
Savepoint* sav_point = request->req_proc_sav_point;
if (request->req_transaction) {
while (sav_point) {
Savepoint* const temp_sav_point = sav_point->sav_next;
delete sav_point;
sav_point = temp_sav_point;
}
}
request->req_proc_sav_point = NULL;
}
#ifdef SCROLLABLE_CURSORS
static jrd_nod* seek_rse(thread_db* tdbb, jrd_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.
*
**************************************/
SET_TDBB(tdbb);
DEV_BLKCHK(node, type_nod);
if (request->req_operation == jrd_req::req_proceed)
{
/* get input arguments */
const dsc* desc = EVL_expr(tdbb, node->nod_arg[e_seek_direction]);
const USHORT direction = (desc && !(request->req_flags & req_null)) ?
MOV_get_long(desc, 0) : MAX_USHORT;
desc = EVL_expr(tdbb, node->nod_arg[e_seek_offset]);
const SLONG offset = (desc && !(request->req_flags & req_null)) ? MOV_get_long(desc, 0) : 0;
RecordSelExpr* rse = (RecordSelExpr*) node->nod_arg[e_seek_rse];
seek_rsb(tdbb, request, rse->rse_rsb, direction, offset);
request->req_operation = jrd_req::req_return;
}
return node->nod_parent;
}
#endif
#ifdef SCROLLABLE_CURSORS
static void seek_rsb(thread_db* tdbb,
jrd_req* request, RecordSource* 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 multiple seeking.
* Uses RSE_get_record() to do the actual work.
*
**************************************/
SET_TDBB(tdbb);
DEV_BLKCHK(rsb, type_rsb);
irsb* 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 */
const irsb* 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(Arg::Gds(isc_stream_eof));
break;
case blr_backward:
if (impure->irsb_flags & irsb_bof)
ERR_post(Arg::Gds(isc_stream_bof));
break;
case blr_bof_forward:
case blr_eof_backward:
break;
default:
// was: BUGCHECK(232);
// replaced with this error to be consistent with find()
ERR_post(Arg::Gds(isc_invalid_direction));
}
/* 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 */
/* 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 */
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:
// Should never go here, because of the boundary
// check above, but anyway...
BUGCHECK(232);
}
}
#endif
static jrd_nod* selct(thread_db* tdbb, 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."
*
**************************************/
SET_TDBB(tdbb);
jrd_req* request = tdbb->getRequest();
BLKCHK(node, type_nod);
switch (request->req_operation)
{
case jrd_req::req_evaluate:
request->req_message = node;
request->req_operation = jrd_req::req_receive;
request->req_flags |= req_stall;
return node;
default:
return node->nod_parent;
}
}
static jrd_nod* send_msg(thread_db* tdbb, jrd_nod* node)
{
/**************************************
*
* s e n d _ m s g
*
**************************************
*
* Functional description
* Execute a SEND statement.
*
**************************************/
SET_TDBB(tdbb);
jrd_req* request = tdbb->getRequest();
BLKCHK(node, type_nod);
switch (request->req_operation)
{
case jrd_req::req_evaluate:
return (node->nod_arg[e_send_statement]);
case jrd_req::req_return:
request->req_operation = jrd_req::req_send;
request->req_message = node->nod_arg[e_send_message];
request->req_flags |= req_stall;
return node;
case jrd_req::req_proceed:
request->req_operation = jrd_req::req_return;
return node->nod_parent;
default:
return (node->nod_parent);
}
}
static void set_error(thread_db* tdbb, const xcp_repeat* exception, jrd_nod* msg_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.
*
**************************************/
Firebird::MetaName name, relation_name;
TEXT message[XCP_MESSAGE_LENGTH + 1];
// since temp used as vary, we need size of vary::vary_length
// (USHORT) extra chars
TEXT temp[XCP_MESSAGE_LENGTH + sizeof(USHORT)];
SET_TDBB(tdbb);
jrd_req* request = tdbb->getRequest();
if (!exception) {
// retrieve the status vector and punt
request->req_last_xcp.copyTo(tdbb->tdbb_status_vector);
request->req_last_xcp.clear();
ERR_punt();
}
USHORT length = 0;
if (msg_node)
{
const char* string = 0;
// evaluate exception message and convert it to string
DSC* desc = EVL_expr(tdbb, msg_node);
if (desc && !(request->req_flags & req_null))
{
length = MOV_make_string(desc, tdbb->getAttachment()->att_charset, &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(Arg::Gds(isc_imp_exc) << Arg::Gds(isc_blktoobig));
*/
memcpy(message, string, length);
}
else
{
length = 0;
}
}
message[length] = 0;
const TEXT* s;
switch (exception->xcp_type)
{
case xcp_sql_code:
ERR_post(Arg::Gds(isc_sqlerr) << Arg::Num(exception->xcp_code));
case xcp_gds_code:
if (exception->xcp_code == isc_check_constraint) {
MET_lookup_cnstrt_for_trigger(tdbb, name, relation_name, request->req_trg_name);
ERR_post(Arg::Gds(exception->xcp_code) << Arg::Str(name) << Arg::Str(relation_name));
}
else
ERR_post(Arg::Gds(exception->xcp_code));
case xcp_xcp_code:
// CVC: If we have the exception name, use it instead of the number.
// Solves SF Bug #494981.
MET_lookup_exception(tdbb, exception->xcp_code, name, temp, sizeof(temp));
if (message[0])
s = message;
else if (temp[0])
s = temp;
else
s = NULL;
if (s && name.length())
{
ERR_post(Arg::Gds(isc_except) << Arg::Num(exception->xcp_code) <<
Arg::Gds(isc_random) << Arg::Str(name) <<
Arg::Gds(isc_random) << Arg::Str(s));
}
else if (s)
{
ERR_post(Arg::Gds(isc_except) << Arg::Num(exception->xcp_code) <<
Arg::Gds(isc_random) << Arg::Str(s));
}
else if (name.length())
{
ERR_post(Arg::Gds(isc_except) << Arg::Num(exception->xcp_code) <<
Arg::Gds(isc_random) << Arg::Str(name));
}
else
ERR_post(Arg::Gds(isc_except) << Arg::Num(exception->xcp_code));
default:
fb_assert(false);
}
}
static jrd_nod* stall(thread_db* tdbb, 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.
*
**************************************/
SET_TDBB(tdbb);
jrd_req* request = tdbb->getRequest();
BLKCHK(node, type_nod);
switch (request->req_operation)
{
case jrd_req::req_sync:
return node->nod_parent;
case jrd_req::req_proceed:
request->req_operation = jrd_req::req_return;
return node->nod_parent;
default:
request->req_message = node;
request->req_operation = jrd_req::req_return;
request->req_flags |= req_stall;
return node;
}
}
static jrd_nod* store(thread_db* tdbb, jrd_nod* node, SSHORT which_trig)
{
/**************************************
*
* s t o r e
*
**************************************
*
* Functional description
* Execute a STORE statement.
*
**************************************/
jrd_req* trigger;
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
BLKCHK(node, type_nod);
jrd_req* request = tdbb->getRequest();
jrd_tra* transaction = request->req_transaction;
impure_state* impure = (impure_state*) ((SCHAR *) request + node->nod_impure);
SSHORT stream = (USHORT)(IPTR) node->nod_arg[e_sto_relation]->nod_arg[e_rel_stream];
record_param* rpb = &request->req_rpb[stream];
jrd_rel* relation = rpb->rpb_relation;
switch (request->req_operation)
{
case jrd_req::req_evaluate:
if (request->req_records_affected.isReadOnly() && !request->req_records_affected.hasCursor())
{
request->req_records_affected.clear();
}
request->req_records_affected.bumpModified(false);
impure->sta_state = 0;
RLCK_reserve_relation(tdbb, transaction, relation, true);
break;
case jrd_req::req_return:
if (impure->sta_state)
return node->nod_parent;
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,
NULL, rpb, jrd_req::req_trigger_insert, PRE_TRIG)))
{
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 below. */
cleanup_rpb(tdbb, rpb);
if (relation->rel_file) {
EXT_store(tdbb, rpb, transaction);
}
else if (relation->isVirtual()) {
VirtualTable::store(tdbb, rpb);
}
else if (!relation->rel_view_rse)
{
USHORT bad_index;
jrd_rel* bad_relation = 0;
VIO_store(tdbb, rpb, transaction);
const IDX_E error_code = IDX_store(tdbb, rpb, transaction, &bad_relation, &bad_index);
if (error_code) {
ERR_duplicate_error(error_code, bad_relation, bad_index);
}
}
rpb->rpb_number.setValid(true);
if (relation->rel_post_store && (which_trig != PRE_TRIG) &&
(trigger = execute_triggers(tdbb, &relation->rel_post_store,
NULL, rpb, jrd_req::req_trigger_insert, POST_TRIG)))
{
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.bumpModified(true);
}
}
else if (relation->rel_file || !relation->rel_view_rse) {
request->req_records_inserted++;
request->req_records_affected.bumpModified(true);
}
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 = jrd_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." */
const Format* format = MET_current(tdbb, relation);
Record* record = VIO_record(tdbb, rpb, format, tdbb->getDefaultPool());
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. */
memset(record->rec_data, 0, rpb->rpb_length);
/* Initialize all fields to missing */
SSHORT n = (format->fmt_count + 7) >> 3;
if (n) {
memset(record->rec_data, 0xff, n);
}
return node->nod_arg[e_sto_statement];
}
static bool test_and_fixup_error(thread_db* tdbb, const PsqlException* conditions, jrd_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.
*
**************************************/
SET_TDBB(tdbb);
if (tdbb->tdbb_flags & TDBB_sys_error)
return false;
ISC_STATUS* status_vector = tdbb->tdbb_status_vector;
const SSHORT sqlcode = gds__sqlcode(status_vector);
bool found = false;
for (USHORT 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)
{
found = true;
}
break;
case xcp_gds_code:
if (status_vector[1] == conditions->xcp_rpt[i].xcp_code)
{
found = true;
}
break;
case xcp_xcp_code:
{
// Look at set_error() routine to understand how the
// exception ID info is encoded inside the status vector.
if ((status_vector[1] == isc_except) &&
(status_vector[3] == conditions->xcp_rpt[i].xcp_code))
{
found = true;
}
}
break;
case xcp_default:
found = true;
break;
default:
fb_assert(false);
}
if (found)
{
request->req_last_xcp.init(status_vector);
fb_utils::init_status(status_vector);
break;
}
}
return found;
}
static void trigger_failure(thread_db* tdbb, jrd_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.invalidate();
if (trigger->req_flags & req_leave)
{
trigger->req_flags &= ~req_leave;
string msg;
MET_trigger_msg(tdbb, msg, trigger->req_trg_name, trigger->req_label);
if (msg.hasData())
{
if (trigger->req_flags & req_sys_trigger)
{
ISC_STATUS code = PAR_symbol_to_gdscode(msg);
if (code)
{
ERR_post(Arg::Gds(isc_integ_fail) << Arg::Num(trigger->req_label) <<
Arg::Gds(code));
}
}
ERR_post(Arg::Gds(isc_integ_fail) << Arg::Num(trigger->req_label) <<
Arg::Gds(isc_random) << Arg::Str(msg));
}
else
{
ERR_post(Arg::Gds(isc_integ_fail) << Arg::Num(trigger->req_label));
}
}
else
{
ERR_punt();
}
}
static void validate(thread_db* 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 = list->nod_arg;
for (const jrd_nod* const* const end = ptr1 + list->nod_count; ptr1 < end; ptr1++)
{
jrd_req* request = tdbb->getRequest();
if (!EVL_boolean(tdbb, (*ptr1)->nod_arg[e_val_boolean]) && !(request->req_flags & req_null))
{
/* Validation error -- report result */
const char* value;
TEXT temp[128];
jrd_nod* node = (*ptr1)->nod_arg[e_val_value];
const dsc* desc = EVL_expr(tdbb, node);
const USHORT length = (desc && !(request->req_flags & req_null)) ?
MOV_make_string(desc, ttype_dynamic, &value,
reinterpret_cast<vary*>(temp), sizeof(temp) - 1) : 0;
if (!desc || (request->req_flags & req_null))
{
value = NULL_STRING_MARK;
}
else if (!length)
{
value = "";
}
else
{
const_cast<char*>(value)[length] = 0; // safe cast - data is actually on the stack
}
const TEXT* name = 0;
if (node->nod_type == nod_field)
{
const USHORT stream = (USHORT)(IPTR) node->nod_arg[e_fld_stream];
const USHORT id = (USHORT)(IPTR) node->nod_arg[e_fld_id];
const jrd_rel* relation = request->req_rpb[stream].rpb_relation;
const jrd_fld* field;
const vec<jrd_fld*>* vector = relation->rel_fields;
if (vector && id < vector->count() && (field = (*vector)[id]))
{
name = field->fld_name.c_str();
}
}
if (!name)
{
name = UNKNOWN_STRING_MARK;
}
ERR_post(Arg::Gds(isc_not_valid) << Arg::Str(name) << Arg::Str(value));
}
}
}
inline void verb_cleanup(thread_db* tdbb, jrd_tra* transaction)
{
/**************************************
*
* v e r b _ c l e a n u p
*
**************************************
*
* Functional description
* 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.
*
**************************************/
try {
VIO_verb_cleanup(tdbb, transaction);
}
catch (const Firebird::Exception&) {
if (tdbb->getDatabase()->dbb_flags & DBB_bugcheck) {
Firebird::status_exception::raise(tdbb->tdbb_status_vector);
}
BUGCHECK(290); // msg 290 error during savepoint backout
}
}