/Firebird-2.5.1.26351-0/src/dsql/dsql.cpp
# · C++ · 3406 lines · 2202 code · 500 blank · 704 comment · 439 complexity · 2a4aff76d17df2fb617ef6efae2dc1ca MD5 · raw file
Large files are truncated click here to view the full file
- /*
- * PROGRAM: Dynamic SQL runtime support
- * MODULE: dsql.cpp
- * DESCRIPTION: Local processing for External entry points.
- *
- * 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.07.06 Sean Leyne - Code Cleanup, removed "#ifdef READONLY_DATABASE"
- * conditionals, as the engine now fully supports
- * readonly databases.
- * December 2001 Mike Nordell: Major overhaul to (try to) make it C++
- * 2001.6.3 Claudio Valderrama: fixed a bad behaved loop in get_plan_info()
- * and get_rsb_item() that caused a crash when plan info was requested.
- * 2001.6.9 Claudio Valderrama: Added nod_del_view, nod_current_role and nod_breakleave.
- * 2002.10.29 Nickolay Samofatov: Added support for savepoints
- * 2002.10.29 Sean Leyne - Removed obsolete "Netware" port
- * 2004.01.16 Vlad Horsun: added support for EXECUTE BLOCK statement
- * Adriano dos Santos Fernandes
- */
- #include "firebird.h"
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include "../dsql/dsql.h"
- #include "../dsql/node.h"
- #include "../jrd/ibase.h"
- #include "../jrd/align.h"
- #include "../jrd/intl.h"
- #include "../jrd/jrd.h"
- #include "../dsql/Parser.h"
- #include "../dsql/ddl_proto.h"
- #include "../dsql/dsql_proto.h"
- #include "../dsql/errd_proto.h"
- #include "../dsql/gen_proto.h"
- #include "../dsql/hsh_proto.h"
- #include "../dsql/make_proto.h"
- #include "../dsql/movd_proto.h"
- #include "../dsql/parse_proto.h"
- #include "../dsql/pass1_proto.h"
- #include "../jrd/blb_proto.h"
- #include "../jrd/cmp_proto.h"
- #include "../jrd/gds_proto.h"
- #include "../jrd/inf_proto.h"
- #include "../jrd/jrd_proto.h"
- #include "../jrd/tra_proto.h"
- #include "../jrd/trace/TraceManager.h"
- #include "../jrd/trace/TraceDSQLHelpers.h"
- #include "../common/classes/init.h"
- #include "../common/utils_proto.h"
- #ifdef SCROLLABLE_CURSORS
- #include "../jrd/scroll_cursors.h"
- #endif
- #include "../common/StatusArg.h"
- #ifdef HAVE_CTYPE_H
- #include <ctype.h>
- #endif
- using namespace Jrd;
- using namespace Dsql;
- using namespace Firebird;
- static void close_cursor(thread_db*, dsql_req*);
- static USHORT convert(SLONG, UCHAR*);
- static void execute_blob(thread_db*, dsql_req*, USHORT, const UCHAR*, USHORT, const UCHAR*,
- USHORT, UCHAR*, USHORT, UCHAR*);
- static void execute_immediate(thread_db*, Attachment*, jrd_tra**,
- USHORT, const TEXT*, USHORT,
- USHORT, const UCHAR*, /*USHORT,*/ USHORT, const UCHAR*,
- USHORT, UCHAR*, /*USHORT,*/ USHORT, UCHAR*);
- static void execute_request(thread_db*, dsql_req*, jrd_tra**, USHORT, const UCHAR*,
- USHORT, const UCHAR*, USHORT, UCHAR*, USHORT, UCHAR*, bool);
- static SSHORT filter_sub_type(const dsql_nod*);
- static bool get_indices(SLONG*, const UCHAR**, SLONG*, SCHAR**);
- static USHORT get_request_info(thread_db*, dsql_req*, SLONG, UCHAR*);
- static bool get_rsb_item(SLONG*, const UCHAR**, SLONG*, SCHAR**, USHORT*, USHORT*);
- static dsql_dbb* init(Attachment*);
- static void map_in_out(dsql_req*, dsql_msg*, USHORT, const UCHAR*, USHORT, UCHAR*, const UCHAR* = 0);
- static USHORT parse_blr(USHORT, const UCHAR*, const USHORT, dsql_par*);
- static dsql_req* prepare(thread_db*, dsql_dbb*, jrd_tra*, USHORT, const TEXT*, USHORT, USHORT);
- static UCHAR* put_item(UCHAR, const USHORT, const UCHAR*, UCHAR*, const UCHAR* const, const bool copy = true);
- static void release_request(thread_db*, dsql_req*, bool);
- static void sql_info(thread_db*, dsql_req*, USHORT, const UCHAR*, ULONG, UCHAR*);
- static UCHAR* var_info(dsql_msg*, const UCHAR*, const UCHAR* const, UCHAR*,
- const UCHAR* const, USHORT, bool);
- static inline bool reqTypeWithCursor(REQ_TYPE req_type)
- {
- switch (req_type)
- {
- case REQ_SELECT:
- case REQ_SELECT_BLOCK:
- case REQ_SELECT_UPD:
- case REQ_EMBED_SELECT:
- case REQ_GET_SEGMENT:
- case REQ_PUT_SEGMENT:
- return true;
- }
- return false;
- }
- #ifdef DSQL_DEBUG
- unsigned DSQL_debug = 0;
- #endif
- namespace
- {
- const UCHAR db_hdr_info_items[] =
- {
- isc_info_db_sql_dialect,
- isc_info_ods_version,
- isc_info_ods_minor_version,
- #ifdef SCROLLABLE_CURSORS
- isc_info_base_level,
- #endif
- isc_info_db_read_only,
- isc_info_end
- };
- const UCHAR explain_info[] =
- {
- isc_info_access_path
- };
- const UCHAR record_info[] =
- {
- isc_info_req_update_count, isc_info_req_delete_count,
- isc_info_req_select_count, isc_info_req_insert_count
- };
- const UCHAR sql_records_info[] =
- {
- isc_info_sql_records
- };
- } // namespace
- #ifdef DSQL_DEBUG
- IMPLEMENT_TRACE_ROUTINE(dsql_trace, "DSQL")
- #endif
- dsql_dbb::~dsql_dbb()
- {
- thread_db* tdbb = JRD_get_thread_data();
- while (!dbb_requests.isEmpty())
- release_request(tdbb, dbb_requests[0], true);
- HSHD_finish(this);
- }
- /**
- DSQL_allocate_statement
- @brief Allocate a statement handle.
- @param tdbb
- @param attachment
- **/
- dsql_req* DSQL_allocate_statement(thread_db* tdbb, Attachment* attachment)
- {
- SET_TDBB(tdbb);
- dsql_dbb* const database = init(attachment);
- Jrd::ContextPoolHolder context(tdbb, database->createPool());
- // allocate the request block
- MemoryPool& pool = *tdbb->getDefaultPool();
- dsql_req* const request = FB_NEW(pool) CompiledStatement(pool);
- request->req_dbb = database;
- database->dbb_requests.add(request);
- return request;
- }
- /**
- DSQL_execute
- @brief Execute a non-SELECT dynamic SQL statement.
- @param tdbb
- @param tra_handle
- @param request
- @param in_blr_length
- @param in_blr
- @param in_msg_type
- @param in_msg_length
- @param in_msg
- @param out_blr_length
- @param out_blr
- @param out_msg_type OBSOLETE
- @param out_msg_length
- @param out_msg
- **/
- void DSQL_execute(thread_db* tdbb,
- jrd_tra** tra_handle,
- dsql_req* request,
- USHORT in_blr_length, const UCHAR* in_blr,
- USHORT in_msg_type, USHORT in_msg_length, const UCHAR* in_msg,
- USHORT out_blr_length, UCHAR* out_blr,
- /*USHORT out_msg_type,*/ USHORT out_msg_length, UCHAR* out_msg)
- {
- SET_TDBB(tdbb);
- Jrd::ContextPoolHolder context(tdbb, &request->req_pool);
- if (request->req_flags & REQ_orphan)
- {
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
- Arg::Gds(isc_bad_req_handle));
- }
- if ((SSHORT) in_msg_type == -1) {
- request->req_type = REQ_EMBED_SELECT;
- }
- // Only allow NULL trans_handle if we're starting a transaction
- if (!*tra_handle && request->req_type != REQ_START_TRANS)
- {
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
- Arg::Gds(isc_bad_trans_handle));
- }
- // If the request is a SELECT or blob statement then this is an open.
- // Make sure the cursor is not already open.
- if (reqTypeWithCursor(request->req_type)) {
- if (request->req_flags & REQ_cursor_open)
- {
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-502) <<
- Arg::Gds(isc_dsql_cursor_open_err));
- }
- }
- // A select with a non zero output length is a singleton select
- const bool singleton = (request->req_type == REQ_SELECT && out_msg_length != 0);
- if (request->req_type != REQ_EMBED_SELECT)
- {
- execute_request(tdbb, request, tra_handle,
- in_blr_length, in_blr, in_msg_length, in_msg,
- out_blr_length, out_blr, out_msg_length, out_msg,
- singleton);
- }
- else
- {
- request->req_transaction = *tra_handle;
- }
- // If the output message length is zero on a REQ_SELECT then we must
- // be doing an OPEN cursor operation.
- // If we do have an output message length, then we're doing
- // a singleton SELECT. In that event, we don't add the cursor
- // to the list of open cursors (it's not really open).
- if (reqTypeWithCursor(request->req_type) && !singleton)
- {
- request->req_flags |= REQ_cursor_open;
- TRA_link_cursor(request->req_transaction, request);
- }
- }
- /**
- DSQL_execute_immediate
- @brief Execute a non-SELECT dynamic SQL statement.
- @param tdbb
- @param attachment
- @param tra_handle
- @param length
- @param string
- @param dialect
- @param in_blr_length
- @param in_blr
- @param in_msg_type OBSOLETE
- @param in_msg_length
- @param in_msg
- @param out_blr_length
- @param out_blr
- @param out_msg_type OBSOLETE
- @param out_msg_length
- @param out_msg
- **/
- void DSQL_execute_immediate(thread_db* tdbb,
- Attachment* attachment,
- jrd_tra** tra_handle,
- USHORT length, const TEXT* string, USHORT dialect,
- USHORT in_blr_length, const UCHAR* in_blr,
- /*USHORT in_msg_type,*/ USHORT in_msg_length, const UCHAR* in_msg,
- USHORT out_blr_length, UCHAR* out_blr,
- /*USHORT out_msg_type,*/ USHORT out_msg_length, UCHAR* out_msg)
- {
- execute_immediate(tdbb, attachment, tra_handle, length,
- string, dialect,
- in_blr_length, in_blr, /*in_msg_type,*/ in_msg_length, in_msg,
- out_blr_length, out_blr, /*out_msg_type,*/ out_msg_length, out_msg);
- }
- /**
- DSQL_fetch
- @brief Fetch next record from a dynamic SQL cursor
- @param user_status
- @param req_handle
- @param blr_length
- @param blr
- @param msg_type OBSOLETE
- @param msg_length
- @param dsql_msg
- @param direction
- @param offset
- **/
- ISC_STATUS DSQL_fetch(thread_db* tdbb,
- dsql_req* request,
- USHORT blr_length, const UCHAR* blr,
- /*USHORT msg_type,*/ USHORT msg_length, UCHAR* dsql_msg_buf
- #ifdef SCROLLABLE_CURSORS
- , USHORT direction, SLONG offset
- #endif
- )
- {
- SET_TDBB(tdbb);
- Jrd::ContextPoolHolder context(tdbb, &request->req_pool);
- // if the cursor isn't open, we've got a problem
- if (reqTypeWithCursor(request->req_type))
- {
- if (!(request->req_flags & REQ_cursor_open))
- {
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-504) <<
- Arg::Gds(isc_dsql_cursor_err) <<
- Arg::Gds(isc_dsql_cursor_not_open));
- }
- }
- #ifdef SCROLLABLE_CURSORS
- // check whether we need to send an asynchronous scrolling message
- // to the engine; the engine will automatically advance one record
- // in the same direction as before, so optimize out messages of that
- // type
- if (request->req_type == REQ_SELECT && request->req_dbb->dbb_base_level >= 5)
- {
- switch (direction)
- {
- case isc_fetch_next:
- if (!(request->req_flags & REQ_backwards))
- offset = 0;
- else {
- direction = blr_forward;
- offset = 1;
- request->req_flags &= ~REQ_backwards;
- }
- break;
- case isc_fetch_prior:
- if (request->req_flags & REQ_backwards)
- offset = 0;
- else {
- direction = blr_backward;
- offset = 1;
- request->req_flags |= REQ_backwards;
- }
- break;
- case isc_fetch_first:
- direction = blr_bof_forward;
- offset = 1;
- request->req_flags &= ~REQ_backwards;
- break;
- case isc_fetch_last:
- direction = blr_eof_backward;
- offset = 1;
- request->req_flags |= REQ_backwards;
- break;
- case isc_fetch_absolute:
- direction = blr_bof_forward;
- request->req_flags &= ~REQ_backwards;
- break;
- case isc_fetch_relative:
- if (offset < 0) {
- direction = blr_backward;
- offset = -offset;
- request->req_flags |= REQ_backwards;
- }
- else {
- direction = blr_forward;
- request->req_flags &= ~REQ_backwards;
- }
- break;
- default:
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-804) <<
- Arg::Gds(isc_dsql_sqlda_err));
- }
- if (offset)
- {
- DSC desc;
- dsql_msg* message = (dsql_msg*) request->req_async;
- desc.dsc_dtype = dtype_short;
- desc.dsc_scale = 0;
- desc.dsc_length = sizeof(USHORT);
- desc.dsc_flags = 0;
- desc.dsc_address = (UCHAR*) & direction;
- dsql_par* offset_parameter = message->msg_parameters;
- dsql_par* parameter = offset_parameter->par_next;
- MOVD_move(tdbb, &desc, ¶meter->par_desc);
- desc.dsc_dtype = dtype_long;
- desc.dsc_scale = 0;
- desc.dsc_length = sizeof(SLONG);
- desc.dsc_flags = 0;
- desc.dsc_address = (UCHAR*) & offset;
- MOVD_move(tdbb, &desc, &offset_parameter->par_desc);
- DsqlCheckout dcoHolder(request->req_dbb);
- if (isc_receive2(tdbb->tdbb_status_vector, &request->req_request,
- message->msg_number, message->msg_length,
- message->msg_buffer, 0, direction, offset))
- {
- Firebird::status_exception::raise(tdbb->tdbb_status_vector);
- }
- }
- }
- #endif
- dsql_msg* message = (dsql_msg*) request->req_receive;
- // Set up things for tracing this call
- Attachment* att = request->req_dbb->dbb_attachment;
- TraceDSQLFetch trace(att, request);
- // Insure that the blr for the message is parsed, regardless of
- // whether anything is found by the call to receive.
- if (blr_length) {
- parse_blr(blr_length, blr, msg_length, message->msg_parameters);
- }
- if (request->req_type == REQ_GET_SEGMENT)
- {
- // For get segment, use the user buffer and indicator directly.
- dsql_par* parameter = request->req_blob->blb_segment;
- dsql_par* null = parameter->par_null;
- USHORT* ret_length = (USHORT *) (dsql_msg_buf + (IPTR) null->par_user_desc.dsc_address);
- UCHAR* buffer = dsql_msg_buf + (IPTR) parameter->par_user_desc.dsc_address;
- *ret_length = BLB_get_segment(tdbb, request->req_blob->blb_blob,
- buffer, parameter->par_user_desc.dsc_length);
- if (request->req_blob->blb_blob->blb_flags & BLB_eof)
- return 100;
- if (request->req_blob->blb_blob->blb_fragment_size)
- return 101;
- return 0;
- }
- JRD_receive(tdbb, request->req_request, message->msg_number, message->msg_length,
- message->msg_buffer, 0);
- const dsql_par* const eof = request->req_eof;
- const bool eof_reached = eof && !*((USHORT*) eof->par_desc.dsc_address);
- if (eof_reached)
- {
- trace.fetch(true, res_successful);
- return 100;
- }
- map_in_out(NULL, message, 0, blr, msg_length, dsql_msg_buf);
- trace.fetch(false, res_successful);
- return FB_SUCCESS;
- }
- /**
- DSQL_free_statement
- @brief Release request for a dsql statement
- @param user_status
- @param req_handle
- @param option
- **/
- void DSQL_free_statement(thread_db* tdbb, dsql_req* request, USHORT option)
- {
- SET_TDBB(tdbb);
- Jrd::ContextPoolHolder context(tdbb, &request->req_pool);
- if (option & DSQL_drop) {
- // Release everything associated with the request
- release_request(tdbb, request, true);
- }
- else if (option & DSQL_unprepare) {
- // Release everything but the request itself
- release_request(tdbb, request, false);
- }
- else if (option & DSQL_close) {
- // Just close the cursor associated with the request
- if (reqTypeWithCursor(request->req_type))
- {
- if (!(request->req_flags & REQ_cursor_open))
- {
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-501) <<
- Arg::Gds(isc_dsql_cursor_close_err));
- }
- close_cursor(tdbb, request);
- }
- }
- }
- /**
- DSQL_insert
- @brief Insert next record into a dynamic SQL cursor
- @param user_status
- @param req_handle
- @param blr_length
- @param blr
- @param msg_type OBSOLETE
- @param msg_length
- @param dsql_msg
- **/
- void DSQL_insert(thread_db* tdbb,
- dsql_req* request,
- USHORT blr_length, const UCHAR* blr,
- /*USHORT msg_type,*/ USHORT msg_length, const UCHAR* dsql_msg_buf)
- {
- SET_TDBB(tdbb);
- Jrd::ContextPoolHolder context(tdbb, &request->req_pool);
- if (request->req_flags & REQ_orphan)
- {
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
- Arg::Gds(isc_bad_req_handle));
- }
- // if the cursor isn't open, we've got a problem
- if (request->req_type == REQ_PUT_SEGMENT)
- {
- if (!(request->req_flags & REQ_cursor_open))
- {
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-504) <<
- Arg::Gds(isc_dsql_cursor_err) <<
- Arg::Gds(isc_dsql_cursor_not_open));
- }
- }
- dsql_msg* message = (dsql_msg*) request->req_receive;
- // Insure that the blr for the message is parsed, regardless of
- // whether anything is found by the call to receive.
- if (blr_length)
- parse_blr(blr_length, blr, msg_length, message->msg_parameters);
- if (request->req_type == REQ_PUT_SEGMENT) {
- // For put segment, use the user buffer and indicator directly.
- dsql_par* parameter = request->req_blob->blb_segment;
- const UCHAR* buffer = dsql_msg_buf + (IPTR) parameter->par_user_desc.dsc_address;
- BLB_put_segment(tdbb, request->req_blob->blb_blob, buffer,
- parameter->par_user_desc.dsc_length);
- }
- }
- /**
- DSQL_prepare
- @brief Prepare a statement for execution.
- @param user_status
- @param trans_handle
- @param req_handle
- @param length
- @param string
- @param dialect
- @param item_length
- @param items
- @param buffer_length
- @param buffer
- **/
- void DSQL_prepare(thread_db* tdbb,
- jrd_tra* transaction,
- dsql_req** req_handle,
- USHORT length, const TEXT* string, USHORT dialect,
- USHORT item_length, const UCHAR* items,
- USHORT buffer_length, UCHAR* buffer)
- {
- SET_TDBB(tdbb);
- dsql_req* const old_request = *req_handle;
- if (!old_request) {
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
- Arg::Gds(isc_bad_req_handle));
- }
- dsql_dbb* database = old_request->req_dbb;
- if (!database) {
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
- Arg::Gds(isc_bad_req_handle));
- }
- // check to see if old request has an open cursor
- if (old_request && (old_request->req_flags & REQ_cursor_open)) {
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-519) <<
- Arg::Gds(isc_dsql_open_cursor_request));
- }
- dsql_req* request = NULL;
- try {
- // Figure out which parser version to use
- // Since the API to dsql8_prepare is public and can not be changed, there needs to
- // be a way to send the parser version to DSQL so that the parser can compare the keyword
- // version to the parser version. To accomplish this, the parser version is combined with
- // the client dialect and sent across that way. In dsql8_prepare_statement, the parser version
- // and client dialect are separated and passed on to their final destinations. The information
- // is combined as follows:
- // Dialect * 10 + parser_version
- //
- // and is extracted in dsql8_prepare_statement as follows:
- // parser_version = ((dialect *10)+parser_version)%10
- // client_dialect = ((dialect *10)+parser_version)/10
- //
- // For example, parser_version = 1 and client dialect = 1
- //
- // combined = (1 * 10) + 1 == 11
- //
- // parser = (combined) %10 == 1
- // dialect = (combined) / 19 == 1
- //
- // If the parser version is not part of the dialect, then assume that the
- // connection being made is a local classic connection.
- USHORT parser_version;
- if ((dialect / 10) == 0)
- parser_version = 2;
- else {
- parser_version = dialect % 10;
- dialect /= 10;
- }
- // Allocate a new request block and then prepare the request. We want to
- // keep the old request around, as is, until we know that we are able
- // to prepare the new one.
- // It would be really *nice* to know *why* we want to
- // keep the old request around -- 1994-October-27 David Schnepper
- // Because that's the client's allocated statement handle and we
- // don't want to trash the context in it -- 2001-Oct-27 Ann Harrison
- request = prepare(tdbb, database, transaction, length, string, dialect, parser_version);
- // Can not prepare a CREATE DATABASE/SCHEMA statement
- if (request->req_type == REQ_CREATE_DB)
- {
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-530) <<
- Arg::Gds(isc_dsql_crdb_prepare_err));
- }
- request->req_flags |= REQ_prepared;
- // Now that we know that the new request exists, zap the old one.
- {
- Jrd::ContextPoolHolder context(tdbb, &old_request->req_pool);
- release_request(tdbb, old_request, true);
- }
- *req_handle = request;
- Jrd::ContextPoolHolder context(tdbb, &request->req_pool);
- sql_info(tdbb, request, item_length, items, buffer_length, buffer);
- }
- catch (const Firebird::Exception&)
- {
- if (request)
- {
- Jrd::ContextPoolHolder context(tdbb, &request->req_pool);
- release_request(tdbb, request, true);
- }
- throw;
- }
- }
- /**
- DSQL_set_cursor_name
- @brief Set a cursor name for a dynamic request
- @param user_status
- @param req_handle
- @param input_cursor
- @param type OBSOLETE
- **/
- void DSQL_set_cursor(thread_db* tdbb,
- dsql_req* request,
- const TEXT* input_cursor)
- //USHORT type)
- {
- SET_TDBB(tdbb);
- Jrd::ContextPoolHolder context(tdbb, &request->req_pool);
- const size_t MAX_CURSOR_LENGTH = 132 - 1;
- Firebird::string cursor = input_cursor;
- if (cursor[0] == '\"')
- {
- // Quoted cursor names eh? Strip'em.
- // Note that "" will be replaced with ".
- // The code is very strange, because it doesn't check for "" really
- // and thus deletes one isolated " in the middle of the cursor.
- for (Firebird::string::iterator i = cursor.begin(); i < cursor.end(); ++i)
- {
- if (*i == '\"') {
- cursor.erase(i);
- }
- }
- }
- else // not quoted name
- {
- const Firebird::string::size_type i = cursor.find(' ');
- if (i != Firebird::string::npos)
- {
- cursor.resize(i);
- }
- cursor.upper();
- }
- USHORT length = (USHORT) fb_utils::name_length(cursor.c_str());
- if (length == 0) {
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-502) <<
- Arg::Gds(isc_dsql_decl_err) <<
- Arg::Gds(isc_dsql_cursor_invalid));
- }
- if (length > MAX_CURSOR_LENGTH) {
- length = MAX_CURSOR_LENGTH;
- }
- cursor.resize(length);
- // If there already is a different cursor by the same name, bitch
- const dsql_sym* symbol = HSHD_lookup(request->req_dbb, cursor.c_str(), length, SYM_cursor, 0);
- if (symbol)
- {
- if (request->req_cursor == symbol)
- return;
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-502) <<
- Arg::Gds(isc_dsql_decl_err) <<
- Arg::Gds(isc_dsql_cursor_redefined) << Arg::Str(symbol->sym_string));
- }
- // If there already is a cursor and its name isn't the same, ditto.
- // We already know there is no cursor by this name in the hash table
- if (!request->req_cursor) {
- request->req_cursor = MAKE_symbol(request->req_dbb, cursor.c_str(), length, SYM_cursor, request);
- }
- else {
- fb_assert(request->req_cursor != symbol);
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-502) <<
- Arg::Gds(isc_dsql_decl_err) <<
- Arg::Gds(isc_dsql_cursor_redefined) << Arg::Str(request->req_cursor->sym_string));
- }
- }
- /**
- DSQL_sql_info
- @brief Provide information on dsql statement
- @param user_status
- @param req_handle
- @param item_length
- @param items
- @param info_length
- @param info
- **/
- void DSQL_sql_info(thread_db* tdbb,
- dsql_req* request,
- USHORT item_length, const UCHAR* items,
- ULONG info_length, UCHAR* info)
- {
- SET_TDBB(tdbb);
- Jrd::ContextPoolHolder context(tdbb, &request->req_pool);
- sql_info(tdbb, request, item_length, items, info_length, info);
- }
- /**
- close_cursor
- @brief Close an open cursor.
- @param request
- @param tdbb
- **/
- static void close_cursor(thread_db* tdbb, dsql_req* request)
- {
- SET_TDBB(tdbb);
- Attachment* attachment = request->req_dbb->dbb_attachment;
- if (request->req_request)
- {
- ThreadStatusGuard status_vector(tdbb);
- try
- {
- if (request->req_type == REQ_GET_SEGMENT || request->req_type == REQ_PUT_SEGMENT)
- {
- BLB_close(tdbb, request->req_blob->blb_blob);
- request->req_blob->blb_blob = NULL;
- }
- else
- {
- // Report some remaining fetches if any
- if (request->req_fetch_baseline)
- {
- TraceDSQLFetch trace(attachment, request);
- trace.fetch(true, res_successful);
- }
- if (request->req_traced && TraceManager::need_dsql_free(attachment))
- {
- TraceSQLStatementImpl stmt(request, NULL);
- TraceManager::event_dsql_free(attachment, &stmt, DSQL_close);
- }
- JRD_unwind_request(tdbb, request->req_request, 0);
- }
- }
- catch (Firebird::Exception&)
- {
- }
- }
- request->req_flags &= ~REQ_cursor_open;
- TRA_unlink_cursor(request->req_transaction, request);
- }
- /**
- convert
- @brief Convert a number to VAX form -- least significant bytes first.
- Return the length.
- @param number
- @param buffer
- **/
- // CVC: This routine should disappear in favor of a centralized function.
- static USHORT convert( SLONG number, UCHAR* buffer)
- {
- const UCHAR* p;
- #ifndef WORDS_BIGENDIAN
- p = (UCHAR*) &number;
- *buffer++ = *p++;
- *buffer++ = *p++;
- *buffer++ = *p++;
- *buffer++ = *p++;
- #else
- p = (UCHAR*) (&number + 1);
- *buffer++ = *--p;
- *buffer++ = *--p;
- *buffer++ = *--p;
- *buffer++ = *--p;
- #endif
- return 4;
- }
- /**
- execute_blob
- @brief Open or create a blob.
- @param tdbb
- @param request
- @param in_blr_length
- @param in_blr
- @param in_msg_length
- @param in_msg
- @param out_blr_length
- @param out_blr
- @param out_msg_length
- @param out_msg
- **/
- static void execute_blob(thread_db* tdbb,
- dsql_req* request,
- USHORT in_blr_length,
- const UCHAR* in_blr,
- USHORT in_msg_length,
- const UCHAR* in_msg,
- USHORT out_blr_length,
- UCHAR* out_blr,
- USHORT out_msg_length,
- UCHAR* out_msg)
- {
- UCHAR bpb[24];
- dsql_blb* blob = request->req_blob;
- map_in_out(request, blob->blb_open_in_msg, in_blr_length, in_blr, in_msg_length, NULL, in_msg);
- UCHAR* p = bpb;
- *p++ = isc_bpb_version1;
- SSHORT filter = filter_sub_type(blob->blb_to);
- if (filter) {
- *p++ = isc_bpb_target_type;
- *p++ = 2;
- *p++ = static_cast<UCHAR>(filter);
- *p++ = filter >> 8;
- }
- filter = filter_sub_type(blob->blb_from);
- if (filter) {
- *p++ = isc_bpb_source_type;
- *p++ = 2;
- *p++ = static_cast<UCHAR>(filter);
- *p++ = filter >> 8;
- }
- USHORT bpb_length = p - bpb;
- if (bpb_length == 1) {
- bpb_length = 0;
- }
- dsql_par* parameter = blob->blb_blob_id;
- const dsql_par* null = parameter->par_null;
- if (request->req_type == REQ_GET_SEGMENT)
- {
- bid* blob_id = (bid*) parameter->par_desc.dsc_address;
- if (null && *((SSHORT *) null->par_desc.dsc_address) < 0) {
- memset(blob_id, 0, sizeof(bid));
- }
- request->req_blob->blb_blob =
- BLB_open2(tdbb, request->req_transaction, blob_id, bpb_length, bpb, true);
- }
- else
- {
- request->req_request = NULL;
- bid* blob_id = (bid*) parameter->par_desc.dsc_address;
- memset(blob_id, 0, sizeof(bid));
- request->req_blob->blb_blob =
- BLB_create2(tdbb, request->req_transaction, blob_id, bpb_length, bpb);
- map_in_out(NULL, blob->blb_open_out_msg, out_blr_length, out_blr, out_msg_length, out_msg);
- }
- }
- /**
- execute_immediate
- @brief Common part of prepare and execute a statement.
- @param tdbb
- @param attachment
- @param tra_handle
- @param length
- @param string
- @param dialect
- @param in_blr_length
- @param in_blr
- @param in_msg_type OBSOLETE
- @param in_msg_length
- @param in_msg
- @param out_blr_length
- @param out_blr
- @param out_msg_type OBSOLETE
- @param out_msg_length
- @param out_msg
- **/
- static void execute_immediate(thread_db* tdbb,
- Attachment* attachment,
- jrd_tra** tra_handle,
- USHORT length, const TEXT* string, USHORT dialect,
- USHORT in_blr_length, const UCHAR* in_blr,
- /*USHORT in_msg_type,*/ USHORT in_msg_length, const UCHAR* in_msg,
- USHORT out_blr_length, UCHAR* out_blr,
- /*USHORT out_msg_type,*/ USHORT out_msg_length, UCHAR* out_msg)
- {
- SET_TDBB(tdbb);
- dsql_dbb* const database = init(attachment);
- dsql_req* request = NULL;
- try {
- // Figure out which parser version to use
- // Since the API to dsql8_execute_immediate is public and can not be changed, there needs to
- // be a way to send the parser version to DSQL so that the parser can compare the keyword
- // version to the parser version. To accomplish this, the parser version is combined with
- // the client dialect and sent across that way. In dsql8_execute_immediate, the parser version
- // and client dialect are separated and passed on to their final destinations. The information
- // is combined as follows:
- // Dialect * 10 + parser_version
- //
- // and is extracted in dsql8_execute_immediate as follows:
- // parser_version = ((dialect *10)+parser_version)%10
- // client_dialect = ((dialect *10)+parser_version)/10
- //
- // For example, parser_version = 1 and client dialect = 1
- //
- // combined = (1 * 10) + 1 == 11
- //
- // parser = (combined) %10 == 1
- // dialect = (combined) / 19 == 1
- //
- // If the parser version is not part of the dialect, then assume that the
- // connection being made is a local classic connection.
- USHORT parser_version;
- if ((dialect / 10) == 0)
- parser_version = 2;
- else {
- parser_version = dialect % 10;
- dialect /= 10;
- }
- request = prepare(tdbb, database, *tra_handle, length, string, dialect, parser_version);
- // Only allow NULL trans_handle if we're starting a transaction
- if (!*tra_handle && request->req_type != REQ_START_TRANS)
- {
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
- Arg::Gds(isc_bad_trans_handle));
- }
- Jrd::ContextPoolHolder context(tdbb, &request->req_pool);
- // A select with a non zero output length is a singleton select
- const bool singleton = (request->req_type == REQ_SELECT && out_msg_length != 0);
- execute_request(tdbb, request, tra_handle,
- in_blr_length, in_blr, in_msg_length, in_msg,
- out_blr_length, out_blr, out_msg_length, out_msg,
- singleton);
- release_request(tdbb, request, true);
- }
- catch (const Firebird::Exception&)
- {
- if (request)
- {
- Jrd::ContextPoolHolder context(tdbb, &request->req_pool);
- release_request(tdbb, request, true);
- }
- throw;
- }
- }
- /**
- execute_request
- @brief Execute a dynamic SQL statement.
- @param tdbb
- @param request
- @param trans_handle
- @param in_blr_length
- @param in_blr
- @param in_msg_length
- @param in_msg
- @param out_blr_length
- @param out_blr
- @param out_msg_length
- @param out_msg
- @param singleton
- **/
- static void execute_request(thread_db* tdbb,
- dsql_req* request,
- jrd_tra** tra_handle,
- USHORT in_blr_length, const UCHAR* in_blr,
- USHORT in_msg_length, const UCHAR* in_msg,
- USHORT out_blr_length, UCHAR* out_blr,
- USHORT out_msg_length, UCHAR* out_msg,
- bool singleton)
- {
- request->req_transaction = *tra_handle;
- switch (request->req_type)
- {
- case REQ_START_TRANS:
- JRD_start_transaction(tdbb, &request->req_transaction, 1, &request->req_dbb->dbb_attachment,
- request->req_blr_data.getCount(), request->req_blr_data.begin());
- *tra_handle = request->req_transaction;
- return;
- case REQ_COMMIT:
- JRD_commit_transaction(tdbb, &request->req_transaction);
- *tra_handle = NULL;
- return;
- case REQ_COMMIT_RETAIN:
- JRD_commit_retaining(tdbb, &request->req_transaction);
- return;
- case REQ_ROLLBACK:
- JRD_rollback_transaction(tdbb, &request->req_transaction);
- *tra_handle = NULL;
- return;
- case REQ_ROLLBACK_RETAIN:
- JRD_rollback_retaining(tdbb, &request->req_transaction);
- return;
- case REQ_CREATE_DB:
- case REQ_DDL:
- {
- TraceDSQLExecute trace(request->req_dbb->dbb_attachment, request);
- DDL_execute(request);
- trace.finish(false, res_successful);
- return;
- }
- case REQ_GET_SEGMENT:
- execute_blob(tdbb, request,
- in_blr_length, in_blr, in_msg_length, in_msg,
- out_blr_length, out_blr, out_msg_length, out_msg);
- return;
- case REQ_PUT_SEGMENT:
- execute_blob(tdbb, request,
- in_blr_length, in_blr, in_msg_length, in_msg,
- out_blr_length, out_blr, out_msg_length, out_msg);
- return;
- case REQ_SELECT:
- case REQ_SELECT_UPD:
- case REQ_EMBED_SELECT:
- case REQ_INSERT:
- case REQ_UPDATE:
- case REQ_UPDATE_CURSOR:
- case REQ_DELETE:
- case REQ_DELETE_CURSOR:
- case REQ_EXEC_PROCEDURE:
- case REQ_SET_GENERATOR:
- case REQ_SAVEPOINT:
- case REQ_EXEC_BLOCK:
- case REQ_SELECT_BLOCK:
- break;
- default:
- // Catch invalid request types
- fb_assert(false);
- }
- // If there is no data required, just start the request
- dsql_msg* message = request->req_send;
- if (message)
- map_in_out(request, message, in_blr_length, in_blr, in_msg_length, NULL, in_msg);
- // we need to map_in_out before tracing of execution start to let trace
- // manager know statement parameters values
- TraceDSQLExecute trace(request->req_dbb->dbb_attachment, request);
- if (!message)
- JRD_start(tdbb, request->req_request, request->req_transaction, 0);
- else
- {
- JRD_start_and_send(tdbb, request->req_request, request->req_transaction, message->msg_number,
- message->msg_length, message->msg_buffer,
- 0);
- }
- // Selectable execute block should get the "proc fetch" flag assigned,
- // which ensures that the savepoint stack is preserved while suspending
- if (request->req_type == REQ_SELECT_BLOCK)
- {
- fb_assert(request->req_request);
- request->req_request->req_flags |= req_proc_fetch;
- }
- // REQ_EXEC_BLOCK has no outputs so there are no out_msg
- // supplied from client side, but REQ_EXEC_BLOCK requires
- // 2-byte message for EOS synchronization
- const bool isBlock = (request->req_type == REQ_EXEC_BLOCK);
- message = request->req_receive;
- if ((out_msg_length && message) || isBlock)
- {
- char temp_buffer[FB_DOUBLE_ALIGN * 2];
- dsql_msg temp_msg;
- // Insure that the blr for the message is parsed, regardless of
- // whether anything is found by the call to receive.
- if (out_msg_length && out_blr_length) {
- parse_blr(out_blr_length, out_blr, out_msg_length, message->msg_parameters);
- }
- else if (!out_msg_length && isBlock) {
- message = &temp_msg;
- message->msg_number = 1;
- message->msg_length = 2;
- message->msg_buffer = (UCHAR*) FB_ALIGN((U_IPTR) temp_buffer, FB_DOUBLE_ALIGN);
- }
- JRD_receive(tdbb, request->req_request, message->msg_number, message->msg_length,
- message->msg_buffer, 0);
- if (out_msg_length)
- map_in_out(NULL, message, 0, out_blr, out_msg_length, out_msg);
- // if this is a singleton select, make sure there's in fact one record
- if (singleton)
- {
- USHORT counter;
- // Create a temp message buffer and try two more receives.
- // If both succeed then the first is the next record and the
- // second is either another record or the end of record message.
- // In either case, there's more than one record.
- UCHAR* message_buffer = (UCHAR*) gds__alloc((ULONG) message->msg_length);
- ISC_STATUS status = FB_SUCCESS;
- ISC_STATUS_ARRAY localStatus;
- for (counter = 0; counter < 2 && !status; counter++)
- {
- AutoSetRestore<ISC_STATUS*> autoStatus(&tdbb->tdbb_status_vector, localStatus);
- fb_utils::init_status(localStatus);
- try
- {
- JRD_receive(tdbb, request->req_request, message->msg_number,
- message->msg_length, message_buffer, 0);
- status = FB_SUCCESS;
- }
- catch (Firebird::Exception&)
- {
- status = tdbb->tdbb_status_vector[1];
- }
- }
- gds__free(message_buffer);
- // two successful receives means more than one record
- // a req_sync error on the first pass above means no records
- // a non-req_sync error on any of the passes above is an error
- if (!status)
- status_exception::raise(Arg::Gds(isc_sing_select_err));
- else if (status == isc_req_sync && counter == 1)
- status_exception::raise(Arg::Gds(isc_stream_eof));
- else if (status != isc_req_sync)
- status_exception::raise(localStatus);
- }
- }
- UCHAR buffer[20]; // Not used after retrieved
- if (request->req_type == REQ_UPDATE_CURSOR)
- {
- sql_info(tdbb, request, sizeof(sql_records_info), sql_records_info, sizeof(buffer), buffer);
- if (!request->req_updates)
- {
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-913) <<
- Arg::Gds(isc_deadlock) <<
- Arg::Gds(isc_update_conflict));
- }
- }
- else if (request->req_type == REQ_DELETE_CURSOR)
- {
- sql_info(tdbb, request, sizeof(sql_records_info), sql_records_info, sizeof(buffer), buffer);
- if (!request->req_deletes)
- {
- ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-913) <<
- Arg::Gds(isc_deadlock) <<
- Arg::Gds(isc_update_conflict));
- }
- }
- const bool have_cursor = reqTypeWithCursor(request->req_type) && !singleton;
- trace.finish(have_cursor, res_successful);
- }
- /**
- filter_sub_type
- @brief Determine the sub_type to use in filtering
- a blob.
- @param node
- **/
- static SSHORT filter_sub_type(const dsql_nod* node)
- {
- if (node->nod_type == nod_constant)
- return (SSHORT) node->getSlong();
- const dsql_par* parameter = (dsql_par*) node->nod_arg[e_par_parameter];
- const dsql_par* null = parameter->par_null;
- if (null)
- {
- if (*((SSHORT *) null->par_desc.dsc_address))
- return 0;
- }
- return *((SSHORT *) parameter->par_desc.dsc_address);
- }
- /**
- get_indices
- @brief Retrieve the indices from the index tree in
- the request info buffer (explain_ptr), and print them out
- in the plan buffer. Return true on success and false on failure.
- @param explain_length_ptr
- @param explain_ptr
- @param plan_length_ptr
- @param plan_ptr
- **/
- static bool get_indices(SLONG* explain_length_ptr, const UCHAR** explain_ptr,
- SLONG* plan_length_ptr, SCHAR** plan_ptr)
- {
- USHORT length;
- SLONG explain_length = *explain_length_ptr;
- const UCHAR* explain = *explain_ptr;
- SLONG& plan_length = *plan_length_ptr;
- SCHAR*& plan = *plan_ptr;
- // go through the index tree information, just
- // extracting the indices used
- explain_length--;
- switch (*explain++)
- {
- case isc_info_rsb_and:
- case isc_info_rsb_or:
- if (!get_indices(&explain_length, &explain, &plan_length, &plan))
- return false;
- if (!get_indices(&explain_length, &explain, &plan_length, &plan))
- return false;
- break;
- case isc_info_rsb_dbkey:
- break;
- case isc_info_rsb_index:
- explain_length--;
- length = *explain++;
- // if this isn't the first index, put out a comma
- if (plan[-1] != '(' && plan[-1] != ' ') {
- plan_length -= 2;
- if (plan_length < 0)
- return false;
- *plan++ = ',';
- *plan++ = ' ';
- }
- // now put out the index name
- if ((plan_length -= length) < 0)
- return false;
- explain_length -= length;
- while (length--)
- *plan++ = *explain++;
- break;
- default:
- return false;
- }
- *explain_length_ptr = explain_length;
- *explain_ptr = explain;
- //*plan_length_ptr = plan_length;
- //*plan_ptr = plan;
- return true;
- }
- /**
- DSQL_get_plan_info
- @brief Get the access plan for the request and turn
- it into a textual representation suitable for
- human reading.
- @param request
- @param buffer_length
- @param out_buffer
- @param realloc
- **/
- ULONG DSQL_get_plan_info(thread_db* tdbb,
- const dsql_req* request,
- SLONG buffer_length,
- SCHAR** out_buffer,
- const bool realloc)
- {
- if (!request->req_request) // DDL
- return 0;
- Firebird::HalfStaticArray<UCHAR, BUFFER_LARGE> explain_buffer;
- explain_buffer.resize(BUFFER_LARGE);
- // get the access path info for the underlying request from the engine
- try
- {
- JRD_request_info(tdbb, request->req_request, 0,
- sizeof(explain_info), explain_info,
- explain_buffer.getCount(), explain_buffer.begin());
- if (explain_buffer[0] == isc_info_truncated)
- {
- explain_buffer.resize(MAX_USHORT);
- JRD_request_info(tdbb, request->req_request, 0,
- sizeof(explain_info), explain_info,
- explain_buffer.getCount(), explain_buffer.begin());
- if (explain_buffer[0] == isc_info_truncated)
- {
- return 0;
- }
- }
- }
- catch (Firebird::Exception&)
- {
- return 0;
- }
- SCHAR* buffer_ptr = *out_buffer;
- SCHAR* plan;
- for (int i = 0; i < 2; i++)
- {
- const UCHAR* explain = explain_buffer.begin();
- if (*explain++ != isc_info_access_path)
- {
- return 0;
- }
- SLONG explain_length = (ULONG) *explain++;
- explain_length += (ULONG) (*explain++) << 8;
- plan = buffer_ptr;
- // CVC: What if we need to do 2nd pass? Those variables were only initialized
- // at the begining of the function hence they had trash the second time.
- USHORT join_count = 0, level = 0;
- const ULONG full_len = buffer_length;
- memset(plan, 0, full_len);
- // This is testing code for the limit case,
- // please do not enable for normal operations.
- /*
- if (full_len == ULONG(MAX_USHORT) - 4)
- {
- const size_t test_offset = 55000;
- memset(plan, '.', test_offset);
- plan += test_offset;
- buffer_length -= test_offset;
- }
- */
- // keep going until we reach the end of the explain info
- while (explain_length > 0 && buffer_length > 0)
- {
- if (!get_rsb_item(&explain_length, &explain, &buffer_length, &plan, &join_count, &level))
- {
- // don't allocate buffer of the same length second time
- // and let user know plan is incomplete
- if (buffer_ptr != *out_buffer ||
- (!realloc && full_len == ULONG(MAX_USHORT) - 4))
- {
- const ptrdiff_t diff = buffer_ptr + full_len - plan;
- if (diff < 3) {
- plan -= 3 - diff;
- }
- fb_assert(plan > buffer_ptr);
- *plan++ = '.';
- *plan++ = '.';
- *plan++ = '.';
- if (!realloc)
- return plan - buffer_ptr;
- ++i;
- break;
- }
- if (!realloc)
- return full_len - buffer_length;
- // assume we have run out of room in the buffer, try again with a larger one
- const size_t new_length = MAX_USHORT;
- char* const temp = static_cast<char*>(gds__alloc(new_length));
- if (!temp) {
- // NOMEM. Do not attempt one more try
- i++;
- break;
- }
- buffer_ptr = temp;
- buffer_length = (SLONG) new_length;
- break;
- }
- }
- if (buffer_ptr == *out_buffer)
- break;
- }
- *out_buffer = buffer_ptr;
- return plan - *out_buffer;
- }
- /**
- get_request_info
- @brief Get the records updated/deleted for record
- @param request
- @param buffer_length
- @param buffer
- **/
- static USHORT get_request_info(thread_db* tdbb,
- dsql_req* request,
- SLONG buffer_length,
- UCHAR* buffer)
- {
- if (!request->req_request) // DDL
- return 0;
- // get the info for the request from the engine
- try
- {
- JRD_request_info(tdbb, request->req_request, 0,
- sizeof(record_info), record_info,
- buffer_length, buffer);
- }
- catch (Firebird::Exception&)
- {
- return 0;
- }
- const UCHAR* data = buffer;
- request->req_updates = request->req_deletes = 0;
- request->req_selects = request->req_inserts = 0;
- UCHAR p;
- while ((p = *data++) != isc_info_end)
- {
- const USHORT data_length = static_cast<USHORT>(gds__vax_integer(data, 2));
- data += 2;
- switch (p)
- {
- case isc_info_req_update_count:
- request->req_updates = gds__vax_integer(data, data_length);
- break;
- case isc_info_req_delete_count:
- request->req_deletes = gds__vax_integer(data, data_length);
- break;
- case isc_info_req_select_count:
- request->req_selects = gds__vax_integer(data, data_length);
- break;
- case isc_info_req_insert_count:
- request->req_inserts = gds__vax_integer(data, data_length);
- break;
- default:
- break;
- }
- data += data_length;
- }
- return data - buffer;
- }
- /**
- get_rsb_item
- @brief Use recursion to print out a reverse-polish
- access plan of joins and join types. Return true on success
- and false on failure
- @param explain_length_ptr
- @param explain_ptr
- @param plan_length_ptr
- @param plan_ptr
- @param parent_join_count
- @param level_ptr
- **/
- static bool get_rsb_item(SLONG* explain_length_ptr,
- const UCHAR** explain_ptr,
- SLONG* plan_length_ptr,
- SCHAR** plan_ptr,
- USHORT* parent_join_count,
- USHORT* level_ptr)
- {
- const SCHAR* p;
- SSHORT rsb_type;
- SLONG explain_length = *explain_length_ptr;
- const UCHAR* explain = *explain_ptr;
- SLONG& plan_length = *plan_length_ptr;
- SCHAR*& plan = *plan_ptr;
- explain_length--;
- switch (*explain++)
- {
- case isc_info_rsb_begin:
- if (!*level_ptr)
- {
- // put out the PLAN prefix
- p = "\nPLAN ";
- if ((plan_length -= strlen(p)) < 0)
- return false;
- while (*p)
- *plan++ = *p++;
- }
- (*level_ptr)++;
- break;
- case isc_info_rsb_end:
- if (*level_ptr) {
- (*level_ptr)--;
- }
- // else --*parent_join_count; ???
- break;
- case isc_info_rsb_relation:
- // for the single relation case, initiate
- // the relation with a parenthesis
- if (!*parent_join_count) {
- if (--plan_length < 0)
- return false;
- *plan++ = '(';
- }
- // if this isn't the first relation, put out a comma
- if (plan[-1] != '(') {
- plan_length -= 2;
- if (plan_length < 0)
- return false;
- *plan++ = ',';
- *plan++ = ' ';
- }
- // put out the relation name
- { // scope to keep length local.
- explain_length--;
- SSHORT length = (USHORT) *explain++;
- explain_length -= length;
- if ((plan_length -= length) < 0)
- return false;
- while (length--)
- *plan++ = *explain++;
- } // scope
- break;
- case isc_info_rsb_type:
- explain_length--;
- // for stream types which have multiple substreams, print out
- // the stream type and recursively print out the substreams so
- // we will know where to put the parentheses
- switch (rsb_type = *explain++)
- {
- case isc_info_rsb_union:
- case isc_info_rsb_recursive:
- // put out all the substreams of the join
- { // scope to have union_count, union_level and union_join_count local.
- explain_length--;
- fb_assert(*explain > 0U);
- USHORT union_count = (USHORT) *explain++ - 1;
- // finish the first union member
- USHORT union_level = *level_ptr;
- USHORT union_join_count = 0;
- while (explain_length > 0 && plan_length > 0)
- {
- if (!get_rsb_item(&explain_length, &explain, &plan_length, &plan,
- &union_join_count, &union_level))
- {
- return false;
- }
- if (union_level == *level_ptr)
- break;
- }
- // for the rest of the members, start the level at 0 so each
- // gets its own "PLAN ... " line
- while (union_count)
- {
- union_join_count = 0;
- union_level = 0;
- while (explain_length > 0 && plan_length > 0)
- {
- if (!get_rsb_item(&explain_length, &explain, &plan_length,
- &plan, &union_join_count, &union_level))
- {
- return false;
- }
- if (!union_level)
- break;
- }
- union_count--;
- }
- } // scope
- break;
- case isc_info_rsb_cross:
- case isc_info_rsb_left_cross:
- case isc_info_rsb_merge:
- // if this join is itself part of a join list,
- // but not the first item, then put out a comma
- if (*parent_join_count && plan[-1] != '(')
- {
- plan_length -= 2;
- if (plan_length < 0)
- return false;
- *plan++ = ',';
- *plan++ = ' ';
- }
- // put out the join type
- if (rsb_type == isc_info_rsb_cross || rsb_type == isc_info_rsb_left_cross)
- {
- p = "JOIN (";
- }
- else {
- p = "MERGE (";
- }
- if ((plan_length -= strlen(p)) < 0)
- return false;
- while (*p)
- *plan++ = *p++;
- // put out all the substreams of the join
- explain_length--;
- { // scope to have join_count local.
- USHORT join_count = (USHORT) *explain++;
- while (join_count && explain_length > 0 && plan_length > 0)
- {
- if (!get_rsb_item(&explain_length, &explain, &plan_length,
- &plan, &join_count, level_ptr))
- {
- return false;
- }
- // CVC: Here's the additional stop condition.
- if (!*level_ptr) {
- break;
- }
- }
- } // scope
- // put out the final parenthesis for the join
- if (--plan_length < 0)
- return false;
- *plan++ = ')';
- // this qualifies as a stream, so decrement the join count
- if (*parent_join_count)
- -- * parent_join_count;
- break;
- case isc_info_rsb_indexed:
- case isc_info_rsb_navigate:
- case isc_info_rsb_sequential:
- case isc_info_rsb_ext_sequential:
- case isc_info_rsb_ext_indexed:
- case isc_info_rsb_virt_sequential:
- switch (rsb_type)
- {
- case isc_info_rsb_indexed:
- case isc_info_rsb_ext_indexed:
- p = " INDEX (";
- break;
- case isc_info_rsb_navigate:
- p = " ORDER ";
- break;
- default:
- p = " NATURAL";
- }
- if ((plan_length -= strlen(p)) < 0)
- return false;
- while (*p)
- *plan++ = *p++;
- // print out additional index information
- if (rsb_type == isc_info_rsb_indexed || rsb_type == isc_info_rsb_navigate ||
- rsb_type == isc_info_rsb_ext_indexed)
- {
- if (!get_indices(&explain_length, &explain, &plan_length, &plan))
- return false;
- }
- if (rsb_type == isc_info_rsb_navigate && *explain == isc_info_rsb_indexed)
- {
- USHORT idx_count = 1;
- if (!get_rsb_item(&explain_length, &explain, &plan_length, &plan, &idx_count, level_ptr))
- {
- return false;
- }
- }
- if (rsb_type == isc_info_rsb_indexed || rsb_type == isc_info_rsb_ext_indexed)
- {
- if (--plan_length < 0)
- return false;
- *plan++ = ')';
- }
- // detect the end of a single relation and put out a final parenthesis
- if (!*parent_join_count)
- {
- if (--plan_length < 0)
- return false;
- *plan++ = ')';
- }
- // this also qualifies as a stream, so decrement the join count
- if (*parent_join_count)
- -- * parent_join_count;
- break;
- case isc_info_rsb_sort:
- // if this sort is on behalf of a union, don't bother to
- // print out the sort, because unions handle the sort on all
- // substreams at once, and a plan maps to each substream
- // in the union, so the sort doesn't really apply to a particular plan
- if (explain_length > 2 && (explain[0] == isc_info_rsb_begin) &&
- (explain[1] == isc_info_rsb_type) && (explain[2] == isc_info_rsb_union))
- {
- break;
- }
- // if this isn't the first item in the list, put out a comma
- if (*parent_join_count && plan[-1] != '(')
- {
- plan_length -= 2;
- if (plan_length < 0)
- return false;
- *plan++ = ',';
- *plan++ = ' ';
- }
- p = "SORT (";
- if ((plan_length -= strlen(p)) < 0)
- return false;
- while (*p)
- *plan++ = *p++;
- // the rsb_sort should always be followed by a begin...end block,
- // allowing us to include everything inside the sort in parentheses
- { // scope to have save_level local.
- const USHORT save_level = *level_ptr;
- while (explain_length > 0 && plan_length > 0)
- {
- if (!get_rsb_item(&explain_length, &explain, &plan_length,
- &plan, parent_join_count,…