SOCI Logo Get SOCI at SourceForge.net. Fast, secure and Free Open Source software downloads

statement.cpp

Go to the documentation of this file.
00001 //
00002 // Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton
00003 // Distributed under the Boost Software License, Version 1.0.
00004 // (See accompanying file LICENSE_1_0.txt or copy at
00005 // http://www.boost.org/LICENSE_1_0.txt)
00006 //
00007 
00008 #define SOCI_POSTGRESQL_SOURCE
00009 #include "soci-postgresql.h"
00010 #include <libpq/libpq-fs.h> // libpq
00011 #include <cctype>
00012 #include <cstdio>
00013 #include <cstring>
00014 #include <ctime>
00015 #include <sstream>
00016 
00017 #ifdef SOCI_PGSQL_NOPARAMS
00018 #define SOCI_PGSQL_NOBINDBYNAME
00019 #endif // SOCI_PGSQL_NOPARAMS
00020 
00021 #ifdef _MSC_VER
00022 #pragma warning(disable:4355)
00023 #endif
00024 
00025 using namespace soci;
00026 using namespace soci::details;
00027 
00028 
00029 postgresql_statement_backend::postgresql_statement_backend(
00030     postgresql_session_backend &session)
00031      : session_(session), result_(NULL), justDescribed_(false),
00032        hasIntoElements_(false), hasVectorIntoElements_(false),
00033        hasUseElements_(false), hasVectorUseElements_(false)
00034 {
00035 }
00036 
00037 void postgresql_statement_backend::alloc()
00038 {
00039     // nothing to do here
00040 }
00041 
00042 void postgresql_statement_backend::clean_up()
00043 {
00044     if (result_ != NULL)
00045     {
00046         PQclear(result_);
00047         result_ = NULL;
00048     }
00049 }
00050 
00051 void postgresql_statement_backend::prepare(std::string const & query,
00052     statement_type stType)
00053 {
00054 #ifdef SOCI_PGSQL_NOBINDBYNAME
00055     query_ = query;
00056 #else
00057     // rewrite the query by transforming all named parameters into
00058     // the postgresql_ numbers ones (:abc -> $1, etc.)
00059 
00060     enum { normal, in_quotes, in_name } state = normal;
00061 
00062     std::string name;
00063     int position = 1;
00064 
00065     for (std::string::const_iterator it = query.begin(), end = query.end();
00066          it != end; ++it)
00067     {
00068         switch (state)
00069         {
00070         case normal:
00071             if (*it == '\'')
00072             {
00073                 query_ += *it;
00074                 state = in_quotes;
00075             }
00076             else if (*it == ':')
00077             {
00078                 // Check whether this is a cast operator (e.g. 23::float)
00079                 // and treat it as a special case, not as a named binding
00080                 const std::string::const_iterator next_it = it + 1;
00081                 if ((next_it != end) && (*next_it == ':'))
00082                 {
00083                     query_ += "::";
00084                     ++it;
00085                 }
00086                 else
00087                 {
00088                     state = in_name;
00089                 }
00090             }
00091             else // regular character, stay in the same state
00092             {
00093                 query_ += *it;
00094             }
00095             break;
00096         case in_quotes:
00097             if (*it == '\'')
00098             {
00099                 query_ += *it;
00100                 state = normal;
00101             }
00102             else // regular quoted character
00103             {
00104                 query_ += *it;
00105             }
00106             break;
00107         case in_name:
00108             if (std::isalnum(*it) || *it == '_')
00109             {
00110                 name += *it;
00111             }
00112             else // end of name
00113             {
00114                 names_.push_back(name);
00115                 name.clear();
00116                 std::ostringstream ss;
00117                 ss << '$' << position++;
00118                 query_ += ss.str();
00119                 query_ += *it;
00120                 state = normal;
00121 
00122                 // Check whether the named parameter is immediatelly
00123                 // followed by a cast operator (e.g. :name::float)
00124                 // and handle the additional colon immediately to avoid
00125                 // its misinterpretation later on.
00126                 if (*it == ':')
00127                 {
00128                     const std::string::const_iterator next_it = it + 1;
00129                     if ((next_it != end) && (*next_it == ':'))
00130                     {
00131                         query_ += ':';
00132                         ++it;
00133                     }
00134                 }
00135             }
00136             break;
00137         }
00138     }
00139 
00140     if (state == in_name)
00141     {
00142         names_.push_back(name);
00143         std::ostringstream ss;
00144         ss << '$' << position++;
00145         query_ += ss.str();
00146     }
00147 
00148 #endif // SOCI_PGSQL_NOBINDBYNAME
00149 
00150 #ifndef SOCI_PGSQL_NOPREPARE
00151 
00152     if (stType == st_repeatable_query)
00153     {
00154         statementName_ = session_.get_next_statement_name();
00155 
00156         PGresult * res = PQprepare(session_.conn_, statementName_.c_str(),
00157             query_.c_str(), static_cast<int>(names_.size()), NULL);
00158         if (res == NULL)
00159         {
00160             throw soci_error("Cannot prepare statement.");
00161         }
00162         ExecStatusType status = PQresultStatus(res);
00163         if (status != PGRES_COMMAND_OK)
00164         {
00165             throw soci_error(PQresultErrorMessage(res));
00166         }
00167         PQclear(res);
00168     }
00169 
00170     stType_ = stType;
00171 
00172 #endif // SOCI_PGSQL_NOPREPARE
00173 }
00174 
00175 statement_backend::exec_fetch_result
00176 postgresql_statement_backend::execute(int number)
00177 {
00178     // If the statement was "just described", then we know that
00179     // it was actually executed with all the use elements
00180     // already bound and pre-used. This means that the result of the
00181     // query is already on the client side, so there is no need
00182     // to re-execute it.
00183 
00184     if (justDescribed_ == false)
00185     {
00186         // This object could have been already filled with data before.
00187         clean_up();
00188 
00189         if (number > 1 && hasIntoElements_)
00190         {
00191              throw soci_error(
00192                   "Bulk use with single into elements is not supported.");
00193         }
00194 
00195         // Since the bulk operations are not natively supported by postgresql_,
00196         // we have to explicitly loop to achieve the bulk operations.
00197         // On the other hand, looping is not needed if there are single
00198         // use elements, even if there is a bulk fetch.
00199         // We know that single use and bulk use elements in the same query are
00200         // not supported anyway, so in the effect the 'number' parameter here
00201         // specifies the size of vectors (into/use), but 'numberOfExecutions'
00202         // specifies the number of loops that need to be performed.
00203         
00204         int numberOfExecutions = 1;
00205         if (number > 0)
00206         {
00207              numberOfExecutions = hasUseElements_ ? 1 : number;
00208         }
00209 
00210         if ((useByPosBuffers_.empty() == false) ||
00211             (useByNameBuffers_.empty() == false))
00212         {
00213             if ((useByPosBuffers_.empty() == false) &&
00214                 (useByNameBuffers_.empty() == false))
00215             {
00216                 throw soci_error(
00217                     "Binding for use elements must be either by position "
00218                     "or by name.");
00219             }
00220 
00221             for (int i = 0; i != numberOfExecutions; ++i)
00222             {
00223                 std::vector<char *> paramValues;
00224 
00225                 if (useByPosBuffers_.empty() == false)
00226                 {
00227                     // use elements bind by position
00228                     // the map of use buffers can be traversed
00229                     // in its natural order
00230 
00231                     for (UseByPosBuffersMap::iterator
00232                              it = useByPosBuffers_.begin(),
00233                              end = useByPosBuffers_.end();
00234                          it != end; ++it)
00235                     {
00236                         char ** buffers = it->second;
00237                         paramValues.push_back(buffers[i]);
00238                     }
00239                 }
00240                 else
00241                 {
00242                     // use elements bind by name
00243 
00244                     for (std::vector<std::string>::iterator
00245                              it = names_.begin(), end = names_.end();
00246                          it != end; ++it)
00247                     {
00248                         UseByNameBuffersMap::iterator b
00249                             = useByNameBuffers_.find(*it);
00250                         if (b == useByNameBuffers_.end())
00251                         {
00252                             std::string msg(
00253                                 "Missing use element for bind by name (");
00254                             msg += *it;
00255                             msg += ").";
00256                             throw soci_error(msg);
00257                         }
00258                         char ** buffers = b->second;
00259                         paramValues.push_back(buffers[i]);
00260                     }
00261                 }
00262 
00263 #ifdef SOCI_PGSQL_NOPARAMS
00264 
00265                 throw soci_error("Queries with parameters are not supported.");
00266 
00267 #else
00268 
00269 #ifdef SOCI_PGSQL_NOPREPARE
00270 
00271                 result_ = PQexecParams(session_.conn_, query_.c_str(),
00272                     static_cast<int>(paramValues.size()),
00273                     NULL, &paramValues[0], NULL, NULL, 0);
00274 
00275 #else
00276 
00277                 if (stType_ == st_repeatable_query)
00278                 {
00279                     // this query was separately prepared
00280 
00281                     result_ = PQexecPrepared(session_.conn_,
00282                         statementName_.c_str(),
00283                         static_cast<int>(paramValues.size()),
00284                         &paramValues[0], NULL, NULL, 0);
00285                 }
00286                 else // stType_ == st_one_time_query
00287                 {
00288                     // this query was not separately prepared and should
00289                     // be executed as a one-time query
00290 
00291                     result_ = PQexecParams(session_.conn_, query_.c_str(),
00292                         static_cast<int>(paramValues.size()),
00293                         NULL, &paramValues[0], NULL, NULL, 0);
00294                 }
00295 
00296 #endif // SOCI_PGSQL_NOPREPARE
00297 
00298 #endif // SOCI_PGSQL_NOPARAMS
00299 
00300                 if (numberOfExecutions > 1)
00301                 {
00302                     // there are only bulk use elements (no intos)
00303                     if (result_ == NULL)
00304                     {
00305                         throw soci_error("Cannot execute query.");
00306                     }
00307 
00308                     ExecStatusType status = PQresultStatus(result_);
00309                     if (status != PGRES_COMMAND_OK)
00310                     {
00311                         throw soci_error(PQresultErrorMessage(result_));
00312                     }
00313                     PQclear(result_);
00314                 }
00315             }
00316 
00317             if (numberOfExecutions > 1)
00318             {
00319                 // it was a bulk operation
00320                 result_ = NULL;
00321                 return ef_no_data;
00322             }
00323 
00324             // otherwise (no bulk), follow the code below
00325         }
00326         else
00327         {
00328             // there are no use elements
00329             // - execute the query without parameter information
00330 
00331 #ifdef SOCI_PGSQL_NOPREPARE
00332 
00333             result_ = PQexec(session_.conn_, query_.c_str());
00334 #else
00335 
00336             if (stType_ == st_repeatable_query)
00337             {
00338                 // this query was separately prepared
00339 
00340                 result_ = PQexecPrepared(session_.conn_,
00341                     statementName_.c_str(), 0, NULL, NULL, NULL, 0);
00342             }
00343             else // stType_ == st_one_time_query
00344             {
00345                 result_ = PQexec(session_.conn_, query_.c_str());
00346             }
00347 
00348 #endif // SOCI_PGSQL_NOPREPARE
00349 
00350             if (result_ == NULL)
00351             {
00352                 throw soci_error("Cannot execute query.");
00353             }
00354         }
00355     }
00356     else
00357     {
00358         // The optimization based on the existing results
00359         // from the row description can be performed only once.
00360         // If the same statement is re-executed,
00361         // it will be *really* re-executed, without reusing existing data.
00362 
00363         justDescribed_ = false;
00364     }
00365 
00366     ExecStatusType status = PQresultStatus(result_);
00367     if (status == PGRES_TUPLES_OK)
00368     {
00369         currentRow_ = 0;
00370         rowsToConsume_ = 0;
00371 
00372         numberOfRows_ = PQntuples(result_);
00373         if (numberOfRows_ == 0)
00374         {
00375             return ef_no_data;
00376         }
00377         else
00378         {
00379             if (number > 0)
00380             {
00381                 // prepare for the subsequent data consumption
00382                 return fetch(number);
00383             }
00384             else
00385             {
00386                 // execute(0) was meant to only perform the query
00387                 return ef_success;
00388             }
00389         }
00390     }
00391     else if (status == PGRES_COMMAND_OK)
00392     {
00393         return ef_no_data;
00394     }
00395     else
00396     {
00397         throw soci_error(PQresultErrorMessage(result_));
00398     }
00399 }
00400 
00401 statement_backend::exec_fetch_result
00402 postgresql_statement_backend::fetch(int number)
00403 {
00404     // Note: This function does not actually fetch anything from anywhere
00405     // - the data was already retrieved from the server in the execute()
00406     // function, and the actual consumption of this data will take place
00407     // in the postFetch functions, called for each into element.
00408     // Here, we only prepare for this to happen (to emulate "the Oracle way").
00409 
00410     // forward the "cursor" from the last fetch
00411     currentRow_ += rowsToConsume_;
00412 
00413     if (currentRow_ >= numberOfRows_)
00414     {
00415         // all rows were already consumed
00416         return ef_no_data;
00417     }
00418     else
00419     {
00420         if (currentRow_ + number > numberOfRows_)
00421         {
00422             rowsToConsume_ = numberOfRows_ - currentRow_;
00423 
00424             // this simulates the behaviour of Oracle
00425             // - when EOF is hit, we return ef_no_data even when there are
00426             // actually some rows fetched
00427             return ef_no_data;
00428         }
00429         else
00430         {
00431             rowsToConsume_ = number;
00432             return ef_success;
00433         }
00434     }
00435 }
00436 
00437 int postgresql_statement_backend::get_number_of_rows()
00438 {
00439     return numberOfRows_ - currentRow_;
00440 }
00441 
00442 std::string postgresql_statement_backend::rewrite_for_procedure_call(
00443     std::string const & query)
00444 {
00445     std::string newQuery("select ");
00446     newQuery += query;
00447     return newQuery;
00448 }
00449 
00450 int postgresql_statement_backend::prepare_for_describe()
00451 {
00452     execute(1);
00453     justDescribed_ = true;
00454 
00455     int columns = PQnfields(result_);
00456     return columns;
00457 }
00458 
00459 void postgresql_statement_backend::describe_column(int colNum, data_type & type,
00460     std::string & columnName)
00461 {
00462     // In postgresql_ column numbers start from 0
00463     int const pos = colNum - 1;
00464 
00465     unsigned long const typeOid = PQftype(result_, pos);
00466     switch (typeOid)
00467     {
00468     // Note: the following list of OIDs was taken from the pg_type table
00469     // we do not claim that this list is exchaustive or even correct.
00470 
00471                // from pg_type:
00472 
00473     case 25:   // text
00474     case 1043: // varchar
00475     case 2275: // cstring
00476     case 18:   // char
00477     case 1042: // bpchar
00478         type = dt_string;
00479         break;
00480 
00481     case 702:  // abstime
00482     case 703:  // reltime
00483     case 1082: // date
00484     case 1083: // time
00485     case 1114: // timestamp
00486     case 1184: // timestamptz
00487     case 1266: // timetz
00488         type = dt_date;
00489         break;
00490 
00491     case 700:  // float4
00492     case 701:  // float8
00493     case 1700: // numeric
00494         type = dt_double;
00495         break;
00496 
00497     case 16:   // bool
00498     case 21:   // int2
00499     case 23:   // int4
00500         type = dt_integer;
00501         break;
00502 
00503     case 20:   // int8
00504         type = dt_long_long;
00505         break;
00506 
00507     case 26:   // oid
00508         type = dt_unsigned_long;
00509         break;
00510 
00511     default:
00512          throw soci_error("Unknown data type.");
00513     }
00514 
00515     columnName = PQfname(result_, pos);
00516 }
00517 
00518 postgresql_standard_into_type_backend *
00519 postgresql_statement_backend::make_into_type_backend()
00520 {
00521     hasIntoElements_ = true;
00522     return new postgresql_standard_into_type_backend(*this);
00523 }
00524 
00525 postgresql_standard_use_type_backend *
00526 postgresql_statement_backend::make_use_type_backend()
00527 {
00528     hasUseElements_ = true;
00529     return new postgresql_standard_use_type_backend(*this);
00530 }
00531 
00532 postgresql_vector_into_type_backend *
00533 postgresql_statement_backend::make_vector_into_type_backend()
00534 {
00535     hasVectorIntoElements_ = true;
00536     return new postgresql_vector_into_type_backend(*this);
00537 }
00538 
00539 postgresql_vector_use_type_backend *
00540 postgresql_statement_backend::make_vector_use_type_backend()
00541 {
00542     hasVectorUseElements_ = true;
00543     return new postgresql_vector_use_type_backend(*this);
00544 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
SourceForge Logo

Generated on Sun Oct 3 2010 17:42:16 for EXTRAS-SOCI by Doxygen 1.7.1