/* * 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 #include #include #include "../dsql/dsql.h" #include "../dsql/node.h" #include "../jrd/ibase.h" #include "../jrd/align.h" #include "../jrd/intl.h" #include "../jrd/intlobj_new.h" #include "../jrd/jrd.h" #include "../jrd/CharSet.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/intl_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" #include "../common/StatusArg.h" #ifdef HAVE_CTYPE_H #include #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*, Jrd::Attachment*, jrd_tra**, USHORT, const TEXT*, USHORT, USHORT, const UCHAR*, /*USHORT,*/ USHORT, const UCHAR*, USHORT, UCHAR*, /*USHORT,*/ USHORT, UCHAR*, bool); 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(dsql_req*, 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(Jrd::Attachment*); static void map_in_out(dsql_req*, bool, const dsql_msg*, USHORT, const UCHAR*, USHORT, UCHAR*, const UCHAR* = 0); static USHORT parse_blr(dsql_req*, USHORT, const UCHAR*, const USHORT, const Array&); static dsql_req* prepareRequest(thread_db*, dsql_dbb*, jrd_tra*, USHORT, const TEXT*, USHORT, USHORT, bool); static dsql_req* prepareStatement(thread_db*, dsql_dbb*, jrd_tra*, USHORT, const TEXT*, USHORT, USHORT, bool); static UCHAR* put_item(UCHAR, const USHORT, const UCHAR*, UCHAR*, const UCHAR* const, const bool copy = true); static void release_statement(DsqlCompiledStatement* statement); 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(const dsql_msg*, const UCHAR*, const UCHAR* const, UCHAR*, const UCHAR* const, USHORT, bool); static inline bool reqTypeWithCursor(REQ_TYPE type) { switch (type) { case REQ_SELECT: case REQ_SELECT_BLOCK: case REQ_SELECT_UPD: 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, 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() { HSHD_finish(this); } /** DSQL_allocate_statement @brief Allocate a statement handle. @param tdbb @param attachment **/ dsql_req* DSQL_allocate_statement(thread_db* tdbb, Jrd::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(); DsqlCompiledStatement* statement = FB_NEW(pool) DsqlCompiledStatement(pool); dsql_req* const request = FB_NEW(pool) dsql_req(statement); request->req_dbb = database; 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_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_length, UCHAR* out_msg) { SET_TDBB(tdbb); Jrd::ContextPoolHolder context(tdbb, &request->getPool()); const DsqlCompiledStatement* statement = request->getStatement(); if (statement->flags & DsqlCompiledStatement::FLAG_ORPHAN) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) << Arg::Gds(isc_bad_req_handle)); } if ((SSHORT) in_msg_type == -1) request->req_flags |= dsql_req::FLAG_EMBEDDED; // Only allow NULL trans_handle if we're starting a transaction if (!*tra_handle && statement->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(statement->type)) { if (request->req_flags & dsql_req::FLAG_OPENED_CURSOR) { 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 bool singleton; if (statement->type == REQ_SELECT && out_msg_length != 0) singleton = true; else singleton = false; if (!(request->req_flags & dsql_req::FLAG_EMBEDDED)) { 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(statement->type) && !singleton) { request->req_flags |= dsql_req::FLAG_OPENED_CURSOR; 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_length @param in_msg @param out_blr_length @param out_blr @param out_msg_length @param out_msg **/ void DSQL_execute_immediate(thread_db* tdbb, Jrd::Attachment* attachment, jrd_tra** tra_handle, USHORT length, const TEXT* string, USHORT dialect, 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 isInternalRequest) { execute_immediate(tdbb, attachment, tra_handle, length, string, dialect, in_blr_length, in_blr, in_msg_length, in_msg, out_blr_length, out_blr, out_msg_length, out_msg, isInternalRequest); } /** DSQL_fetch @brief Fetch next record from a dynamic SQL cursor @param user_status @param req_handle @param blr_length @param blr @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_length, UCHAR* dsql_msg_buf) { SET_TDBB(tdbb); Jrd::ContextPoolHolder context(tdbb, &request->getPool()); // if the cursor isn't open, we've got a problem if (!(request->req_flags & dsql_req::FLAG_OPENED_CURSOR)) { 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->getStatement()->getReceiveMsg(); // Set up things for tracing this call Jrd::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(request, blr_length, blr, msg_length, message->msg_parameters); } if (request->getStatement()->type == REQ_GET_SEGMENT) { // For get segment, use the user buffer and indicator directly. const dsql_par* parameter = request->getStatement()->getBlob()->blb_segment; const dsql_par* null = parameter->par_null; dsc userDesc; if (!request->req_user_descs.get(parameter, userDesc)) userDesc.clear(); dsc userNullDesc; if (!request->req_user_descs.get(null, userNullDesc)) userNullDesc.clear(); USHORT* ret_length = (USHORT *) (dsql_msg_buf + (IPTR) userNullDesc.dsc_address); UCHAR* buffer = dsql_msg_buf + (IPTR) userDesc.dsc_address; *ret_length = BLB_get_segment(tdbb, request->req_blb, buffer, userDesc.dsc_length); if (request->req_blb->blb_flags & BLB_eof) return 100; if (request->req_blb->blb_fragment_size) return 101; return 0; } UCHAR* msgBuffer = request->req_msg_buffers[message->msg_buffer_number]; JRD_receive(tdbb, request->req_request, message->msg_number, message->msg_length, msgBuffer, 0); const dsql_par* const eof = request->getStatement()->getEof(); dsc eofDesc = eof->par_desc; eofDesc.dsc_address = msgBuffer + IPTR(eofDesc.dsc_address); const bool eof_reached = eof && !*((USHORT*) eofDesc.dsc_address); if (eof_reached) { trace.fetch(true, res_successful); return 100; } map_in_out(request, true, 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->getPool()); 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 (!(request->req_flags & dsql_req::FLAG_OPENED_CURSOR)) { 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_length @param dsql_msg **/ void DSQL_insert(thread_db* tdbb, dsql_req* request, USHORT blr_length, const UCHAR* blr, USHORT msg_length, const UCHAR* dsql_msg_buf) { SET_TDBB(tdbb); Jrd::ContextPoolHolder context(tdbb, &request->getPool()); if (request->getStatement()->flags & DsqlCompiledStatement::FLAG_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->getStatement()->type == REQ_PUT_SEGMENT) { if (!(request->req_flags & dsql_req::FLAG_OPENED_CURSOR)) { 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->getStatement()->getReceiveMsg(); // 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(request, blr_length, blr, msg_length, message->msg_parameters); if (request->getStatement()->type == REQ_PUT_SEGMENT) { // For put segment, use the user buffer and indicator directly. const dsql_par* parameter = request->getStatement()->getBlob()->blb_segment; dsc userDesc; if (!request->req_user_descs.get(parameter, userDesc)) userDesc.clear(); const UCHAR* buffer = dsql_msg_buf + (IPTR) userDesc.dsc_address; BLB_put_segment(tdbb, request->req_blb, buffer, userDesc.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, bool isInternalRequest) { 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 & dsql_req::FLAG_OPENED_CURSOR)) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-519) << Arg::Gds(isc_dsql_open_cursor_request)); } dsql_req* request = NULL; if (!string) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << // Unexpected end of command // CVC: Nothing will be line 1, column 1 for the user. Arg::Gds(isc_command_end_err2) << Arg::Num(1) << Arg::Num(1)); } if (!length) { length = strlen(string); } 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 = prepareRequest(tdbb, database, transaction, length, string, dialect, parser_version, isInternalRequest); // Can not prepare a CREATE DATABASE/SCHEMA statement if (request->getStatement()->type == REQ_CREATE_DB) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-530) << Arg::Gds(isc_dsql_crdb_prepare_err)); } // Now that we know that the new request exists, zap the old one. { Jrd::ContextPoolHolder context(tdbb, &old_request->getPool()); release_request(tdbb, old_request, true); } *req_handle = request; Jrd::ContextPoolHolder context(tdbb, &request->getPool()); sql_info(tdbb, request, item_length, items, buffer_length, buffer); } catch (const Firebird::Exception&) { if (request) { Jrd::ContextPoolHolder context(tdbb, &request->getPool()); release_request(tdbb, request, true); } throw; } } /** DSQL_set_cursor @brief Set a cursor name for a dynamic request @param tdbb @param req_handle @param input_cursor **/ void DSQL_set_cursor(thread_db* tdbb, dsql_req* request, const TEXT* input_cursor) { SET_TDBB(tdbb); Jrd::ContextPoolHolder context(tdbb, &request->getPool()); 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->getPool()); 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); Jrd::Attachment* attachment = request->req_dbb->dbb_attachment; if (request->req_request) { ThreadStatusGuard status_vector(tdbb); try { if (request->getStatement()->type == REQ_GET_SEGMENT || request->getStatement()->type == REQ_PUT_SEGMENT) { BLB_close(tdbb, request->req_blb); request->req_blb = 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 &= ~dsql_req::FLAG_OPENED_CURSOR; 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]; const dsql_blb* blob = request->getStatement()->getBlob(); map_in_out(request, false, 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(request, blob->blb_to); if (filter) { *p++ = isc_bpb_target_type; *p++ = 2; *p++ = static_cast(filter); *p++ = filter >> 8; } filter = filter_sub_type(request, blob->blb_from); if (filter) { *p++ = isc_bpb_source_type; *p++ = 2; *p++ = static_cast(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; UCHAR* msgBuffer = request->req_msg_buffers[parameter->par_message->msg_buffer_number]; dsc desc = parameter->par_desc; desc.dsc_address = msgBuffer + IPTR(desc.dsc_address); bid* blob_id = (bid*) desc.dsc_address; if (request->getStatement()->type == REQ_GET_SEGMENT) { if (null) { desc = null->par_desc; desc.dsc_address = msgBuffer + IPTR(desc.dsc_address); if (*((SSHORT*) desc.dsc_address) < 0) memset(blob_id, 0, sizeof(bid)); } request->req_blb = BLB_open2(tdbb, request->req_transaction, blob_id, bpb_length, bpb, true); } else { request->req_request = NULL; memset(blob_id, 0, sizeof(bid)); request->req_blb = BLB_create2(tdbb, request->req_transaction, blob_id, bpb_length, bpb); map_in_out(request, true, 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_length @param in_msg @param out_blr_length @param out_blr @param out_msg_length @param out_msg **/ static void execute_immediate(thread_db* tdbb, Jrd::Attachment* attachment, jrd_tra** tra_handle, USHORT length, const TEXT* string, USHORT dialect, 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 isInternalRequest) { SET_TDBB(tdbb); if (!string) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << // Unexpected end of command // CVC: Nothing will be line 1, column 1 for the user. Arg::Gds(isc_command_end_err2) << Arg::Num(1) << Arg::Num(1)); } if (!length) { length = strlen(string); } 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 = prepareRequest(tdbb, database, *tra_handle, length, string, dialect, parser_version, isInternalRequest); Jrd::ContextPoolHolder context(tdbb, &request->getPool()); 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, false); release_request(tdbb, request, true); } catch (const Firebird::Exception&) { if (request) { Jrd::ContextPoolHolder context(tdbb, &request->getPool()); 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->getStatement()->type) { case REQ_START_TRANS: JRD_start_transaction(tdbb, &request->req_transaction, 1, &request->req_dbb->dbb_attachment, request->getStatement()->getBlrData().getCount(), request->getStatement()->getBlrData().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_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 const dsql_msg* message = request->getStatement()->getSendMsg(); if (message) map_in_out(request, false, 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 { UCHAR* msgBuffer = request->req_msg_buffers[message->msg_buffer_number]; JRD_start_and_send(tdbb, request->req_request, request->req_transaction, message->msg_number, message->msg_length, msgBuffer, 0); } // 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->getStatement()->type == REQ_EXEC_BLOCK); message = request->getStatement()->getReceiveMsg(); if ((out_msg_length && message) || isBlock) { UCHAR temp_buffer[FB_DOUBLE_ALIGN * 2]; dsql_msg temp_msg(*getDefaultMemoryPool()); // Insure that the blr for the message is parsed, regardless of // whether anything is found by the call to receive. UCHAR* msgBuffer = request->req_msg_buffers[message->msg_buffer_number]; if (out_msg_length && out_blr_length) { parse_blr(request, out_blr_length, out_blr, out_msg_length, message->msg_parameters); } else if (!out_msg_length && isBlock) { message = &temp_msg; temp_msg.msg_number = 1; temp_msg.msg_length = 2; msgBuffer = (UCHAR*) FB_ALIGN((U_IPTR) temp_buffer, FB_DOUBLE_ALIGN); } JRD_receive(tdbb, request->req_request, message->msg_number, message->msg_length, msgBuffer, 0); if (out_msg_length) map_in_out(request, true, 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 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) ERRD_post(Arg::Gds(isc_sing_select_err)); else if (status == isc_req_sync && counter == 1) ERRD_post(Arg::Gds(isc_stream_eof)); else if (status != isc_req_sync) status_exception::raise(localStatus); } } UCHAR buffer[20]; // Not used after retrieved if (request->getStatement()->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->getStatement()->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->getStatement()->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(dsql_req* request, 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; dsc desc; UCHAR* msgBuffer = request->req_msg_buffers[parameter->par_message->msg_buffer_number]; if (null) { desc = null->par_desc; desc.dsc_address = msgBuffer + IPTR(null->par_desc.dsc_address); if (*((SSHORT*) desc.dsc_address)) return 0; } desc = parameter->par_desc; desc.dsc_address = msgBuffer + IPTR(parameter->par_desc.dsc_address); return *((SSHORT*) 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 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(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(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, level_ptr)) { return false; } if (*level_ptr == save_level) break; } if (--plan_length < 0) return false; *plan++ = ')'; } // scope break; default: break; } // switch (rsb_type = *explain++) break; default: break; } *explain_length_ptr = explain_length; *explain_ptr = explain; //*plan_length_ptr = plan_length; //*plan_ptr = plan; return true; } /** init @brief Initialize dynamic SQL. This is called only once. @param db_handle **/ static dsql_dbb* init(Jrd::Attachment* attachment) { thread_db* tdbb = JRD_get_thread_data(); if (!attachment->att_dsql_instance) { MemoryPool& pool = *attachment->att_database->createPool(); dsql_dbb* const database = FB_NEW(pool) dsql_dbb(pool); database->dbb_attachment = attachment; database->dbb_database = attachment->att_database; attachment->att_dsql_instance = database; UCHAR buffer[BUFFER_TINY]; try { ThreadStatusGuard status_vector(tdbb); INF_database_info(db_hdr_info_items, sizeof(db_hdr_info_items), buffer, sizeof(buffer)); } catch (Firebird::Exception&) { return database; } const UCHAR* data = buffer; UCHAR p; while ((p = *data++) != isc_info_end) { const SSHORT l = static_cast(gds__vax_integer(data, 2)); data += 2; switch (p) { case isc_info_db_sql_dialect: fb_assert(l == 1); database->dbb_db_SQL_dialect = (USHORT) data[0]; break; case isc_info_ods_version: database->dbb_ods_version = gds__vax_integer(data, l); if (database->dbb_ods_version < 12) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-804) << Arg::Gds(isc_dsql_too_old_ods) << Arg::Num(12)); } break; case isc_info_ods_minor_version: database->dbb_minor_version = gds__vax_integer(data, l); break; case isc_info_db_read_only: fb_assert(l == 1); database->dbb_read_only = (USHORT) data[0] ? true : false; break; default: break; } data += l; } } return attachment->att_dsql_instance; } /** map_in_out @brief Map data from external world into message or from message to external world. @param request @param message @param blr_length @param blr @param msg_length @param dsql_msg_buf @param in_dsql_msg_buf **/ static void map_in_out(dsql_req* request, bool toExternal, const dsql_msg* message, USHORT blr_length, const UCHAR* blr, USHORT msg_length, UCHAR* dsql_msg_buf, const UCHAR* in_dsql_msg_buf) { USHORT count = parse_blr(request, blr_length, blr, msg_length, message->msg_parameters); bool err = false; for (size_t i = 0; i < message->msg_parameters.getCount(); ++i) { dsql_par* parameter = message->msg_parameters[i]; if (parameter->par_index) { // Make sure the message given to us is long enough dsc desc; if (!request->req_user_descs.get(parameter, desc)) desc.clear(); USHORT length = (IPTR) desc.dsc_address + desc.dsc_length; if (length > msg_length || !desc.dsc_dtype) { err = true; break; } UCHAR* msgBuffer = request->req_msg_buffers[parameter->par_message->msg_buffer_number]; SSHORT* flag = NULL; dsql_par* const null_ind = parameter->par_null; if (null_ind != NULL) { dsc userNullDesc; if (!request->req_user_descs.get(null_ind, userNullDesc)) userNullDesc.clear(); const USHORT null_offset = (IPTR) userNullDesc.dsc_address; length = null_offset + sizeof(SSHORT); if (length > msg_length) { err = true; break; } dsc nullDesc = null_ind->par_desc; nullDesc.dsc_address = msgBuffer + IPTR(nullDesc.dsc_address); if (toExternal) { flag = reinterpret_cast(dsql_msg_buf + null_offset); *flag = *reinterpret_cast(nullDesc.dsc_address); } else { flag = reinterpret_cast(nullDesc.dsc_address); *flag = *reinterpret_cast(in_dsql_msg_buf + null_offset); } } dsc parDesc = parameter->par_desc; parDesc.dsc_address = msgBuffer + IPTR(parDesc.dsc_address); if (toExternal) { desc.dsc_address = dsql_msg_buf + (IPTR) desc.dsc_address; if (!flag || *flag >= 0) { MOVD_move(&parDesc, &desc); } else { memset(desc.dsc_address, 0, desc.dsc_length); } } else if (!flag || *flag >= 0) { if (!(parDesc.dsc_flags & DSC_null)) { // Safe cast because desc is used as source only. desc.dsc_address = const_cast(in_dsql_msg_buf) + (IPTR) desc.dsc_address; MOVD_move(&desc, &parDesc); } } else { memset(parDesc.dsc_address, 0, parDesc.dsc_length); } count--; } } // If we got here because the loop was exited early or if part of the // message given to us hasn't been used, complain. if (err || count) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-804) << Arg::Gds(isc_dsql_sqlda_err)); } const DsqlCompiledStatement* statement = request->getStatement(); const dsql_par* parameter; const dsql_par* dbkey; if (!toExternal && (dbkey = statement->getParentDbKey()) && (parameter = statement->getDbKey())) { UCHAR* parentMsgBuffer = statement->getParentRequest() ? statement->getParentRequest()->req_msg_buffers[dbkey->par_message->msg_buffer_number] : NULL; UCHAR* msgBuffer = request->req_msg_buffers[parameter->par_message->msg_buffer_number]; fb_assert(parentMsgBuffer); dsc parentDesc = dbkey->par_desc; parentDesc.dsc_address = parentMsgBuffer + IPTR(parentDesc.dsc_address); dsc desc = parameter->par_desc; desc.dsc_address = msgBuffer + IPTR(desc.dsc_address); MOVD_move(&parentDesc, &desc); dsql_par* null_ind = parameter->par_null; if (null_ind != NULL) { desc = null_ind->par_desc; desc.dsc_address = msgBuffer + IPTR(desc.dsc_address); SSHORT* flag = (SSHORT*) desc.dsc_address; *flag = 0; } } const dsql_par* rec_version; if (!toExternal && (rec_version = statement->getParentRecVersion()) && (parameter = statement->getRecVersion())) { UCHAR* parentMsgBuffer = statement->getParentRequest() ? statement->getParentRequest()->req_msg_buffers[rec_version->par_message->msg_buffer_number] : NULL; UCHAR* msgBuffer = request->req_msg_buffers[parameter->par_message->msg_buffer_number]; fb_assert(parentMsgBuffer); dsc parentDesc = rec_version->par_desc; parentDesc.dsc_address = parentMsgBuffer + IPTR(parentDesc.dsc_address); dsc desc = parameter->par_desc; desc.dsc_address = msgBuffer + IPTR(desc.dsc_address); MOVD_move(&parentDesc, &desc); dsql_par* null_ind = parameter->par_null; if (null_ind != NULL) { desc = null_ind->par_desc; desc.dsc_address = msgBuffer + IPTR(desc.dsc_address); SSHORT* flag = (SSHORT*) desc.dsc_address; *flag = 0; } } } /** parse_blr @brief Parse the message of a blr request. @param blr_length @param blr @param msg_length @param parameters **/ static USHORT parse_blr(dsql_req* request, USHORT blr_length, const UCHAR* blr, const USHORT msg_length, const Array& parameters_list) { HalfStaticArray parameters; for (size_t i = 0; i < parameters_list.getCount(); ++i) { dsql_par* param = parameters_list[i]; if (param->par_index) { if (param->par_index > parameters.getCount()) parameters.grow(param->par_index); fb_assert(!parameters[param->par_index - 1]); parameters[param->par_index - 1] = param; } } // If there's no blr length, then the format of the current message buffer // is identical to the format of the previous one. if (!blr_length) { return parameters.getCount(); } if (*blr != blr_version4 && *blr != blr_version5) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-804) << Arg::Gds(isc_dsql_sqlda_err)); } blr++; // skip the blr_version if (*blr++ != blr_begin || *blr++ != blr_message) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-804) << Arg::Gds(isc_dsql_sqlda_err)); } ++blr; // skip the message number USHORT count = *blr++; count += (*blr++) << 8; count /= 2; USHORT offset = 0; for (USHORT index = 1; index <= count; index++) { dsc desc; desc.dsc_scale = 0; desc.dsc_sub_type = 0; desc.dsc_flags = 0; switch (*blr++) { case blr_text: desc.dsc_dtype = dtype_text; desc.dsc_sub_type = ttype_dynamic; desc.dsc_length = *blr++; desc.dsc_length += (*blr++) << 8; break; case blr_varying: desc.dsc_dtype = dtype_varying; desc.dsc_sub_type = ttype_dynamic; desc.dsc_length = *blr++ + sizeof(USHORT); desc.dsc_length += (*blr++) << 8; break; case blr_text2: desc.dsc_dtype = dtype_text; desc.dsc_sub_type = *blr++; desc.dsc_sub_type += (*blr++) << 8; desc.dsc_length = *blr++; desc.dsc_length += (*blr++) << 8; break; case blr_varying2: desc.dsc_dtype = dtype_varying; desc.dsc_sub_type = *blr++; desc.dsc_sub_type += (*blr++) << 8; desc.dsc_length = *blr++ + sizeof(USHORT); desc.dsc_length += (*blr++) << 8; break; case blr_short: desc.dsc_dtype = dtype_short; desc.dsc_length = sizeof(SSHORT); desc.dsc_scale = *blr++; break; case blr_long: desc.dsc_dtype = dtype_long; desc.dsc_length = sizeof(SLONG); desc.dsc_scale = *blr++; break; case blr_int64: desc.dsc_dtype = dtype_int64; desc.dsc_length = sizeof(SINT64); desc.dsc_scale = *blr++; break; case blr_quad: desc.dsc_dtype = dtype_quad; desc.dsc_length = sizeof(SLONG) * 2; desc.dsc_scale = *blr++; break; case blr_float: desc.dsc_dtype = dtype_real; desc.dsc_length = sizeof(float); break; case blr_double: case blr_d_float: desc.dsc_dtype = dtype_double; desc.dsc_length = sizeof(double); break; case blr_timestamp: desc.dsc_dtype = dtype_timestamp; desc.dsc_length = sizeof(SLONG) * 2; break; case blr_sql_date: desc.dsc_dtype = dtype_sql_date; desc.dsc_length = sizeof(SLONG); break; case blr_sql_time: desc.dsc_dtype = dtype_sql_time; desc.dsc_length = sizeof(SLONG); break; case blr_blob2: { desc.dsc_dtype = dtype_blob; desc.dsc_length = sizeof(ISC_QUAD); desc.dsc_sub_type = *blr++; desc.dsc_sub_type += (*blr++) << 8; USHORT textType = *blr++; textType += (*blr++) << 8; desc.setTextType(textType); } break; default: ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-804) << Arg::Gds(isc_dsql_sqlda_err)); } USHORT align = type_alignments[desc.dsc_dtype]; if (align) offset = FB_ALIGN(offset, align); desc.dsc_address = (UCHAR*)(IPTR) offset; offset += desc.dsc_length; if (*blr++ != blr_short || *blr++ != 0) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-804) << Arg::Gds(isc_dsql_sqlda_err)); } align = type_alignments[dtype_short]; if (align) offset = FB_ALIGN(offset, align); USHORT null_offset = offset; offset += sizeof(SSHORT); const dsql_par* const parameter = parameters[index - 1]; fb_assert(parameter); // ASF: Older than 2.5 engine hasn't validating strings in DSQL. After this has been // implemented in 2.5, selecting a NONE column with UTF-8 attachment charset started // failing. The real problem is that the client encodes SQL_TEXT/SQL_VARYING using // blr_text/blr_varying (i.e. with the connection charset). I'm reseting the charset // here at the server as a way to make older (and not yet changed) client work // correctly. if (desc.isText()) desc.setTextType(ttype_none); request->req_user_descs.put(parameter, desc); dsql_par* null = parameter->par_null; if (null) { desc.clear(); desc.dsc_dtype = dtype_short; desc.dsc_scale = 0; desc.dsc_length = sizeof(SSHORT); desc.dsc_address = (UCHAR*)(IPTR) null_offset; request->req_user_descs.put(null, desc); } } if (*blr++ != (UCHAR) blr_end || offset != msg_length) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-804) << Arg::Gds(isc_dsql_sqlda_err)); } return count; } // Prepare a request for execution. Return SQL status code. // Note: caller is responsible for pool handling. static dsql_req* prepareRequest(thread_db* tdbb, dsql_dbb* database, jrd_tra* transaction, USHORT stringLength, const TEXT* string, USHORT clientDialect, USHORT parserVersion, bool isInternalRequest) { return prepareStatement(tdbb, database, transaction, stringLength, string, clientDialect, parserVersion, isInternalRequest); } // Prepare a statement for execution. Return SQL status code. // Note: caller is responsible for pool handling. static dsql_req* prepareStatement(thread_db* tdbb, dsql_dbb* database, jrd_tra* transaction, USHORT string_length, const TEXT* string, USHORT client_dialect, USHORT parser_version, bool isInternalRequest) { ISC_STATUS_ARRAY local_status; MOVE_CLEAR(local_status, sizeof(local_status)); TraceDSQLPrepare trace(database->dbb_attachment, string_length, string); if (client_dialect > SQL_DIALECT_CURRENT) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) << Arg::Gds(isc_wish_list)); } if (!string) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << // Unexpected end of command // CVC: Nothing will be line 1, column 1 for the user. Arg::Gds(isc_command_end_err2) << Arg::Num(1) << Arg::Num(1)); } if (!string_length) { string_length = strlen(string); } // Get rid of the trailing ";" if there is one. for (const TEXT* p = string + string_length; p-- > string;) { if (*p != ' ') { if (*p == ';') string_length = p - string; break; } } // allocate the statement block, then prepare the statement Jrd::ContextPoolHolder context(tdbb, database->createPool()); MemoryPool& pool = *tdbb->getDefaultPool(); DsqlCompiledStatement* statement = FB_NEW(pool) DsqlCompiledStatement(pool); DsqlCompilerScratch* scratch = FB_NEW(pool) DsqlCompilerScratch(pool, database, transaction, statement); scratch->clientDialect = client_dialect; dsql_req* request = FB_NEW(statement->getPool()) dsql_req(statement); request->req_dbb = database; request->req_transaction = transaction; request->req_traced = true; trace.setStatement(request); try { // Parse the SQL statement. If it croaks, return Parser parser(*tdbb->getDefaultPool(), client_dialect, scratch->getAttachment()->dbb_db_SQL_dialect, parser_version, string, string_length, tdbb->getAttachment()->att_charset); dsql_nod* node = parser.parse(); Firebird::string transformedText = parser.getTransformedString(); SSHORT charSetId = database->dbb_attachment->att_charset; // If the attachment charset is NONE, replace non-ASCII characters by question marks, so that // engine internals doesn't receive non-mappeable data to UTF8. If an attachment charset is // used, validate the string. if (charSetId == CS_NONE) { for (char* p = transformedText.begin(), *end = transformedText.end(); p < end; ++p) { if (UCHAR(*p) > 0x7F) *p = '?'; } } else { CharSet* charSet = INTL_charset_lookup(tdbb, charSetId); if (!charSet->wellFormed(transformedText.length(), (const UCHAR*) transformedText.begin(), NULL)) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << Arg::Gds(isc_malformed_string)); } } statement->sqlText = FB_NEW(pool) RefString(pool, transformedText); if (!node) { // CVC: Apparently, dsql_ypparse won't return if the command is incomplete, // because yyerror() will call ERRD_post(). // This may be a special case, but we don't know about positions here. ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << // Unexpected end of command Arg::Gds(isc_command_end_err)); } // allocate the send and receive messages statement->setSendMsg(FB_NEW(pool) dsql_msg(pool)); dsql_msg* message = FB_NEW(pool) dsql_msg(pool); statement->setReceiveMsg(message); message->msg_number = 1; statement->type = REQ_SELECT; // No work is done during pass1 for set transaction - like // checking for valid table names. This is because that will // require a valid transaction handle. // Error will be caught at execute time. node = PASS1_statement(scratch, node); if (!node) return request; switch (statement->type) { case REQ_COMMIT: case REQ_COMMIT_RETAIN: case REQ_ROLLBACK: case REQ_ROLLBACK_RETAIN: case REQ_GET_SEGMENT: case REQ_PUT_SEGMENT: case REQ_START_TRANS: request->req_traced = false; break; default: request->req_traced = true; } // stop here for statements not requiring code generation if (statement->type == REQ_DDL && parser.isStmtAmbiguous() && scratch->getAttachment()->dbb_db_SQL_dialect != client_dialect) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-817) << Arg::Gds(isc_ddl_not_allowed_by_db_sql_dial) << Arg::Num(scratch->getAttachment()->dbb_db_SQL_dialect)); } switch (statement->type) { // Work on blob segment statements case REQ_GET_SEGMENT: case REQ_PUT_SEGMENT: GEN_port(scratch, statement->getBlob()->blb_open_in_msg); GEN_port(scratch, statement->getBlob()->blb_open_out_msg); GEN_port(scratch, statement->getBlob()->blb_segment_msg); break; } switch (statement->type) { case REQ_COMMIT: case REQ_COMMIT_RETAIN: case REQ_ROLLBACK: case REQ_ROLLBACK_RETAIN: return request; case REQ_START_TRANS: GEN_start_transaction(scratch, node); return request; default: if (client_dialect > SQL_DIALECT_V5) statement->flags |= DsqlCompiledStatement::FLAG_BLR_VERSION5; else statement->flags |= DsqlCompiledStatement::FLAG_BLR_VERSION4; GEN_request(request, scratch, node); // fall into case REQ_GET_SEGMENT: case REQ_PUT_SEGMENT: { // Create the messages buffers for (size_t i = 0; i < scratch->ports.getCount(); ++i) { dsql_msg* message = scratch->ports[i]; // Allocate buffer for message const ULONG newLen = message->msg_length + FB_DOUBLE_ALIGN - 1; message->msg_buffer_number = request->req_msg_buffers.getCount(); request->req_msg_buffers.grow(message->msg_buffer_number + 1); UCHAR*& msgBuffer = request->req_msg_buffers[message->msg_buffer_number]; fb_assert(!msgBuffer); msgBuffer = FB_NEW(*tdbb->getDefaultPool()) UCHAR[newLen]; msgBuffer = (UCHAR*) FB_ALIGN((U_IPTR) msgBuffer, FB_DOUBLE_ALIGN); } break; } } switch (statement->type) { case REQ_GET_SEGMENT: case REQ_PUT_SEGMENT: return request; } const ULONG length = (ULONG) statement->getBlrData().getCount(); // stop here for ddl statements if (statement->type == REQ_CREATE_DB || statement->type == REQ_DDL) { // Notify Trace API manager about new DDL request cooked. trace.prepare(res_successful); return request; } // have the access method compile the statement #ifdef DSQL_DEBUG if (DSQL_debug & 64) { dsql_trace("Resulting BLR code for DSQL:"); gds__trace_raw("Statement:\n"); gds__trace_raw(string, string_length); gds__trace_raw("\nBLR:\n"); fb_print_blr(statement->getBlrData().begin(), (ULONG) statement->getBlrData().getCount(), gds__trace_printer, 0, 0); } #endif // check for warnings if (tdbb->tdbb_status_vector[2] == isc_arg_warning) { // save a status vector memcpy(local_status, tdbb->tdbb_status_vector, sizeof(ISC_STATUS_ARRAY)); } ISC_STATUS status = FB_SUCCESS; try { JRD_compile(tdbb, scratch->getAttachment()->dbb_attachment, &request->req_request, length, statement->getBlrData().begin(), statement->sqlText, statement->getDebugData().getCount(), statement->getDebugData().begin(), isInternalRequest); } catch (const Firebird::Exception&) { status = tdbb->tdbb_status_vector[1]; trace.prepare(status == isc_no_priv ? res_unauthorized : res_failed); } // restore warnings (if there are any) if (local_status[2] == isc_arg_warning) { size_t indx, len, warning; // find end of a status vector PARSE_STATUS(tdbb->tdbb_status_vector, indx, warning); if (indx) --indx; // calculate length of saved warnings PARSE_STATUS(local_status, len, warning); len -= 2; if ((len + indx - 1) < ISC_STATUS_LENGTH) memcpy(&tdbb->tdbb_status_vector[indx], &local_status[2], sizeof(ISC_STATUS) * len); } // free blr memory statement->getBlrData().free(); if (status) Firebird::status_exception::raise(tdbb->tdbb_status_vector); // Notify Trace API manager about new request cooked. trace.prepare(res_successful); return request; } catch (const Firebird::Exception&) { trace.prepare(res_failed); release_request(tdbb, request, true); throw; } } /** put_item @brief Put information item in output buffer if there is room, and return an updated pointer. If there isn't room for the item, indicate truncation and return NULL. @param item @param length @param string @param ptr @param end @param copy **/ static UCHAR* put_item( UCHAR item, const USHORT length, const UCHAR* string, UCHAR* ptr, const UCHAR* const end, const bool copy) { if (ptr + length + 3 >= end) { *ptr = isc_info_truncated; return NULL; } *ptr++ = item; *ptr++ = (UCHAR) length; *ptr++ = length >> 8; if (length && copy) memcpy(ptr, string, length); return ptr + length; } // Release a compiled statement. static void release_statement(DsqlCompiledStatement* statement) { if (statement->getParentRequest()) { dsql_req* parent = statement->getParentRequest(); size_t pos; if (parent->cursors.find(statement, pos)) parent->cursors.remove(pos); statement->setParentRequest(NULL); } statement->sqlText = NULL; statement->getBlrData().free(); // free blr memory } /** release_request @brief Release a dynamic request. @param request @param top_level **/ static void release_request(thread_db* tdbb, dsql_req* request, bool drop) { SET_TDBB(tdbb); // If request is parent, orphan the children and release a portion of their requests for (size_t i = 0; i < request->cursors.getCount(); ++i) { DsqlCompiledStatement* child = request->cursors[i]; child->flags |= DsqlCompiledStatement::FLAG_ORPHAN; child->setParentRequest(NULL); Jrd::ContextPoolHolder context(tdbb, &child->getPool()); release_statement(child); } // For requests that are linked to a parent, unlink it const DsqlCompiledStatement* statement = request->getStatement(); release_statement(const_cast(statement)); // If the request had an open cursor, close it if (request->req_flags & dsql_req::FLAG_OPENED_CURSOR) { close_cursor(tdbb, request); } Jrd::Attachment* att = request->req_dbb->dbb_attachment; const bool need_trace_free = request->req_traced && TraceManager::need_dsql_free(att); if (need_trace_free) { TraceSQLStatementImpl stmt(request, NULL); TraceManager::event_dsql_free(att, &stmt, DSQL_drop); } if (request->req_cursor) { HSHD_remove(request->req_cursor); request->req_cursor = NULL; } // If a request has been compiled, release it now if (request->req_request) { ThreadStatusGuard status_vector(tdbb); try { CMP_release(tdbb, request->req_request); request->req_request = NULL; } catch (Firebird::Exception&) { } } // Release the entire request if explicitly asked for if (drop) request->req_dbb->deletePool(&request->getPool()); } /** sql_info @brief Return DSQL information buffer. @param request @param item_length @param items @param info_length @param info **/ static void sql_info(thread_db* tdbb, dsql_req* request, USHORT item_length, const UCHAR* items, ULONG info_length, UCHAR* info) { if (!item_length || !items || !info_length || !info) return; UCHAR buffer[BUFFER_SMALL]; memset(buffer, 0, sizeof(buffer)); // Pre-initialize buffer. This is necessary because we don't want to transfer rubbish over the wire memset(info, 0, info_length); const UCHAR* const end_items = items + item_length; const UCHAR* const end_info = info + info_length; UCHAR *start_info; if (*items == isc_info_length) { start_info = info; items++; } else { start_info = NULL; } // CVC: Is it the idea that this pointer remains with its previous value // in the loop or should it be made NULL in each iteration? const dsql_msg* message = NULL; bool messageFound = false; USHORT first_index = 0; const DsqlCompiledStatement* statement = request->getStatement(); while (items < end_items && *items != isc_info_end) { ULONG length; USHORT number; const UCHAR item = *items++; switch (item) { case isc_info_sql_select: case isc_info_sql_bind: message = (item == isc_info_sql_select) ? statement->getReceiveMsg() : statement->getSendMsg(); messageFound = true; if (info + 1 >= end_info) { *info = isc_info_truncated; return; } *info++ = item; break; case isc_info_sql_stmt_type: switch (statement->type) { case REQ_SELECT: number = isc_info_sql_stmt_select; break; case REQ_SELECT_UPD: number = isc_info_sql_stmt_select_for_upd; break; case REQ_CREATE_DB: case REQ_DDL: number = isc_info_sql_stmt_ddl; break; case REQ_GET_SEGMENT: number = isc_info_sql_stmt_get_segment; break; case REQ_PUT_SEGMENT: number = isc_info_sql_stmt_put_segment; break; case REQ_COMMIT: case REQ_COMMIT_RETAIN: number = isc_info_sql_stmt_commit; break; case REQ_ROLLBACK: case REQ_ROLLBACK_RETAIN: number = isc_info_sql_stmt_rollback; break; case REQ_START_TRANS: number = isc_info_sql_stmt_start_trans; break; case REQ_INSERT: number = isc_info_sql_stmt_insert; break; case REQ_UPDATE: case REQ_UPDATE_CURSOR: number = isc_info_sql_stmt_update; break; case REQ_DELETE: case REQ_DELETE_CURSOR: number = isc_info_sql_stmt_delete; break; case REQ_EXEC_PROCEDURE: number = isc_info_sql_stmt_exec_procedure; break; case REQ_SET_GENERATOR: number = isc_info_sql_stmt_set_generator; break; case REQ_SAVEPOINT: number = isc_info_sql_stmt_savepoint; break; case REQ_EXEC_BLOCK: number = isc_info_sql_stmt_exec_procedure; break; case REQ_SELECT_BLOCK: number = isc_info_sql_stmt_select; break; default: number = 0; break; } length = convert((SLONG) number, buffer); info = put_item(item, length, buffer, info, end_info); if (!info) { return; } break; case isc_info_sql_sqlda_start: length = *items++; first_index = static_cast(gds__vax_integer(items, length)); items += length; break; case isc_info_sql_batch_fetch: if (statement->flags & DsqlCompiledStatement::FLAG_NO_BATCH) number = 0; else number = 1; length = convert((SLONG) number, buffer); if (!(info = put_item(item, length, buffer, info, end_info))) { return; } break; case isc_info_sql_records: length = get_request_info(tdbb, request, sizeof(buffer), buffer); if (length && !(info = put_item(item, length, buffer, info, end_info))) { return; } break; case isc_info_sql_get_plan: { // be careful, get_plan_info() will reallocate the buffer to a // larger size if it is not big enough //UCHAR* buffer_ptr = buffer; UCHAR* buffer_ptr = info + 3; // Somebody decided to put a platform-dependent NEWLINE at the beginning, // see get_rsb_item! This idea predates FB1. static const size_t minPlan = strlen("\nPLAN (T NATURAL)"); if (info + minPlan + 3 >= end_info) { fb_assert(info < end_info); *info = isc_info_truncated; info = NULL; length = 0; } else { //length = DSQL_get_plan_info(tdbb, request, sizeof(buffer), // reinterpret_cast(&buffer_ptr)); length = DSQL_get_plan_info(tdbb, request, (end_info - info - 4), reinterpret_cast(&buffer_ptr), false); } if (length) { if (length > MAX_USHORT) { fb_assert(info < end_info); *info = isc_info_truncated; info = NULL; } else info = put_item(item, length, buffer_ptr, info, end_info, false); } //if (length > sizeof(buffer) || buffer_ptr != buffer) { // gds__free(buffer_ptr); //} if (!info) { return; } } break; case isc_info_sql_num_variables: case isc_info_sql_describe_vars: if (messageFound) { number = message ? message->msg_index : 0; length = convert((SLONG) number, buffer); if (!(info = put_item(item, length, buffer, info, end_info))) { return; } if (item == isc_info_sql_num_variables) { continue; } const UCHAR* end_describe = items; while (end_describe < end_items && *end_describe != isc_info_end && *end_describe != isc_info_sql_describe_end) { end_describe++; } info = var_info(message, items, end_describe, info, end_info, first_index, message == statement->getSendMsg()); if (!info) { return; } items = end_describe; if (*items == isc_info_sql_describe_end) { items++; } break; } // else fall into default: buffer[0] = item; length = 1 + convert((SLONG) isc_infunk, buffer + 1); if (!(info = put_item(isc_info_error, length, buffer, info, end_info))) { return; } } } *info++ = isc_info_end; if (start_info && (end_info - info >= 7)) { const SLONG number = info - start_info; fb_assert(number > 0); memmove(start_info + 7, start_info, number); USHORT length = convert(number, buffer); fb_assert(length == 4); // We only accept SLONG put_item(isc_info_length, length, buffer, start_info, end_info); } } /** var_info @brief Provide information on an internal message. @param message @param items @param end_describe @param info @param end @param first_index **/ static UCHAR* var_info(const dsql_msg* message, const UCHAR* items, const UCHAR* const end_describe, UCHAR* info, const UCHAR* const end, USHORT first_index, bool input_message) { if (!message || !message->msg_index) return info; thread_db* tdbb = JRD_get_thread_data(); Jrd::Attachment* attachment = tdbb->getAttachment(); HalfStaticArray parameters; for (size_t i = 0; i < message->msg_parameters.getCount(); ++i) { const dsql_par* param = message->msg_parameters[i]; if (param->par_index) { if (param->par_index > parameters.getCount()) parameters.grow(param->par_index); fb_assert(!parameters[param->par_index - 1]); parameters[param->par_index - 1] = param; } } UCHAR buf[128]; for (size_t i = 0; i < parameters.getCount(); i++) { const dsql_par* param = parameters[i]; fb_assert(param); if (param->par_index >= first_index) { SLONG sql_len = param->par_desc.dsc_length; SLONG sql_sub_type = 0; SLONG sql_scale = 0; SLONG sql_type = 0; switch (param->par_desc.dsc_dtype) { case dtype_real: sql_type = SQL_FLOAT; break; case dtype_array: sql_type = SQL_ARRAY; break; case dtype_timestamp: sql_type = SQL_TIMESTAMP; break; case dtype_sql_date: sql_type = SQL_TYPE_DATE; break; case dtype_sql_time: sql_type = SQL_TYPE_TIME; break; case dtype_double: sql_type = SQL_DOUBLE; sql_scale = param->par_desc.dsc_scale; break; case dtype_text: if (input_message && (param->par_desc.dsc_flags & DSC_null)) { sql_type = SQL_NULL; sql_len = 0; } else { sql_type = SQL_TEXT; sql_sub_type = param->par_desc.dsc_sub_type; } break; case dtype_blob: sql_type = SQL_BLOB; sql_sub_type = param->par_desc.dsc_sub_type; sql_scale = param->par_desc.dsc_scale; break; case dtype_varying: sql_type = SQL_VARYING; sql_len -= sizeof(USHORT); sql_sub_type = param->par_desc.dsc_sub_type; break; case dtype_short: case dtype_long: case dtype_int64: switch (param->par_desc.dsc_dtype) { case dtype_short: sql_type = SQL_SHORT; break; case dtype_long: sql_type = SQL_LONG; break; default: sql_type = SQL_INT64; } sql_scale = param->par_desc.dsc_scale; if (param->par_desc.dsc_sub_type) sql_sub_type = param->par_desc.dsc_sub_type; break; case dtype_quad: sql_type = SQL_QUAD; sql_scale = param->par_desc.dsc_scale; break; default: ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-804) << Arg::Gds(isc_dsql_datatype_err)); } if (sql_type && (param->par_desc.dsc_flags & DSC_nullable)) sql_type++; for (const UCHAR* describe = items; describe < end_describe;) { USHORT length; string name; const UCHAR* buffer = buf; UCHAR item = *describe++; switch (item) { case isc_info_sql_sqlda_seq: length = convert((SLONG) param->par_index, buf); break; case isc_info_sql_message_seq: length = 0; break; case isc_info_sql_type: length = convert((SLONG) sql_type, buf); break; case isc_info_sql_sub_type: length = convert((SLONG) sql_sub_type, buf); break; case isc_info_sql_scale: length = convert((SLONG) sql_scale, buf); break; case isc_info_sql_length: length = convert((SLONG) sql_len, buf); break; case isc_info_sql_null_ind: length = convert((SLONG) (sql_type & 1), buf); break; case isc_info_sql_field: if (param->par_name) { name = attachment->stringToUserCharSet(tdbb, param->par_name); length = name.length(); buffer = reinterpret_cast(name.c_str()); } else length = 0; break; case isc_info_sql_relation: if (param->par_rel_name) { name = attachment->stringToUserCharSet(tdbb, param->par_rel_name); length = name.length(); buffer = reinterpret_cast(name.c_str()); } else length = 0; break; case isc_info_sql_owner: if (param->par_owner_name) { name = attachment->stringToUserCharSet(tdbb, param->par_owner_name); length = name.length(); buffer = reinterpret_cast(name.c_str()); } else length = 0; break; case isc_info_sql_relation_alias: if (param->par_rel_alias) { name = attachment->stringToUserCharSet(tdbb, param->par_rel_alias); length = name.length(); buffer = reinterpret_cast(name.c_str()); } else length = 0; break; case isc_info_sql_alias: if (param->par_alias) { name = attachment->stringToUserCharSet(tdbb, param->par_alias); length = name.length(); buffer = reinterpret_cast(name.c_str()); } else length = 0; break; default: buf[0] = item; item = isc_info_error; length = 1 + convert((SLONG) isc_infunk, buf + 1); break; } if (!(info = put_item(item, length, buffer, info, end))) return info; } if (info + 1 >= end) { *info = isc_info_truncated; return NULL; } *info++ = isc_info_sql_describe_end; } // if() } // for() return info; }