/Data/PostgreSQL/src/StatementExecutor.cpp

https://github.com/obiltschnig/poco · C++ · 513 lines · 340 code · 99 blank · 74 comment · 50 complexity · 4c4b64b4bbe5f84b31d99ddd83d8bdca MD5 · raw file

  1. //
  2. // StatementExecutor.cpp
  3. //
  4. // Library: Data/PostgreSQL
  5. // Package: PostgreSQL
  6. // Module: StatementExecutor
  7. //
  8. // Copyright (c) 2015, Applied Informatics Software Engineering GmbH.
  9. // and Contributors.
  10. //
  11. // SPDX-License-Identifier: BSL-1.0
  12. //
  13. #include "Poco/Data/PostgreSQL/StatementExecutor.h"
  14. #include "Poco/Data/PostgreSQL/PostgreSQLTypes.h"
  15. #include "Poco/Format.h"
  16. #include "Poco/UUID.h"
  17. #include "Poco/UUIDGenerator.h"
  18. #include "Poco/NumberParser.h"
  19. #include "Poco/NumberParser.h"
  20. #include "Poco/RegularExpression.h" // TODO: remove after C++ 11 implementation
  21. //#include <regex> // saved for C++ 11 implementation
  22. #include <algorithm>
  23. #include <set>
  24. namespace
  25. {
  26. std::size_t countOfPlaceHoldersInSQLStatement(const std::string& aSQLStatement)
  27. {
  28. // Find unique placeholders.
  29. // Unique placeholders allow the same placeholder to be used multiple times in the same statement.
  30. // NON C++11 implementation
  31. //if (aSQLStatement.empty())
  32. //{
  33. //return 0;
  34. //}
  35. // set to hold the unique placeholders ($1, $2, $3, etc.).
  36. // A set is used because the same placeholder can be used muliple times
  37. std::set<std::string> placeholderSet;
  38. Poco::RegularExpression placeholderRE("[$][0-9]+");
  39. Poco::RegularExpression::Match match = { 0 , 0 }; // Match is a struct, not a class :-(
  40. std::size_t startingPosition = 0;
  41. while (match.offset != std::string::npos)
  42. {
  43. try
  44. {
  45. if (placeholderRE.match(aSQLStatement, startingPosition, match))
  46. {
  47. placeholderSet.insert(aSQLStatement.substr(match.offset, match.length));
  48. startingPosition = match.offset + match.length;
  49. }
  50. }
  51. catch (Poco::RegularExpressionException &)
  52. {
  53. break;
  54. }
  55. }
  56. /* C++ 11 implementation
  57. std::regex const expression("[$][0-9]+"); // match literal dollar signs followed directly by one or more digits
  58. std::sregex_iterator itr(aSQLStatement.begin(), aSQLStatement.end(), expression);
  59. std::sregex_iterator eItr;
  60. // set to hold the unique placeholders ($1, $2, $3, etc.).
  61. // A set is used because the same placeholder can be used muliple times
  62. std::set<std::string> placeholderSet;
  63. while (itr != eItr)
  64. {
  65. placeholderSet.insert(itr->str());
  66. ++itr;
  67. }
  68. */
  69. return placeholderSet.size();
  70. }
  71. } // namespace
  72. namespace Poco {
  73. namespace Data {
  74. namespace PostgreSQL {
  75. StatementExecutor::StatementExecutor(SessionHandle& aSessionHandle)
  76. : _sessionHandle (aSessionHandle),
  77. _state (STMT_INITED),
  78. _pResultHandle (0),
  79. _countPlaceholdersInSQLStatement (0),
  80. _currentRow (0),
  81. _affectedRowCount (0)
  82. {
  83. }
  84. StatementExecutor::~StatementExecutor()
  85. {
  86. try
  87. {
  88. // remove the prepared statement from the session
  89. if ( _sessionHandle.isConnected()
  90. && _state >= STMT_COMPILED
  91. )
  92. {
  93. _sessionHandle.deallocatePreparedStatement(_preparedStatementName);
  94. }
  95. PQResultClear resultClearer(_pResultHandle);
  96. }
  97. catch (...)
  98. {
  99. }
  100. }
  101. StatementExecutor::State
  102. StatementExecutor::state() const
  103. {
  104. return _state;
  105. }
  106. void StatementExecutor::prepare(const std::string& aSQLStatement)
  107. {
  108. if (! _sessionHandle.isConnected())
  109. {
  110. throw NotConnectedException();
  111. }
  112. if (_state >= STMT_COMPILED)
  113. {
  114. return;
  115. }
  116. // clear out the metadata. One way or another it is now obsolete.
  117. _countPlaceholdersInSQLStatement = 0;
  118. _SQLStatement= std::string();
  119. _preparedStatementName = std::string();
  120. _resultColumns.clear();
  121. // clear out any result data. One way or another it is now obsolete.
  122. clearResults();
  123. // prepare parameters for the call to PQprepare
  124. const char* ptrCSQLStatement = aSQLStatement.c_str();
  125. std::size_t countPlaceholdersInSQLStatement = countOfPlaceHoldersInSQLStatement(aSQLStatement);
  126. Poco::UUIDGenerator& generator = Poco::UUIDGenerator::defaultGenerator();
  127. Poco::UUID uuid(generator.create()); // time based
  128. std::string statementName = uuid.toString();
  129. statementName.insert(0, 1, 'p'); // prepared statement names can't start with a number
  130. std::replace(statementName.begin(), statementName.end(), '-', 'p'); // PostgreSQL doesn't like dashes in prepared statement names
  131. const char* pStatementName = statementName.c_str();
  132. PGresult* ptrPGResult = 0;
  133. {
  134. // lock the session
  135. Poco::FastMutex::ScopedLock mutexLocker(_sessionHandle.mutex());
  136. // prepare the statement - temporary PGresult returned
  137. ptrPGResult = PQprepare(_sessionHandle,
  138. pStatementName,
  139. ptrCSQLStatement,
  140. countPlaceholdersInSQLStatement,
  141. 0 // not specifying type Oids
  142. );
  143. }
  144. {
  145. // setup to clear the result from PQprepare
  146. PQResultClear resultClearer(ptrPGResult);
  147. if ( ! ptrPGResult
  148. || PQresultStatus(ptrPGResult) != PGRES_COMMAND_OK
  149. )
  150. {
  151. throw StatementException(std::string("postgresql_stmt_prepare error: ") + PQresultErrorMessage (ptrPGResult) + " " + aSQLStatement);
  152. }
  153. }
  154. // Determine what the structure of a statement result will look like
  155. {
  156. // lock the session
  157. Poco::FastMutex::ScopedLock mutexLocker(_sessionHandle.mutex());
  158. ptrPGResult = PQdescribePrepared(_sessionHandle, pStatementName);
  159. }
  160. {
  161. PQResultClear resultClearer(ptrPGResult);
  162. if (! ptrPGResult
  163. || PQresultStatus(ptrPGResult) != PGRES_COMMAND_OK
  164. )
  165. {
  166. throw StatementException(std::string("postgresql_stmt_describe error: ") + PQresultErrorMessage (ptrPGResult) + " " + aSQLStatement);
  167. }
  168. // remember the structure of the statement result
  169. int fieldCount = PQnfields(ptrPGResult);
  170. if (fieldCount < 0)
  171. {
  172. fieldCount = 0;
  173. }
  174. for (int i = 0; i < fieldCount; ++i)
  175. {
  176. _resultColumns.push_back(
  177. MetaColumn(i, // position
  178. PQfname(ptrPGResult, i), // name
  179. oidToColumnDataType(PQftype(ptrPGResult, i)), // Poco::Data::MetaColumn type
  180. 0, // column data lengths are not correct as returned by PQfsize() for the various column types
  181. 0, // column precisions are not correct as returned by PQfmod() for the various column types
  182. true // nullable? - no efficient way to tell at this point, so assume yes
  183. )
  184. );
  185. }
  186. }
  187. _SQLStatement = aSQLStatement;
  188. _preparedStatementName = statementName;
  189. _countPlaceholdersInSQLStatement = countPlaceholdersInSQLStatement;
  190. _state = STMT_COMPILED; // must be last
  191. }
  192. void StatementExecutor::bindParams(const InputParameterVector& anInputParameterVector)
  193. {
  194. if (! _sessionHandle.isConnected())
  195. {
  196. throw NotConnectedException();
  197. }
  198. if (_state < STMT_COMPILED)
  199. {
  200. throw StatementException("Statement is not compiled yet");
  201. }
  202. if (anInputParameterVector.size() != _countPlaceholdersInSQLStatement)
  203. {
  204. throw StatementException(std::string("incorrect bind parameters count for SQL Statement: ") + _SQLStatement);
  205. }
  206. // Just record the input vector for later execution
  207. _inputParameterVector = anInputParameterVector;
  208. }
  209. void StatementExecutor::execute()
  210. {
  211. if (! _sessionHandle.isConnected())
  212. {
  213. throw NotConnectedException();
  214. }
  215. if (_state < STMT_COMPILED)
  216. {
  217. throw StatementException("Statement is not compiled yet");
  218. }
  219. if ( _countPlaceholdersInSQLStatement != 0
  220. && _inputParameterVector.size() != _countPlaceholdersInSQLStatement
  221. )
  222. {
  223. throw StatementException("Count of Parameters in Statement different than supplied parameters");
  224. }
  225. // "transmogrify" the _inputParameterVector to the C format required by PQexecPrepared
  226. /* - from example
  227. const char *paramValues[1];
  228. int paramLengths[1];
  229. int paramFormats[1];
  230. */
  231. std::vector<const char *> pParameterVector;
  232. std::vector<int> parameterLengthVector;
  233. std::vector<int> parameterFormatVector;
  234. InputParameterVector::const_iterator cItr = _inputParameterVector.begin();
  235. InputParameterVector::const_iterator cItrEnd = _inputParameterVector.end();
  236. for (; cItr != cItrEnd; ++cItr)
  237. {
  238. try
  239. {
  240. pParameterVector.push_back (static_cast<const char*>(cItr->pInternalRepresentation()));
  241. parameterLengthVector.push_back(cItr->size());
  242. parameterFormatVector.push_back(cItr->isBinary() ? 1 : 0);
  243. }
  244. catch (std::bad_alloc&)
  245. {
  246. throw StatementException("Memory Allocation Error");
  247. }
  248. }
  249. // clear out any result data. One way or another it is now obsolete.
  250. clearResults();
  251. PGresult* ptrPGResult = 0;
  252. {
  253. Poco::FastMutex::ScopedLock mutexLocker(_sessionHandle.mutex());
  254. /* - from api doc
  255. PGresult *PQexecPrepared(PGconn *conn,
  256. const char *stmtName,
  257. int nParams,
  258. const char * const *paramValues,
  259. const int *paramLengths,
  260. const int *paramFormats,
  261. int resultFormat);
  262. */
  263. ptrPGResult = PQexecPrepared (_sessionHandle,
  264. _preparedStatementName.c_str(),
  265. _countPlaceholdersInSQLStatement,
  266. _inputParameterVector.size() != 0 ? &pParameterVector[ 0 ] : 0,
  267. _inputParameterVector.size() != 0 ? &parameterLengthVector[ 0 ] : 0,
  268. _inputParameterVector.size() != 0 ? &parameterFormatVector[ 0 ] : 0,
  269. 0 // text based result please!
  270. );
  271. }
  272. // Don't setup to auto clear the result (ptrPGResult). It is required to retrieve the results later.
  273. if ( ! ptrPGResult
  274. || (PQresultStatus(ptrPGResult) != PGRES_COMMAND_OK
  275. && PQresultStatus(ptrPGResult) != PGRES_TUPLES_OK)
  276. )
  277. {
  278. PQResultClear resultClearer(ptrPGResult);
  279. const char* pSeverity = PQresultErrorField(ptrPGResult, PG_DIAG_SEVERITY);
  280. const char* pSQLState = PQresultErrorField(ptrPGResult, PG_DIAG_SQLSTATE);
  281. const char* pDetail = PQresultErrorField(ptrPGResult, PG_DIAG_MESSAGE_DETAIL);
  282. const char* pHint = PQresultErrorField(ptrPGResult, PG_DIAG_MESSAGE_HINT);
  283. const char* pConstraint = PQresultErrorField(ptrPGResult, PG_DIAG_CONSTRAINT_NAME);
  284. throw StatementException(std::string("postgresql_stmt_execute error: ")
  285. + PQresultErrorMessage (ptrPGResult)
  286. + " Severity: "
  287. + (pSeverity ? pSeverity : "N/A")
  288. + " State: "
  289. + (pSQLState ? pSQLState : "N/A")
  290. + " Detail: "
  291. + (pDetail ? pDetail : "N/A")
  292. + " Hint: "
  293. + (pHint ? pHint : "N/A")
  294. + " Constraint: "
  295. + (pConstraint ? pConstraint : "N/A")
  296. );
  297. }
  298. _pResultHandle = ptrPGResult;
  299. // are there any results?
  300. int affectedRowCount = 0;
  301. if (PGRES_TUPLES_OK == PQresultStatus(_pResultHandle))
  302. {
  303. affectedRowCount = PQntuples(_pResultHandle);
  304. if (affectedRowCount >= 0)
  305. {
  306. _affectedRowCount = static_cast<std::size_t>(affectedRowCount);
  307. }
  308. }
  309. else
  310. { // non Select DML statments also have an affected row count.
  311. // unfortunately PostgreSQL offers up this count as a char * - go figure!
  312. const char * pNonSelectAffectedRowCountString = PQcmdTuples(_pResultHandle);
  313. if (0 != pNonSelectAffectedRowCountString)
  314. {
  315. if ( Poco::NumberParser::tryParse(pNonSelectAffectedRowCountString, affectedRowCount)
  316. && affectedRowCount >= 0
  317. )
  318. {
  319. _affectedRowCount = static_cast<std::size_t>(affectedRowCount);
  320. _currentRow = _affectedRowCount; // no fetching on these statements!
  321. }
  322. }
  323. }
  324. _state = STMT_EXECUTED;
  325. }
  326. bool
  327. StatementExecutor::fetch()
  328. {
  329. if (! _sessionHandle.isConnected())
  330. {
  331. throw NotConnectedException();
  332. }
  333. if (_state < STMT_EXECUTED)
  334. {
  335. throw StatementException("Statement is not yet executed");
  336. }
  337. std::size_t countColumns = columnsReturned();
  338. // first time to fetch?
  339. if (0 == _outputParameterVector.size())
  340. {
  341. // setup a output vector for the results
  342. _outputParameterVector.resize(countColumns);
  343. }
  344. // already retrieved last row?
  345. if (_currentRow == getAffectedRowCount())
  346. {
  347. return false;
  348. }
  349. if (0 == countColumns
  350. || PGRES_TUPLES_OK != PQresultStatus(_pResultHandle)
  351. )
  352. {
  353. return false;
  354. }
  355. for (std::size_t i = 0; i < countColumns; ++i)
  356. {
  357. int fieldLength = PQgetlength(_pResultHandle, static_cast<int> (_currentRow), static_cast<int> (i));
  358. Oid columnInternalDataType = PQftype(_pResultHandle, i); // Oid of column
  359. _outputParameterVector.at(i).setValues(oidToColumnDataType(columnInternalDataType), // Poco::Data::MetaData version of the Column Data Type
  360. columnInternalDataType, // Postegres Version
  361. _currentRow, // the row number of the result
  362. PQgetvalue(_pResultHandle, _currentRow, i), // a pointer to the data
  363. (-1 == fieldLength ? 0 : fieldLength), // the length of the data returned
  364. PQgetisnull(_pResultHandle, _currentRow, i) == 1 ? true : false // is the column value null?
  365. );
  366. }
  367. // advance to next row
  368. ++_currentRow;
  369. return true;
  370. }
  371. std::size_t
  372. StatementExecutor::getAffectedRowCount() const
  373. {
  374. return _affectedRowCount;
  375. }
  376. std::size_t
  377. StatementExecutor::columnsReturned() const
  378. {
  379. return static_cast<std::size_t> (_resultColumns.size());
  380. }
  381. const MetaColumn&
  382. StatementExecutor::metaColumn(std::size_t aPosition) const
  383. {
  384. if (aPosition >= columnsReturned())
  385. {
  386. throw StatementException("Invalid column number for metaColumn");
  387. }
  388. return _resultColumns.at(aPosition);
  389. }
  390. const OutputParameter&
  391. StatementExecutor::resultColumn(std::size_t aPosition) const
  392. {
  393. if (aPosition >= columnsReturned())
  394. {
  395. throw StatementException("Invalid column number for resultColumn");
  396. }
  397. return _outputParameterVector.at(aPosition);
  398. }
  399. void
  400. StatementExecutor::clearResults()
  401. {
  402. // clear out any old result first
  403. {
  404. PQResultClear resultClearer(_pResultHandle);
  405. }
  406. _outputParameterVector.clear();
  407. _affectedRowCount = 0;
  408. _currentRow = 0;
  409. }
  410. }}}