// // 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 #include 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 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 pParameterVector; std::vector parameterLengthVector; std::vector parameterFormatVector; InputParameterVector::const_iterator cItr = _inputParameterVector.begin(); InputParameterVector::const_iterator cItrEnd = _inputParameterVector.end(); for (; cItr != cItrEnd; ++cItr) { try { pParameterVector.push_back (static_cast(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(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(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 (_currentRow), static_cast (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 (_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