mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-31 03:23:03 +01:00
530 lines
12 KiB
C++
530 lines
12 KiB
C++
/*
|
|
* The contents of this file are subject to the Initial
|
|
* Developer's 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.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl.
|
|
*
|
|
* Software distributed under the License is distributed AS IS,
|
|
* 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 Adriano dos Santos Fernandes
|
|
* for the Firebird Open Source RDBMS project.
|
|
*
|
|
* Copyright (c) 2011 Adriano dos Santos Fernandes <adrianosf at gmail.com>
|
|
* and all contributors signed below.
|
|
*
|
|
* All Rights Reserved.
|
|
* Contributor(s): ______________________________________.
|
|
* Alex Peshkoff
|
|
*
|
|
*/
|
|
|
|
#include "firebird.h"
|
|
#include "../common/StatementMetadata.h"
|
|
#include "memory_routines.h"
|
|
#include "../common/StatusHolder.h"
|
|
#include "../jrd/inf_pub.h"
|
|
#include "../yvalve/gds_proto.h"
|
|
#include "../common/utils_proto.h"
|
|
#include "../dsql/sqlda_pub.h"
|
|
|
|
namespace Firebird {
|
|
|
|
|
|
static const UCHAR DESCRIBE_VARS[] =
|
|
{
|
|
isc_info_sql_describe_vars,
|
|
isc_info_sql_sqlda_seq,
|
|
isc_info_sql_type,
|
|
isc_info_sql_sub_type,
|
|
isc_info_sql_scale,
|
|
isc_info_sql_length,
|
|
isc_info_sql_field,
|
|
isc_info_sql_relation,
|
|
isc_info_sql_owner,
|
|
isc_info_sql_alias,
|
|
isc_info_sql_describe_end
|
|
};
|
|
|
|
static const unsigned INFO_BUFFER_SIZE = MAX_USHORT;
|
|
|
|
|
|
static int getNumericInfo(const UCHAR** ptr);
|
|
static void getStringInfo(const UCHAR** ptr, string* str);
|
|
|
|
|
|
// Build a list of info codes based on a prepare flags bitmask.
|
|
// Return "estimated" necessary size for the result buffer.
|
|
unsigned StatementMetadata::buildInfoItems(Array<UCHAR>& items, unsigned flags)
|
|
{
|
|
items.clear();
|
|
|
|
if (flags & IStatement::PREPARE_PREFETCH_TYPE)
|
|
items.add(isc_info_sql_stmt_type);
|
|
|
|
if (flags & IStatement::PREPARE_PREFETCH_FLAGS)
|
|
items.add(isc_info_sql_stmt_flags);
|
|
|
|
if (flags & IStatement::PREPARE_PREFETCH_INPUT_PARAMETERS)
|
|
{
|
|
items.add(isc_info_sql_bind);
|
|
items.push(DESCRIBE_VARS, sizeof(DESCRIBE_VARS));
|
|
}
|
|
|
|
if (flags & IStatement::PREPARE_PREFETCH_OUTPUT_PARAMETERS)
|
|
{
|
|
items.add(isc_info_sql_select);
|
|
items.push(DESCRIBE_VARS, sizeof(DESCRIBE_VARS));
|
|
}
|
|
|
|
if (flags & IStatement::PREPARE_PREFETCH_LEGACY_PLAN)
|
|
items.add(isc_info_sql_get_plan);
|
|
|
|
if (flags & IStatement::PREPARE_PREFETCH_DETAILED_PLAN)
|
|
items.add(isc_info_sql_explain_plan);
|
|
|
|
return INFO_BUFFER_SIZE;
|
|
}
|
|
|
|
// Build a prepare flags bitmask based on a list of info codes.
|
|
unsigned StatementMetadata::buildInfoFlags(unsigned itemsLength, const UCHAR* items)
|
|
{
|
|
unsigned flags = 0;
|
|
const UCHAR* end = items + itemsLength;
|
|
UCHAR c;
|
|
|
|
while (items < end && (c = *items++) != isc_info_end)
|
|
{
|
|
switch (c)
|
|
{
|
|
case isc_info_sql_stmt_type:
|
|
flags |= IStatement::PREPARE_PREFETCH_TYPE;
|
|
break;
|
|
|
|
case isc_info_sql_stmt_flags:
|
|
flags |= IStatement::PREPARE_PREFETCH_FLAGS;
|
|
break;
|
|
|
|
case isc_info_sql_get_plan:
|
|
flags |= IStatement::PREPARE_PREFETCH_LEGACY_PLAN;
|
|
break;
|
|
|
|
case isc_info_sql_explain_plan:
|
|
flags |= IStatement::PREPARE_PREFETCH_DETAILED_PLAN;
|
|
break;
|
|
|
|
case isc_info_sql_select:
|
|
flags |= IStatement::PREPARE_PREFETCH_OUTPUT_PARAMETERS;
|
|
break;
|
|
|
|
case isc_info_sql_bind:
|
|
flags |= IStatement::PREPARE_PREFETCH_INPUT_PARAMETERS;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
// Get statement type.
|
|
unsigned StatementMetadata::getType()
|
|
{
|
|
if (!type.specified)
|
|
{
|
|
UCHAR info[] = {isc_info_sql_stmt_type};
|
|
UCHAR result[16];
|
|
|
|
getAndParse(sizeof(info), info, sizeof(result), result);
|
|
|
|
fb_assert(type.specified);
|
|
}
|
|
|
|
return type.value;
|
|
}
|
|
|
|
unsigned StatementMetadata::getFlags()
|
|
{
|
|
if (!flags.specified)
|
|
{
|
|
UCHAR info[] = {isc_info_sql_stmt_flags};
|
|
UCHAR result[16];
|
|
|
|
getAndParse(sizeof(info), info, sizeof(result), result);
|
|
|
|
fb_assert(flags.specified);
|
|
}
|
|
|
|
return flags.value;
|
|
}
|
|
|
|
// Get statement plan.
|
|
const char* StatementMetadata::getPlan(bool detailed)
|
|
{
|
|
string* plan = detailed ? &detailedPlan : &legacyPlan;
|
|
|
|
if (plan->isEmpty())
|
|
{
|
|
UCHAR info[] = {detailed ? isc_info_sql_explain_plan : isc_info_sql_get_plan};
|
|
UCHAR result[INFO_BUFFER_SIZE];
|
|
|
|
getAndParse(sizeof(info), info, sizeof(result), result);
|
|
}
|
|
|
|
return plan->nullStr();
|
|
}
|
|
|
|
// Get statement input parameters.
|
|
IMessageMetadata* StatementMetadata::getInputMetadata()
|
|
{
|
|
if (!inputParameters->fetched)
|
|
fetchParameters(isc_info_sql_bind, inputParameters);
|
|
|
|
inputParameters->addRef();
|
|
return inputParameters;
|
|
}
|
|
|
|
// Get statement output parameters.
|
|
IMessageMetadata* StatementMetadata::getOutputMetadata()
|
|
{
|
|
if (!outputParameters->fetched)
|
|
fetchParameters(isc_info_sql_select, outputParameters);
|
|
|
|
outputParameters->addRef();
|
|
return outputParameters;
|
|
}
|
|
|
|
// Get number of records affected by the statement execution.
|
|
ISC_UINT64 StatementMetadata::getAffectedRecords()
|
|
{
|
|
UCHAR info[] = {isc_info_sql_records};
|
|
UCHAR result[33];
|
|
|
|
getAndParse(sizeof(info), info, sizeof(result), result);
|
|
|
|
ISC_UINT64 count = 0;
|
|
|
|
if (result[0] == isc_info_sql_records)
|
|
{
|
|
const UCHAR* p = result + 3;
|
|
|
|
while (*p != isc_info_end)
|
|
{
|
|
UCHAR counter = *p++;
|
|
const SSHORT len = gds__vax_integer(p, 2);
|
|
p += 2;
|
|
if (counter != isc_info_req_select_count)
|
|
count += gds__vax_integer(p, len);
|
|
p += len;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
// Reset the object to its initial state.
|
|
void StatementMetadata::clear()
|
|
{
|
|
type.specified = false;
|
|
legacyPlan = detailedPlan = "";
|
|
inputParameters->items.clear();
|
|
outputParameters->items.clear();
|
|
inputParameters->fetched = outputParameters->fetched = false;
|
|
}
|
|
|
|
// Parse an info response buffer.
|
|
void StatementMetadata::parse(unsigned bufferLength, const UCHAR* buffer)
|
|
{
|
|
const UCHAR* bufferEnd = buffer + bufferLength;
|
|
Parameters* parameters = NULL;
|
|
bool finish = false;
|
|
UCHAR c;
|
|
|
|
while (!finish && buffer < bufferEnd && (c = *buffer++) != isc_info_end)
|
|
{
|
|
switch (c)
|
|
{
|
|
case isc_info_sql_stmt_type:
|
|
type = getNumericInfo(&buffer);
|
|
break;
|
|
|
|
case isc_info_sql_stmt_flags:
|
|
flags = getNumericInfo(&buffer);
|
|
break;
|
|
|
|
case isc_info_sql_get_plan:
|
|
case isc_info_sql_explain_plan:
|
|
{
|
|
string* plan = (c == isc_info_sql_explain_plan ? &detailedPlan : &legacyPlan);
|
|
getStringInfo(&buffer, plan);
|
|
break;
|
|
}
|
|
|
|
case isc_info_sql_select:
|
|
parameters = outputParameters;
|
|
break;
|
|
|
|
case isc_info_sql_bind:
|
|
parameters = inputParameters;
|
|
break;
|
|
|
|
case isc_info_sql_num_variables:
|
|
case isc_info_sql_describe_vars:
|
|
{
|
|
if (!parameters)
|
|
{
|
|
finish = true;
|
|
break;
|
|
}
|
|
|
|
getNumericInfo(&buffer); // skip the message index
|
|
|
|
if (c == isc_info_sql_num_variables)
|
|
continue;
|
|
|
|
parameters->fetched = true;
|
|
|
|
Parameters::Item temp(*getDefaultMemoryPool());
|
|
Parameters::Item* param = &temp;
|
|
bool finishDescribe = false;
|
|
|
|
// Loop over the variables being described.
|
|
while (!finishDescribe)
|
|
{
|
|
switch ((c = *buffer++))
|
|
{
|
|
case isc_info_sql_describe_end:
|
|
param->finished = true;
|
|
break;
|
|
|
|
case isc_info_sql_sqlda_seq:
|
|
{
|
|
unsigned num = getNumericInfo(&buffer);
|
|
|
|
while (parameters->items.getCount() < num)
|
|
parameters->items.add();
|
|
|
|
param = ¶meters->items[num - 1];
|
|
break;
|
|
}
|
|
|
|
case isc_info_sql_type:
|
|
param->type = getNumericInfo(&buffer);
|
|
param->nullable = (param->type & 1) != 0;
|
|
param->type &= ~1;
|
|
break;
|
|
|
|
case isc_info_sql_sub_type:
|
|
param->subType = getNumericInfo(&buffer);
|
|
break;
|
|
|
|
case isc_info_sql_length:
|
|
param->length = getNumericInfo(&buffer);
|
|
break;
|
|
|
|
case isc_info_sql_scale:
|
|
param->scale = getNumericInfo(&buffer);
|
|
break;
|
|
|
|
case isc_info_sql_field:
|
|
getStringInfo(&buffer, ¶m->field);
|
|
break;
|
|
|
|
case isc_info_sql_relation:
|
|
getStringInfo(&buffer, ¶m->relation);
|
|
break;
|
|
|
|
case isc_info_sql_owner:
|
|
getStringInfo(&buffer, ¶m->owner);
|
|
break;
|
|
|
|
case isc_info_sql_alias:
|
|
getStringInfo(&buffer, ¶m->alias);
|
|
break;
|
|
|
|
case isc_info_truncated:
|
|
parameters->fetched = false;
|
|
// fall into
|
|
|
|
default:
|
|
--buffer;
|
|
finishDescribe = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (parameters->fetched)
|
|
{
|
|
unsigned off = 0;
|
|
|
|
for (unsigned n = 0; n < parameters->items.getCount(); ++n)
|
|
{
|
|
Parameters::Item* param = ¶meters->items[n];
|
|
|
|
if (!param->finished)
|
|
{
|
|
parameters->fetched = false;
|
|
break;
|
|
}
|
|
|
|
off = fb_utils::sqlTypeToDsc(off, param->type, param->length,
|
|
NULL /*dtype*/, NULL /*length*/, ¶m->offset, ¶m->nullInd);
|
|
|
|
switch(param->type)
|
|
{
|
|
case SQL_VARYING:
|
|
case SQL_TEXT:
|
|
param->charSet = param->subType;
|
|
param->subType = 0;
|
|
break;
|
|
case SQL_BLOB:
|
|
param->charSet = param->scale;
|
|
param->scale = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (parameters->fetched)
|
|
parameters->length = off;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
finish = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// CVC: This routine assumes the input is well formed, hence at least check we didn't read
|
|
// beyond the buffer's end, although I would prefer to make the previous code more robust.
|
|
// fb_assert(buffer <= bufferEnd);
|
|
// ASF: User may pass any (including unknown from new version) info code and we can't
|
|
// understand them. In this case, we leave these code without parse (when they're in the end)
|
|
// or we'll need extra info calls (done by methods of this class) to parse only the info we
|
|
// can understand.
|
|
|
|
for (ObjectsArray<Parameters::Item>::iterator i = inputParameters->items.begin();
|
|
i != inputParameters->items.end() && inputParameters->fetched;
|
|
++i)
|
|
{
|
|
inputParameters->fetched = i->finished;
|
|
}
|
|
|
|
for (ObjectsArray<Parameters::Item>::iterator i = outputParameters->items.begin();
|
|
i != outputParameters->items.end() && outputParameters->fetched;
|
|
++i)
|
|
{
|
|
outputParameters->fetched = i->finished;
|
|
}
|
|
}
|
|
|
|
// Get a info buffer and parse it.
|
|
void StatementMetadata::getAndParse(unsigned itemsLength, const UCHAR* items,
|
|
unsigned bufferLength, UCHAR* buffer)
|
|
{
|
|
LocalStatus ls;
|
|
CheckStatusWrapper status(&ls);
|
|
statement->getInfo(&status, itemsLength, items, bufferLength, buffer);
|
|
ls.check();
|
|
|
|
parse(bufferLength, buffer);
|
|
}
|
|
|
|
// Fill an output buffer from the cached data. Return true if succeeded.
|
|
bool StatementMetadata::fillFromCache(unsigned itemsLength, const UCHAR* items,
|
|
unsigned bufferLength, UCHAR* buffer)
|
|
{
|
|
//// TODO: Respond more things locally. isc_dsql_prepare_m will need.
|
|
|
|
if (((itemsLength == 1 && items[0] == isc_info_sql_stmt_type) ||
|
|
(itemsLength == 2 && items[0] == isc_info_sql_stmt_type &&
|
|
(items[1] == isc_info_end || items[1] == 0))) &&
|
|
type.specified)
|
|
{
|
|
if (bufferLength >= 8)
|
|
{
|
|
*buffer++ = isc_info_sql_stmt_type;
|
|
put_vax_short(buffer, 4);
|
|
buffer += 2;
|
|
put_vax_long(buffer, type.value);
|
|
buffer += 4;
|
|
*buffer = isc_info_end;
|
|
}
|
|
else
|
|
*buffer = isc_info_truncated;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Fetch input or output parameter list.
|
|
void StatementMetadata::fetchParameters(UCHAR code, Parameters* parameters)
|
|
{
|
|
while (!parameters->fetched)
|
|
{
|
|
unsigned startIndex = 0;
|
|
|
|
for (ObjectsArray<Parameters::Item>::iterator i = parameters->items.begin();
|
|
i != parameters->items.end();
|
|
++i)
|
|
{
|
|
if (!i->finished)
|
|
break;
|
|
|
|
++startIndex;
|
|
}
|
|
|
|
UCHAR items[5 + sizeof(DESCRIBE_VARS)] =
|
|
{
|
|
isc_info_sql_sqlda_start,
|
|
2,
|
|
(startIndex & 0xFF),
|
|
((startIndex >> 8) & 0xFF),
|
|
code
|
|
};
|
|
memcpy(items + 5, DESCRIBE_VARS, sizeof(DESCRIBE_VARS));
|
|
|
|
UCHAR buffer[INFO_BUFFER_SIZE];
|
|
getAndParse(sizeof(items), items, sizeof(buffer), buffer);
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------
|
|
|
|
|
|
// Pick up a VAX format numeric info item with a 2 byte length.
|
|
static int getNumericInfo(const UCHAR** ptr)
|
|
{
|
|
const SSHORT len = static_cast<SSHORT>(gds__vax_integer(*ptr, 2));
|
|
*ptr += 2;
|
|
int item = gds__vax_integer(*ptr, len);
|
|
*ptr += len;
|
|
return item;
|
|
}
|
|
|
|
// Pick up a string valued info item.
|
|
static void getStringInfo(const UCHAR** ptr, string* str)
|
|
{
|
|
const UCHAR* p = *ptr;
|
|
SSHORT len = static_cast<SSHORT>(gds__vax_integer(p, 2));
|
|
|
|
// CVC: What else can we do here?
|
|
if (len < 0)
|
|
len = 0;
|
|
|
|
*ptr += len + 2;
|
|
p += 2;
|
|
|
|
str->assign(p, len);
|
|
}
|
|
|
|
|
|
} // namespace Firebird
|