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