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