mirror of
https://github.com/VCMP-SqMod/SqMod.git
synced 2025-01-31 18:07:14 +01:00
415 lines
11 KiB
C++
415 lines
11 KiB
C++
|
//
|
||
|
// StatementExecutor.cpp
|
||
|
//
|
||
|
// Library: Data/PostgreSQL
|
||
|
// Package: PostgreSQL
|
||
|
// Module: StatementExecutor
|
||
|
//
|
||
|
// Copyright (c) 2015, Applied Informatics Software Engineering GmbH.
|
||
|
// and Contributors.
|
||
|
//
|
||
|
// SPDX-License-Identifier: BSL-1.0
|
||
|
//
|
||
|
|
||
|
|
||
|
#include "Poco/Data/PostgreSQL/StatementExecutor.h"
|
||
|
#include "Poco/Data/PostgreSQL/PostgreSQLTypes.h"
|
||
|
#include "Poco/Format.h"
|
||
|
#include "Poco/UUID.h"
|
||
|
#include "Poco/UUIDGenerator.h"
|
||
|
#include "Poco/NumberParser.h"
|
||
|
#include "Poco/NumberParser.h"
|
||
|
#include "Poco/RegularExpression.h"
|
||
|
#include <algorithm>
|
||
|
#include <set>
|
||
|
|
||
|
|
||
|
namespace
|
||
|
{
|
||
|
std::size_t countOfPlaceHoldersInSQLStatement(const std::string& aSQLStatement)
|
||
|
{
|
||
|
// Find unique placeholders.
|
||
|
// Unique placeholders allow the same placeholder to be used multiple times in the same statement.
|
||
|
|
||
|
// NON C++11 implementation
|
||
|
|
||
|
//if (aSQLStatement.empty())
|
||
|
//{
|
||
|
//return 0;
|
||
|
//}
|
||
|
|
||
|
// set to hold the unique placeholders ($1, $2, $3, etc.).
|
||
|
// A set is used because the same placeholder can be used muliple times
|
||
|
std::set<std::string> placeholderSet;
|
||
|
|
||
|
Poco::RegularExpression placeholderRE("[$][0-9]+");
|
||
|
Poco::RegularExpression::Match match = { 0 , 0 }; // Match is a struct, not a class :-(
|
||
|
|
||
|
std::size_t startingPosition = 0;
|
||
|
|
||
|
while (match.offset != std::string::npos)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
if (placeholderRE.match(aSQLStatement, startingPosition, match))
|
||
|
{
|
||
|
placeholderSet.insert(aSQLStatement.substr(match.offset, match.length));
|
||
|
startingPosition = match.offset + match.length;
|
||
|
}
|
||
|
}
|
||
|
catch (Poco::RegularExpressionException &)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return placeholderSet.size();
|
||
|
}
|
||
|
} // namespace
|
||
|
|
||
|
|
||
|
namespace Poco {
|
||
|
namespace Data {
|
||
|
namespace PostgreSQL {
|
||
|
|
||
|
|
||
|
StatementExecutor::StatementExecutor(SessionHandle& sessionHandle):
|
||
|
_sessionHandle(sessionHandle),
|
||
|
_state(STMT_INITED),
|
||
|
_pResultHandle(0),
|
||
|
_countPlaceholdersInSQLStatement(0),
|
||
|
_currentRow(0),
|
||
|
_affectedRowCount(0)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
StatementExecutor::~StatementExecutor()
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
// remove the prepared statement from the session
|
||
|
if(_sessionHandle.isConnected() && _state >= STMT_COMPILED)
|
||
|
{
|
||
|
_sessionHandle.deallocatePreparedStatement(_preparedStatementName);
|
||
|
}
|
||
|
|
||
|
PQResultClear resultClearer(_pResultHandle);
|
||
|
}
|
||
|
catch (...)
|
||
|
{
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
StatementExecutor::State StatementExecutor::state() const
|
||
|
{
|
||
|
return _state;
|
||
|
}
|
||
|
|
||
|
|
||
|
void StatementExecutor::prepare(const std::string& aSQLStatement)
|
||
|
{
|
||
|
if (! _sessionHandle.isConnected()) throw NotConnectedException();
|
||
|
if (_state >= STMT_COMPILED) return;
|
||
|
|
||
|
// clear out the metadata. One way or another it is now obsolete.
|
||
|
_countPlaceholdersInSQLStatement = 0;
|
||
|
_SQLStatement= std::string();
|
||
|
_preparedStatementName = std::string();
|
||
|
_resultColumns.clear();
|
||
|
|
||
|
// clear out any result data. One way or another it is now obsolete.
|
||
|
clearResults();
|
||
|
|
||
|
// prepare parameters for the call to PQprepare
|
||
|
const char* ptrCSQLStatement = aSQLStatement.c_str();
|
||
|
std::size_t countPlaceholdersInSQLStatement = countOfPlaceHoldersInSQLStatement(aSQLStatement);
|
||
|
|
||
|
Poco::UUIDGenerator& generator = Poco::UUIDGenerator::defaultGenerator();
|
||
|
Poco::UUID uuid(generator.create()); // time based
|
||
|
std::string statementName = uuid.toString();
|
||
|
statementName.insert(0, 1, 'p'); // prepared statement names can't start with a number
|
||
|
std::replace(statementName.begin(), statementName.end(), '-', 'p'); // PostgreSQL doesn't like dashes in prepared statement names
|
||
|
const char* pStatementName = statementName.c_str();
|
||
|
|
||
|
PGresult* ptrPGResult = 0;
|
||
|
|
||
|
{
|
||
|
Poco::FastMutex::ScopedLock mutexLocker(_sessionHandle.mutex());
|
||
|
|
||
|
// prepare the statement - temporary PGresult returned
|
||
|
ptrPGResult = PQprepare(_sessionHandle, pStatementName, ptrCSQLStatement, (int)countPlaceholdersInSQLStatement, 0);
|
||
|
}
|
||
|
|
||
|
{
|
||
|
// setup to clear the result from PQprepare
|
||
|
PQResultClear resultClearer(ptrPGResult);
|
||
|
|
||
|
if (!ptrPGResult || PQresultStatus(ptrPGResult) != PGRES_COMMAND_OK)
|
||
|
{
|
||
|
throw StatementException(std::string("postgresql_stmt_prepare error: ") + PQresultErrorMessage (ptrPGResult) + " " + aSQLStatement);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Determine what the structure of a statement result will look like
|
||
|
{
|
||
|
Poco::FastMutex::ScopedLock mutexLocker(_sessionHandle.mutex());
|
||
|
ptrPGResult = PQdescribePrepared(_sessionHandle, pStatementName);
|
||
|
}
|
||
|
|
||
|
{
|
||
|
PQResultClear resultClearer(ptrPGResult);
|
||
|
if (! ptrPGResult || PQresultStatus(ptrPGResult) != PGRES_COMMAND_OK)
|
||
|
{
|
||
|
throw StatementException(std::string("postgresql_stmt_describe error: ") +
|
||
|
PQresultErrorMessage (ptrPGResult) + " " + aSQLStatement);
|
||
|
}
|
||
|
|
||
|
// remember the structure of the statement result
|
||
|
int fieldCount = PQnfields(ptrPGResult);
|
||
|
if (fieldCount < 0) fieldCount = 0;
|
||
|
|
||
|
for (int i = 0; i < fieldCount; ++i)
|
||
|
{
|
||
|
_resultColumns.push_back(MetaColumn(i, PQfname(ptrPGResult, i),
|
||
|
oidToColumnDataType(PQftype(ptrPGResult, i)), 0, 0, true));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_SQLStatement = aSQLStatement;
|
||
|
_preparedStatementName = statementName;
|
||
|
_countPlaceholdersInSQLStatement = countPlaceholdersInSQLStatement;
|
||
|
_state = STMT_COMPILED; // must be last
|
||
|
}
|
||
|
|
||
|
|
||
|
void StatementExecutor::bindParams(const InputParameterVector& anInputParameterVector)
|
||
|
{
|
||
|
if (! _sessionHandle.isConnected()) throw NotConnectedException();
|
||
|
|
||
|
if (_state < STMT_COMPILED) throw StatementException("Statement is not compiled yet");
|
||
|
|
||
|
if (anInputParameterVector.size() != _countPlaceholdersInSQLStatement)
|
||
|
{
|
||
|
throw StatementException(std::string("incorrect bind parameters count for SQL Statement: ") +
|
||
|
_SQLStatement);
|
||
|
}
|
||
|
|
||
|
// Just record the input vector for later execution
|
||
|
_inputParameterVector = anInputParameterVector;
|
||
|
}
|
||
|
|
||
|
|
||
|
void StatementExecutor::execute()
|
||
|
{
|
||
|
if (! _sessionHandle.isConnected()) throw NotConnectedException();
|
||
|
|
||
|
if (_state < STMT_COMPILED) throw StatementException("Statement is not compiled yet");
|
||
|
|
||
|
if (_countPlaceholdersInSQLStatement != 0 &&
|
||
|
_inputParameterVector.size() != _countPlaceholdersInSQLStatement)
|
||
|
{
|
||
|
throw StatementException("Count of Parameters in Statement different than supplied parameters");
|
||
|
}
|
||
|
|
||
|
// "transmogrify" the _inputParameterVector to the C format required by PQexecPrepared
|
||
|
|
||
|
/* - from example
|
||
|
const char *paramValues[1];
|
||
|
int paramLengths[1];
|
||
|
int paramFormats[1];
|
||
|
*/
|
||
|
|
||
|
std::vector<const char *> pParameterVector;
|
||
|
std::vector<int> parameterLengthVector;
|
||
|
std::vector<int> parameterFormatVector;
|
||
|
|
||
|
InputParameterVector::const_iterator cItr = _inputParameterVector.begin();
|
||
|
InputParameterVector::const_iterator cItrEnd = _inputParameterVector.end();
|
||
|
|
||
|
for (; cItr != cItrEnd; ++cItr)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
pParameterVector.push_back (static_cast<const char*>(cItr->pInternalRepresentation()));
|
||
|
parameterLengthVector.push_back((int)cItr->size());
|
||
|
parameterFormatVector.push_back((int)cItr->isBinary() ? 1 : 0);
|
||
|
}
|
||
|
catch (std::bad_alloc&)
|
||
|
{
|
||
|
throw StatementException("Memory Allocation Error");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// clear out any result data. One way or another it is now obsolete.
|
||
|
clearResults();
|
||
|
|
||
|
PGresult* ptrPGResult = 0;
|
||
|
{
|
||
|
Poco::FastMutex::ScopedLock mutexLocker(_sessionHandle.mutex());
|
||
|
|
||
|
ptrPGResult = PQexecPrepared (_sessionHandle,
|
||
|
_preparedStatementName.c_str(), (int)_countPlaceholdersInSQLStatement,
|
||
|
_inputParameterVector.size() != 0 ? &pParameterVector[ 0 ] : 0,
|
||
|
_inputParameterVector.size() != 0 ? ¶meterLengthVector[ 0 ] : 0,
|
||
|
_inputParameterVector.size() != 0 ? ¶meterFormatVector[ 0 ] : 0, 0);
|
||
|
}
|
||
|
|
||
|
// Don't setup to auto clear the result (ptrPGResult). It is required to retrieve the results later.
|
||
|
|
||
|
if (!ptrPGResult || (PQresultStatus(ptrPGResult) != PGRES_COMMAND_OK &&
|
||
|
PQresultStatus(ptrPGResult) != PGRES_TUPLES_OK))
|
||
|
{
|
||
|
PQResultClear resultClearer(ptrPGResult);
|
||
|
|
||
|
const char* pSeverity = PQresultErrorField(ptrPGResult, PG_DIAG_SEVERITY);
|
||
|
const char* pSQLState = PQresultErrorField(ptrPGResult, PG_DIAG_SQLSTATE);
|
||
|
const char* pDetail = PQresultErrorField(ptrPGResult, PG_DIAG_MESSAGE_DETAIL);
|
||
|
const char* pHint = PQresultErrorField(ptrPGResult, PG_DIAG_MESSAGE_HINT);
|
||
|
const char* pConstraint = PQresultErrorField(ptrPGResult, PG_DIAG_CONSTRAINT_NAME);
|
||
|
|
||
|
throw StatementException(std::string("postgresql_stmt_execute error: ")
|
||
|
+ PQresultErrorMessage (ptrPGResult)
|
||
|
+ " Severity: " + (pSeverity ? pSeverity : "N/A")
|
||
|
+ " State: " + (pSQLState ? pSQLState : "N/A")
|
||
|
+ " Detail: " + (pDetail ? pDetail : "N/A")
|
||
|
+ " Hint: " + (pHint ? pHint : "N/A")
|
||
|
+ " Constraint: " + (pConstraint ? pConstraint : "N/A"));
|
||
|
}
|
||
|
|
||
|
_pResultHandle = ptrPGResult;
|
||
|
|
||
|
// are there any results?
|
||
|
|
||
|
int affectedRowCount = 0;
|
||
|
|
||
|
if (PGRES_TUPLES_OK == PQresultStatus(_pResultHandle))
|
||
|
{
|
||
|
affectedRowCount = PQntuples(_pResultHandle);
|
||
|
|
||
|
if (affectedRowCount >= 0)
|
||
|
{
|
||
|
_affectedRowCount = static_cast<std::size_t>(affectedRowCount);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{ // non Select DML statments also have an affected row count.
|
||
|
// unfortunately PostgreSQL offers up this count as a char * - go figure!
|
||
|
const char * pNonSelectAffectedRowCountString = PQcmdTuples(_pResultHandle);
|
||
|
if (0 != pNonSelectAffectedRowCountString)
|
||
|
{
|
||
|
if ( Poco::NumberParser::tryParse(pNonSelectAffectedRowCountString, affectedRowCount)
|
||
|
&& affectedRowCount >= 0
|
||
|
)
|
||
|
{
|
||
|
_affectedRowCount = static_cast<std::size_t>(affectedRowCount);
|
||
|
_currentRow = _affectedRowCount; // no fetching on these statements!
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_state = STMT_EXECUTED;
|
||
|
}
|
||
|
|
||
|
|
||
|
bool StatementExecutor::fetch()
|
||
|
{
|
||
|
if (! _sessionHandle.isConnected())
|
||
|
{
|
||
|
throw NotConnectedException();
|
||
|
}
|
||
|
|
||
|
if (_state < STMT_EXECUTED)
|
||
|
{
|
||
|
throw StatementException("Statement is not yet executed");
|
||
|
}
|
||
|
|
||
|
std::size_t countColumns = columnsReturned();
|
||
|
|
||
|
// first time to fetch?
|
||
|
if (0 == _outputParameterVector.size())
|
||
|
{
|
||
|
// setup a output vector for the results
|
||
|
_outputParameterVector.resize(countColumns);
|
||
|
}
|
||
|
|
||
|
// already retrieved last row?
|
||
|
if (_currentRow == getAffectedRowCount())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (0 == countColumns || PGRES_TUPLES_OK != PQresultStatus(_pResultHandle))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < countColumns; ++i)
|
||
|
{
|
||
|
int fieldLength = PQgetlength(_pResultHandle, static_cast<int> (_currentRow), static_cast<int> (i));
|
||
|
|
||
|
Oid columnInternalDataType = PQftype(_pResultHandle, i); // Oid of column
|
||
|
|
||
|
_outputParameterVector.at(i).setValues(oidToColumnDataType(columnInternalDataType), // Poco::Data::MetaData version of the Column Data Type
|
||
|
columnInternalDataType, // Postgres Version
|
||
|
_currentRow, // the row number of the result
|
||
|
PQgetvalue(_pResultHandle, (int)_currentRow, i), // a pointer to the data
|
||
|
(-1 == fieldLength ? 0 : fieldLength), // the length of the data returned
|
||
|
PQgetisnull(_pResultHandle, (int)_currentRow, i) == 1 ? true : false); // is the column value null?
|
||
|
}
|
||
|
|
||
|
++_currentRow;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
std::size_t StatementExecutor::getAffectedRowCount() const
|
||
|
{
|
||
|
return _affectedRowCount;
|
||
|
}
|
||
|
|
||
|
|
||
|
std::size_t StatementExecutor::columnsReturned() const
|
||
|
{
|
||
|
return static_cast<std::size_t> (_resultColumns.size());
|
||
|
}
|
||
|
|
||
|
|
||
|
const MetaColumn& StatementExecutor::metaColumn(std::size_t aPosition) const
|
||
|
{
|
||
|
if (aPosition >= columnsReturned())
|
||
|
{
|
||
|
throw StatementException("Invalid column number for metaColumn");
|
||
|
}
|
||
|
|
||
|
return _resultColumns.at(aPosition);
|
||
|
}
|
||
|
|
||
|
|
||
|
const OutputParameter& StatementExecutor::resultColumn(std::size_t aPosition) const
|
||
|
{
|
||
|
if (aPosition >= columnsReturned())
|
||
|
{
|
||
|
throw StatementException("Invalid column number for resultColumn");
|
||
|
}
|
||
|
|
||
|
return _outputParameterVector.at(aPosition);
|
||
|
}
|
||
|
|
||
|
|
||
|
void StatementExecutor::clearResults()
|
||
|
{
|
||
|
// clear out any old result first
|
||
|
{
|
||
|
PQResultClear resultClearer(_pResultHandle);
|
||
|
}
|
||
|
|
||
|
_outputParameterVector.clear();
|
||
|
_affectedRowCount = 0;
|
||
|
_currentRow = 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
} } } // Poco::Data::PostgreSQL
|