mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-31 10:03:03 +01:00
431 lines
9.3 KiB
C++
431 lines
9.3 KiB
C++
|
#include "../common/StatementMetadata.h"
|
||
|
#include "memory_routines.h"
|
||
|
#include "../common/StatusHolder.h"
|
||
|
#include "../jrd/inf_pub.h"
|
||
|
#include "../yvalve/gds_proto.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.
|
||
|
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_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_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;
|
||
|
}
|
||
|
|
||
|
// 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);
|
||
|
|
||
|
fb_assert(plan->hasData());
|
||
|
}
|
||
|
|
||
|
return plan->nullStr();
|
||
|
}
|
||
|
|
||
|
// Get statement input parameters.
|
||
|
const IParametersMetadata* StatementMetadata::getInputParameters()
|
||
|
{
|
||
|
if (!inputParameters.fetched)
|
||
|
fetchParameters(isc_info_sql_bind, &inputParameters);
|
||
|
|
||
|
return &inputParameters;
|
||
|
}
|
||
|
|
||
|
// Get statement output parameters.
|
||
|
const IParametersMetadata* StatementMetadata::getOutputParameters()
|
||
|
{
|
||
|
if (!outputParameters.fetched)
|
||
|
fetchParameters(isc_info_sql_select, &outputParameters);
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
++p;
|
||
|
const SSHORT len = gds__vax_integer(p, 2);
|
||
|
p += 2;
|
||
|
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_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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
finish = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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 status;
|
||
|
statement->getInfo(&status, itemsLength, items, bufferLength, buffer);
|
||
|
status.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
|