mirror of
https://github.com/Telecominfraproject/wlan-cloud-lib-poco.git
synced 2025-11-01 19:17:59 +00:00
add postgresql
This commit is contained in:
432
Data/PostgreSQL/src/StatementExecutor.cpp
Normal file
432
Data/PostgreSQL/src/StatementExecutor.cpp
Normal file
@@ -0,0 +1,432 @@
|
||||
//
|
||||
// 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" // TODO: remove after C++ 11 implementation
|
||||
//#include <regex> // saved for C++ 11 implementation
|
||||
#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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* C++ 11 implementation
|
||||
|
||||
std::regex const expression("[$][0-9]+"); // match literal dollar signs followed directly by one or more digits
|
||||
|
||||
std::sregex_iterator itr(aSQLStatement.begin(), aSQLStatement.end(), expression);
|
||||
std::sregex_iterator eItr;
|
||||
|
||||
// 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;
|
||||
|
||||
while (itr != eItr)
|
||||
{
|
||||
placeholderSet.insert(itr->str());
|
||||
++itr;
|
||||
}
|
||||
*/
|
||||
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
|
||||
Reference in New Issue
Block a user