backends/postgresql/statement.cpp
Go to the documentation of this file.
1 //
2 // Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton
3 // Distributed under the Boost Software License, Version 1.0.
4 // (See accompanying file LICENSE_1_0.txt or copy at
5 // http://www.boost.org/LICENSE_1_0.txt)
6 //
7 
8 #define SOCI_POSTGRESQL_SOURCE
9 #include "soci-postgresql.h"
10 #include <soci-platform.h>
11 #include <libpq/libpq-fs.h> // libpq
12 #include <cassert>
13 #include <cctype>
14 #include <cstdio>
15 #include <cstdlib>
16 #include <cstring>
17 #include <ctime>
18 #include <sstream>
19 
20 #ifdef SOCI_POSTGRESQL_NOPARAMS
21 #ifndef SOCI_POSTGRESQL_NOBINDBYNAME
22 #define SOCI_POSTGRESQL_NOBINDBYNAME
23 #endif // SOCI_POSTGRESQL_NOBINDBYNAME
24 #endif // SOCI_POSTGRESQL_NOPARAMS
25 
26 #ifdef _MSC_VER
27 #pragma warning(disable:4355)
28 #endif
29 
30 using namespace soci;
31 using namespace soci::details;
32 
35  : session_(session)
36  , rowsAffectedBulk_(-1LL), justDescribed_(false)
37  , hasIntoElements_(false), hasVectorIntoElements_(false)
38  , hasUseElements_(false), hasVectorUseElements_(false)
39 {
40 }
41 
43 {
44  if (statementName_.empty() == false)
45  {
46  try
47  {
49  }
50  catch (...)
51  {
52  // Don't allow exceptions to escape from dtor. Suppressing them is
53  // not ideal, but terminating the program, as would happen if we're
54  // already unwinding the stack because of a previous exception,
55  // would be even worse.
56  }
57  }
58 }
59 
61 {
62  // nothing to do here
63 }
64 
66 {
67  // 'reset' the value for a
68  // potential new execution.
69  rowsAffectedBulk_ = -1;
70 
71  // nothing to do here
72 }
73 
74 void postgresql_statement_backend::prepare(std::string const & query,
75  statement_type stType)
76 {
77 #ifdef SOCI_POSTGRESQL_NOBINDBYNAME
78  query_ = query;
79 #else
80  // rewrite the query by transforming all named parameters into
81  // the postgresql_ numbers ones (:abc -> $1, etc.)
82 
83  enum { normal, in_quotes, in_name } state = normal;
84 
85  std::string name;
86  int position = 1;
87 
88  for (std::string::const_iterator it = query.begin(), end = query.end();
89  it != end; ++it)
90  {
91  switch (state)
92  {
93  case normal:
94  if (*it == '\'')
95  {
96  query_ += *it;
97  state = in_quotes;
98  }
99  else if (*it == ':')
100  {
101  // Check whether this is a cast operator (e.g. 23::float)
102  // and treat it as a special case, not as a named binding
103  const std::string::const_iterator next_it = it + 1;
104  if ((next_it != end) && (*next_it == ':'))
105  {
106  query_ += "::";
107  ++it;
108  }
109  // Check whether this is an assignment(e.g. x:=y)
110  // and treat it as a special case, not as a named binding
111  else if ((next_it != end) && (*next_it == '='))
112  {
113  query_ += ":=";
114  ++it;
115  }
116  else
117  {
118  state = in_name;
119  }
120  }
121  else // regular character, stay in the same state
122  {
123  query_ += *it;
124  }
125  break;
126  case in_quotes:
127  if (*it == '\'')
128  {
129  query_ += *it;
130  state = normal;
131  }
132  else // regular quoted character
133  {
134  query_ += *it;
135  }
136  break;
137  case in_name:
138  if (std::isalnum(*it) || *it == '_')
139  {
140  name += *it;
141  }
142  else // end of name
143  {
144  names_.push_back(name);
145  name.clear();
146  std::ostringstream ss;
147  ss << '$' << position++;
148  query_ += ss.str();
149  query_ += *it;
150  state = normal;
151 
152  // Check whether the named parameter is immediatelly
153  // followed by a cast operator (e.g. :name::float)
154  // and handle the additional colon immediately to avoid
155  // its misinterpretation later on.
156  if (*it == ':')
157  {
158  const std::string::const_iterator next_it = it + 1;
159  if ((next_it != end) && (*next_it == ':'))
160  {
161  query_ += ':';
162  ++it;
163  }
164  }
165  }
166  break;
167  }
168  }
169 
170  if (state == in_name)
171  {
172  names_.push_back(name);
173  std::ostringstream ss;
174  ss << '$' << position++;
175  query_ += ss.str();
176  }
177 
178 #endif // SOCI_POSTGRESQL_NOBINDBYNAME
179 
180 #ifndef SOCI_POSTGRESQL_NOPREPARE
181 
182  if (stType == st_repeatable_query)
183  {
184  assert(statementName_.empty());
185 
186  // Holding the name temporarily in this var because
187  // if it fails to prepare it we can't DEALLOCATE it.
188  std::string statementName = session_.get_next_statement_name();
189 
190  postgresql_result result(
191  PQprepare(session_.conn_, statementName.c_str(),
192  query_.c_str(), static_cast<int>(names_.size()), NULL));
193  result.check_for_errors("Cannot prepare statement.");
194 
195  // Now it's safe to save this info.
196  statementName_ = statementName;
197  }
198 
199  stType_ = stType;
200 
201 #endif // SOCI_POSTGRESQL_NOPREPARE
202 }
203 
206 {
207  // If the statement was "just described", then we know that
208  // it was actually executed with all the use elements
209  // already bound and pre-used. This means that the result of the
210  // query is already on the client side, so there is no need
211  // to re-execute it.
212 
213  if (justDescribed_ == false)
214  {
215  // This object could have been already filled with data before.
216  clean_up();
217 
218  if (number > 1 && hasIntoElements_)
219  {
220  throw soci_error(
221  "Bulk use with single into elements is not supported.");
222  }
223 
224  // Since the bulk operations are not natively supported by postgresql_,
225  // we have to explicitly loop to achieve the bulk operations.
226  // On the other hand, looping is not needed if there are single
227  // use elements, even if there is a bulk fetch.
228  // We know that single use and bulk use elements in the same query are
229  // not supported anyway, so in the effect the 'number' parameter here
230  // specifies the size of vectors (into/use), but 'numberOfExecutions'
231  // specifies the number of loops that need to be performed.
232 
233  int numberOfExecutions = 1;
234  if (number > 0)
235  {
236  numberOfExecutions = hasUseElements_ ? 1 : number;
237  }
238 
239  if ((useByPosBuffers_.empty() == false) ||
240  (useByNameBuffers_.empty() == false))
241  {
242  if ((useByPosBuffers_.empty() == false) &&
243  (useByNameBuffers_.empty() == false))
244  {
245  throw soci_error(
246  "Binding for use elements must be either by position "
247  "or by name.");
248  }
249  long long rowsAffectedBulkTemp = 0;
250  for (int i = 0; i != numberOfExecutions; ++i)
251  {
252  std::vector<char *> paramValues;
253 
254  if (useByPosBuffers_.empty() == false)
255  {
256  // use elements bind by position
257  // the map of use buffers can be traversed
258  // in its natural order
259 
260  for (UseByPosBuffersMap::iterator
261  it = useByPosBuffers_.begin(),
262  end = useByPosBuffers_.end();
263  it != end; ++it)
264  {
265  char ** buffers = it->second;
266  paramValues.push_back(buffers[i]);
267  }
268  }
269  else
270  {
271  // use elements bind by name
272 
273  for (std::vector<std::string>::iterator
274  it = names_.begin(), end = names_.end();
275  it != end; ++it)
276  {
277  UseByNameBuffersMap::iterator b
278  = useByNameBuffers_.find(*it);
279  if (b == useByNameBuffers_.end())
280  {
281  std::string msg(
282  "Missing use element for bind by name (");
283  msg += *it;
284  msg += ").";
285  throw soci_error(msg);
286  }
287  char ** buffers = b->second;
288  paramValues.push_back(buffers[i]);
289  }
290  }
291 
292 #ifdef SOCI_POSTGRESQL_NOPARAMS
293 
294  throw soci_error("Queries with parameters are not supported.");
295 
296 #else
297 
298 #ifdef SOCI_POSTGRESQL_NOPREPARE
299 
300  result_.reset(PQexecParams(session_.conn_, query_.c_str(),
301  static_cast<int>(paramValues.size()),
302  NULL, &paramValues[0], NULL, NULL, 0));
303 #else
305  {
306  // this query was separately prepared
307 
308  result_.reset(PQexecPrepared(session_.conn_,
309  statementName_.c_str(),
310  static_cast<int>(paramValues.size()),
311  &paramValues[0], NULL, NULL, 0));
312  }
313  else // stType_ == st_one_time_query
314  {
315  // this query was not separately prepared and should
316  // be executed as a one-time query
317 
318  result_.reset(PQexecParams(session_.conn_, query_.c_str(),
319  static_cast<int>(paramValues.size()),
320  NULL, &paramValues[0], NULL, NULL, 0));
321  }
322 
323 #endif // SOCI_POSTGRESQL_NOPREPARE
324 
325 #endif // SOCI_POSTGRESQL_NOPARAMS
326 
327  if (numberOfExecutions > 1)
328  {
329  // there are only bulk use elements (no intos)
330 
331  // preserve the number of rows affected so far.
332  rowsAffectedBulk_ = rowsAffectedBulkTemp;
333 
334  result_.check_for_errors("Cannot execute query.");
335 
336  rowsAffectedBulkTemp += get_affected_rows();
337  }
338  }
339  rowsAffectedBulk_ = rowsAffectedBulkTemp;
340 
341  if (numberOfExecutions > 1)
342  {
343  // it was a bulk operation
344  result_.reset();
345  return ef_no_data;
346  }
347 
348  // otherwise (no bulk), follow the code below
349  }
350  else
351  {
352  // there are no use elements
353  // - execute the query without parameter information
354 
355 #ifdef SOCI_POSTGRESQL_NOPREPARE
356 
357  result_.reset(PQexec(session_.conn_, query_.c_str()));
358 #else
360  {
361  // this query was separately prepared
362 
363  result_.reset(PQexecPrepared(session_.conn_,
364  statementName_.c_str(), 0, NULL, NULL, NULL, 0));
365  }
366  else // stType_ == st_one_time_query
367  {
368  result_.reset(PQexec(session_.conn_, query_.c_str()));
369  }
370 
371 #endif // SOCI_POSTGRESQL_NOPREPARE
372  }
373  }
374  else
375  {
376  // The optimization based on the existing results
377  // from the row description can be performed only once.
378  // If the same statement is re-executed,
379  // it will be *really* re-executed, without reusing existing data.
380 
381  justDescribed_ = false;
382  }
383 
384  if (result_.check_for_data("Cannot execute query."))
385  {
386  currentRow_ = 0;
387  rowsToConsume_ = 0;
388 
389  numberOfRows_ = PQntuples(result_);
390  if (numberOfRows_ == 0)
391  {
392  return ef_no_data;
393  }
394  else
395  {
396  if (number > 0)
397  {
398  // prepare for the subsequent data consumption
399  return fetch(number);
400  }
401  else
402  {
403  // execute(0) was meant to only perform the query
404  return ef_success;
405  }
406  }
407  }
408  else
409  {
410  return ef_no_data;
411  }
412 }
413 
416 {
417  // Note: This function does not actually fetch anything from anywhere
418  // - the data was already retrieved from the server in the execute()
419  // function, and the actual consumption of this data will take place
420  // in the postFetch functions, called for each into element.
421  // Here, we only prepare for this to happen (to emulate "the Oracle way").
422 
423  // forward the "cursor" from the last fetch
425 
426  if (currentRow_ >= numberOfRows_)
427  {
428  // all rows were already consumed
429  return ef_no_data;
430  }
431  else
432  {
433  if (currentRow_ + number > numberOfRows_)
434  {
436 
437  // this simulates the behaviour of Oracle
438  // - when EOF is hit, we return ef_no_data even when there are
439  // actually some rows fetched
440  return ef_no_data;
441  }
442  else
443  {
444  rowsToConsume_ = number;
445  return ef_success;
446  }
447  }
448 }
449 
451 {
452  // PQcmdTuples() doesn't really modify the result but it takes a non-const
453  // pointer to it, so we can't rely on implicit conversion here.
454  const char * const resultStr = PQcmdTuples(result_.get_result());
455  char * end;
456  long long result = std::strtoll(resultStr, &end, 0);
457  if (end != resultStr)
458  {
459  return result;
460  }
461  else if (rowsAffectedBulk_ >= 0)
462  {
463  return rowsAffectedBulk_;
464  }
465  else
466  {
467  return -1;
468  }
469 }
470 
472 {
473  return numberOfRows_ - currentRow_;
474 }
475 
477  std::string const & query)
478 {
479  std::string newQuery("select ");
480  newQuery += query;
481  return newQuery;
482 }
483 
485 {
486  execute(1);
487  justDescribed_ = true;
488 
489  int columns = PQnfields(result_);
490  return columns;
491 }
492 
494  std::string & columnName)
495 {
496  // In postgresql_ column numbers start from 0
497  int const pos = colNum - 1;
498 
499  unsigned long const typeOid = PQftype(result_, pos);
500  switch (typeOid)
501  {
502  // Note: the following list of OIDs was taken from the pg_type table
503  // we do not claim that this list is exchaustive or even correct.
504 
505  // from pg_type:
506 
507  case 25: // text
508  case 1043: // varchar
509  case 2275: // cstring
510  case 18: // char
511  case 1042: // bpchar
512  case 142: // xml
513  case 114: // json
514  case 17: // bytea
515  case 2950: // uuid
516  type = dt_string;
517  break;
518 
519  case 702: // abstime
520  case 703: // reltime
521  case 1082: // date
522  case 1083: // time
523  case 1114: // timestamp
524  case 1184: // timestamptz
525  case 1266: // timetz
526  type = dt_date;
527  break;
528 
529  case 700: // float4
530  case 701: // float8
531  case 1700: // numeric
532  type = dt_double;
533  break;
534 
535  case 16: // bool
536  case 21: // int2
537  case 23: // int4
538  case 26: // oid
539  type = dt_integer;
540  break;
541 
542  case 20: // int8
543  type = dt_long_long;
544  break;
545 
546  default:
547  {
548  int form = PQfformat(result_, pos);
549  int size = PQfsize(result_, pos);
550  if (form == 0 && size == -1)
551  {
552  type = dt_string;
553  }
554  else
555  {
556  std::stringstream message;
557  message << "unknown data type with typelem: " << typeOid << " for colNum: " << colNum << " with name: " << PQfname(result_, pos);
558  throw soci_error(message.str());
559  }
560  }
561  }
562 
563  columnName = PQfname(result_, pos);
564 }
565 
568 {
569  hasIntoElements_ = true;
570  return new postgresql_standard_into_type_backend(*this);
571 }
572 
575 {
576  hasUseElements_ = true;
577  return new postgresql_standard_use_type_backend(*this);
578 }
579 
582 {
583  hasVectorIntoElements_ = true;
584  return new postgresql_vector_into_type_backend(*this);
585 }
586 
589 {
590  hasVectorUseElements_ = true;
591  return new postgresql_vector_use_type_backend(*this);
592 }
details::statement_type stType_
bool check_for_data(char const *errMsg) const
void deallocate_prepared_statement(const std::string &statementName)
virtual void prepare(std::string const &query, details::statement_type stType)
virtual postgresql_vector_into_type_backend * make_vector_into_type_backend()
details::postgresql_result result_
virtual postgresql_standard_into_type_backend * make_into_type_backend()
virtual postgresql_standard_use_type_backend * make_use_type_backend()
virtual exec_fetch_result fetch(int number)
postgresql_statement_backend(postgresql_session_backend &session)
virtual postgresql_vector_use_type_backend * make_vector_use_type_backend()
std::vector< std::string > names_
postgresql_session_backend & session_
virtual void describe_column(int colNum, data_type &dtype, std::string &columnName)
void reset(PGresult *result=NULL)
void check_for_errors(char const *errMsg) const
virtual std::string rewrite_for_procedure_call(std::string const &query)
virtual exec_fetch_result execute(int number)


asr_lib_ism
Author(s): Hanselmann Fabian, Heller Florian, Heizmann Heinrich, Kübler Marcel, Mehlhaus Jonas, Meißner Pascal, Qattan Mohamad, Reckling Reno, Stroh Daniel
autogenerated on Wed Jan 8 2020 04:02:41