1
0
mirror of https://github.com/VCMP-SqMod/SqMod.git synced 2025-01-31 18:07:14 +01:00
SqMod/vendor/POCO/Data/PostgreSQL/src/StatementExecutor.cpp
Sandu Liviu Catalin 4a6bfc086c Major plugin refactor and cleanup.
Switched to POCO library for unified platform/library interface.
Deprecated the external module API. It was creating more problems than solving.
Removed most built-in libraries in favor of system libraries for easier maintenance.
Cleaned and secured code with help from static analyzers.
2021-01-30 08:51:39 +02:00

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 ? &parameterLengthVector[ 0 ] : 0,
_inputParameterVector.size() != 0 ? &parameterFormatVector[ 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