File indexing completed on 2025-02-23 05:15:18

0001 //
0002 // Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton
0003 // MySQL backend copyright (C) 2006 Pawel Aleksander Fedorynski
0004 // Distributed under the Boost Software License, Version 1.0.
0005 // (See accompanying file LICENSE_1_0.txt or copy at
0006 // http://www.boost.org/LICENSE_1_0.txt)
0007 //
0008 
0009 #define SOCI_MYSQL_SOURCE
0010 #include "soci/mysql/soci-mysql.h"
0011 #include <cctype>
0012 #include <ciso646>
0013 
0014 using namespace soci;
0015 using namespace soci::details;
0016 using std::string;
0017 
0018 
0019 mysql_statement_backend::mysql_statement_backend(
0020     mysql_session_backend &session)
0021     : session_(session), result_(NULL),
0022        rowsAffectedBulk_(-1LL), justDescribed_(false),
0023        hasIntoElements_(false), hasVectorIntoElements_(false),
0024        hasUseElements_(false), hasVectorUseElements_(false)
0025 {
0026 }
0027 
0028 void mysql_statement_backend::alloc()
0029 {
0030     // nothing to do here.
0031 }
0032 
0033 void mysql_statement_backend::clean_up()
0034 {
0035     // 'reset' the value for a
0036     // potential new execution.
0037     rowsAffectedBulk_ = -1;
0038 
0039     if (result_ != NULL)
0040     {
0041         mysql_free_result(result_);
0042         result_ = NULL;
0043     }
0044 }
0045 
0046 void mysql_statement_backend::prepare(std::string const & query,
0047     statement_type /* eType */)
0048 {
0049     queryChunks_.clear();
0050     enum { eNormal, eInQuotes, eInName } state = eNormal;
0051 
0052     std::string name;
0053     queryChunks_.push_back("");
0054 
0055     bool escaped = false;
0056     for (std::string::const_iterator it = query.begin(), end = query.end();
0057          it != end; ++it)
0058     {
0059         switch (state)
0060         {
0061         case eNormal:
0062             if (*it == '\'')
0063             {
0064                 queryChunks_.back() += *it;
0065                 state = eInQuotes;
0066             }
0067             else if (*it == ':')
0068             {
0069                 const std::string::const_iterator next_it = it + 1;
0070                 // Check whether this is an assignment (e.g. @x:=y)
0071                 // and treat it as a special case, not as a named binding.
0072                 if (next_it != end && *next_it == '=')
0073                 {
0074                     queryChunks_.back() += ":=";
0075                     ++it;
0076                 }
0077                 else
0078                 {
0079                     state = eInName;
0080                 }
0081             }
0082             else // regular character, stay in the same state
0083             {
0084                 queryChunks_.back() += *it;
0085             }
0086             break;
0087         case eInQuotes:
0088             if (*it == '\'' && !escaped)
0089             {
0090                 queryChunks_.back() += *it;
0091                 state = eNormal;
0092             }
0093             else // regular quoted character
0094             {
0095                 queryChunks_.back() += *it;
0096             }
0097             escaped = *it == '\\' && !escaped;
0098             break;
0099         case eInName:
0100             if (std::isalnum(*it) || *it == '_')
0101             {
0102                 name += *it;
0103             }
0104             else // end of name
0105             {
0106                 names_.push_back(name);
0107                 name.clear();
0108                 queryChunks_.push_back("");
0109                 queryChunks_.back() += *it;
0110                 state = eNormal;
0111             }
0112             break;
0113         }
0114     }
0115 
0116     if (state == eInName)
0117     {
0118         names_.push_back(name);
0119     }
0120 /*
0121   cerr << "Chunks: ";
0122   for (std::vector<std::string>::iterator i = queryChunks_.begin();
0123   i != queryChunks_.end(); ++i)
0124   {
0125   cerr << "\"" << *i << "\" ";
0126   }
0127   cerr << "\nNames: ";
0128   for (std::vector<std::string>::iterator i = names_.begin();
0129   i != names_.end(); ++i)
0130   {
0131   cerr << "\"" << *i << "\" ";
0132   }
0133   cerr << endl;
0134 */
0135 }
0136 
0137 statement_backend::exec_fetch_result
0138 mysql_statement_backend::execute(int number)
0139 {
0140     if (justDescribed_ == false)
0141     {
0142         clean_up();
0143 
0144         if (number > 1 && hasIntoElements_)
0145         {
0146              throw soci_error(
0147                   "Bulk use with single into elements is not supported.");
0148         }
0149         // number - size of vectors (into/use)
0150         // numberOfExecutions - number of loops to perform
0151         int numberOfExecutions = 1;
0152         if (number > 0)
0153         {
0154              numberOfExecutions = hasUseElements_ ? 1 : number;
0155         }
0156 
0157         std::string query;
0158         if (not useByPosBuffers_.empty() or not useByNameBuffers_.empty())
0159         {
0160             if (not useByPosBuffers_.empty() and not useByNameBuffers_.empty())
0161             {
0162                 throw soci_error(
0163                     "Binding for use elements must be either by position "
0164                     "or by name.");
0165             }
0166             long long rowsAffectedBulkTemp = -1;
0167             for (int i = 0; i != numberOfExecutions; ++i)
0168             {
0169                 std::vector<char *> paramValues;
0170 
0171                 if (not useByPosBuffers_.empty())
0172                 {
0173                     // use elements bind by position
0174                     // the map of use buffers can be traversed
0175                     // in its natural order
0176 
0177                     for (UseByPosBuffersMap::iterator
0178                              it = useByPosBuffers_.begin(),
0179                              end = useByPosBuffers_.end();
0180                          it != end; ++it)
0181                     {
0182                         char **buffers = it->second;
0183                         //cerr<<"i: "<<i<<", buffers[i]: "<<buffers[i]<<endl;
0184                         paramValues.push_back(buffers[i]);
0185                     }
0186                 }
0187                 else
0188                 {
0189                     // use elements bind by name
0190 
0191                     for (std::vector<std::string>::iterator
0192                              it = names_.begin(), end = names_.end();
0193                          it != end; ++it)
0194                     {
0195                         UseByNameBuffersMap::iterator b
0196                             = useByNameBuffers_.find(*it);
0197                         if (b == useByNameBuffers_.end())
0198                         {
0199                             std::string msg(
0200                                 "Missing use element for bind by name (");
0201                             msg += *it;
0202                             msg += ").";
0203                             throw soci_error(msg);
0204                         }
0205                         char **buffers = b->second;
0206                         paramValues.push_back(buffers[i]);
0207                     }
0208                 }
0209                 //cerr << "queryChunks_.size(): "<<queryChunks_.size()<<endl;
0210                 //cerr << "paramValues.size(): "<<paramValues.size()<<endl;
0211                 if (queryChunks_.size() != paramValues.size()
0212                     and queryChunks_.size() != paramValues.size() + 1)
0213                 {
0214                     throw soci_error("Wrong number of parameters.");
0215                 }
0216 
0217                 std::vector<std::string>::const_iterator ci
0218                     = queryChunks_.begin();
0219                 for (std::vector<char*>::const_iterator
0220                          pi = paramValues.begin(), end = paramValues.end();
0221                      pi != end; ++ci, ++pi)
0222                 {
0223                     query += *ci;
0224                     query += *pi;
0225                 }
0226                 if (ci != queryChunks_.end())
0227                 {
0228                     query += *ci;
0229                 }
0230                 if (numberOfExecutions > 1)
0231                 {
0232                     // bulk operation
0233                     //std::cerr << "bulk operation:\n" << query << std::endl;
0234                     if (0 != mysql_real_query(session_.conn_, query.c_str(),
0235                             static_cast<unsigned long>(query.size())))
0236                     {
0237                         // preserve the number of rows affected so far.
0238                         rowsAffectedBulk_ = rowsAffectedBulkTemp;
0239                         throw mysql_soci_error(mysql_error(session_.conn_),
0240                             mysql_errno(session_.conn_));
0241                     }
0242                     else
0243                     {
0244                         if(rowsAffectedBulkTemp == -1)
0245                         {
0246                             rowsAffectedBulkTemp = 0;
0247                         }
0248                         rowsAffectedBulkTemp += static_cast<long long>(mysql_affected_rows(session_.conn_));
0249                     }
0250                     if (mysql_field_count(session_.conn_) != 0)
0251                     {
0252                         throw soci_error("The query shouldn't have returned"
0253                             " any data but it did.");
0254                     }
0255                     query.clear();
0256                 }
0257             }
0258             rowsAffectedBulk_ = rowsAffectedBulkTemp;
0259             if (numberOfExecutions > 1)
0260             {
0261                 // bulk
0262                 return ef_no_data;
0263             }
0264         }
0265         else
0266         {
0267             query = queryChunks_.front();
0268         }
0269 
0270         //std::cerr << query << std::endl;
0271         if (0 != mysql_real_query(session_.conn_, query.c_str(),
0272                 static_cast<unsigned long>(query.size())))
0273         {
0274             throw mysql_soci_error(mysql_error(session_.conn_),
0275                 mysql_errno(session_.conn_));
0276         }
0277         result_ = mysql_store_result(session_.conn_);
0278         if (result_ == NULL and mysql_field_count(session_.conn_) != 0)
0279         {
0280             throw mysql_soci_error(mysql_error(session_.conn_),
0281                 mysql_errno(session_.conn_));
0282         }
0283         if (result_ != NULL)
0284         {
0285             // Cache the rows offsets to have random access to the rows later.
0286             // [mysql_data_seek() is O(n) so we don't want to use it].
0287             int numrows = static_cast<int>(mysql_num_rows(result_));
0288             resultRowOffsets_.resize(numrows);
0289             for (int i = 0; i < numrows; i++)
0290             {
0291                 resultRowOffsets_[i] = mysql_row_tell(result_);
0292                 mysql_fetch_row(result_);
0293             }
0294         }
0295     }
0296     else
0297     {
0298         justDescribed_ = false;
0299     }
0300 
0301     if (result_ != NULL)
0302     {
0303         currentRow_ = 0;
0304         rowsToConsume_ = 0;
0305 
0306         numberOfRows_ = static_cast<int>(mysql_num_rows(result_));
0307         if (numberOfRows_ == 0)
0308         {
0309             return ef_no_data;
0310         }
0311         else
0312         {
0313             if (number > 0)
0314             {
0315                 // prepare for the subsequent data consumption
0316                 return fetch(number);
0317             }
0318             else
0319             {
0320                 // execute(0) was meant to only perform the query
0321                 return ef_success;
0322             }
0323         }
0324     }
0325     else
0326     {
0327         // it was not a SELECT
0328         return ef_no_data;
0329     }
0330 }
0331 
0332 statement_backend::exec_fetch_result
0333 mysql_statement_backend::fetch(int number)
0334 {
0335     // Note: This function does not actually fetch anything from anywhere
0336     // - the data was already retrieved from the server in the execute()
0337     // function, and the actual consumption of this data will take place
0338     // in the postFetch functions, called for each into element.
0339     // Here, we only prepare for this to happen (to emulate "the Oracle way").
0340 
0341     // forward the "cursor" from the last fetch
0342     currentRow_ += rowsToConsume_;
0343 
0344     if (currentRow_ >= numberOfRows_)
0345     {
0346         // all rows were already consumed
0347         return ef_no_data;
0348     }
0349     else
0350     {
0351         if (currentRow_ + number > numberOfRows_)
0352         {
0353             rowsToConsume_ = numberOfRows_ - currentRow_;
0354 
0355             // this simulates the behaviour of Oracle
0356             // - when EOF is hit, we return ef_no_data even when there are
0357             // actually some rows fetched
0358             return ef_no_data;
0359         }
0360         else
0361         {
0362             rowsToConsume_ = number;
0363             return ef_success;
0364         }
0365     }
0366 }
0367 
0368 long long mysql_statement_backend::get_affected_rows()
0369 {
0370     if (rowsAffectedBulk_ >= 0)
0371     {
0372         return rowsAffectedBulk_;
0373     }
0374     return static_cast<long long>(mysql_affected_rows(session_.conn_));
0375 }
0376 
0377 int mysql_statement_backend::get_number_of_rows()
0378 {
0379     return numberOfRows_ - currentRow_;
0380 }
0381 
0382 std::string mysql_statement_backend::get_parameter_name(int index) const
0383 {
0384     return names_.at(index);
0385 }
0386 
0387 std::string mysql_statement_backend::rewrite_for_procedure_call(
0388     std::string const &query)
0389 {
0390     std::string newQuery("select ");
0391     newQuery += query;
0392     return newQuery;
0393 }
0394 
0395 int mysql_statement_backend::prepare_for_describe()
0396 {
0397     execute(1);
0398     justDescribed_ = true;
0399 
0400     int columns = mysql_field_count(session_.conn_);
0401     return columns;
0402 }
0403 
0404 void mysql_statement_backend::describe_column(int colNum,
0405     data_type & type, std::string & columnName)
0406 {
0407     int pos = colNum - 1;
0408     MYSQL_FIELD *field = mysql_fetch_field_direct(result_, pos);
0409     switch (field->type)
0410     {
0411     case FIELD_TYPE_CHAR:       //MYSQL_TYPE_TINY:
0412     case FIELD_TYPE_SHORT:      //MYSQL_TYPE_SHORT:
0413     case FIELD_TYPE_INT24:      //MYSQL_TYPE_INT24:
0414         type = dt_integer;
0415         break;
0416     case FIELD_TYPE_LONG:       //MYSQL_TYPE_LONG:
0417         type = field->flags & UNSIGNED_FLAG ? dt_long_long
0418                                             : dt_integer;
0419         break;
0420     case FIELD_TYPE_LONGLONG:   //MYSQL_TYPE_LONGLONG:
0421         type = field->flags & UNSIGNED_FLAG ? dt_unsigned_long_long :
0422                                               dt_long_long;
0423         break;
0424     case FIELD_TYPE_FLOAT:      //MYSQL_TYPE_FLOAT:
0425     case FIELD_TYPE_DOUBLE:     //MYSQL_TYPE_DOUBLE:
0426     case FIELD_TYPE_DECIMAL:    //MYSQL_TYPE_DECIMAL:
0427     // Prior to MySQL v. 5.x there was no column type corresponding
0428     // to MYSQL_TYPE_NEWDECIMAL. However, MySQL server 5.x happily
0429     // sends field type number 246, no matter which version of libraries
0430     // the client is using.
0431     case 246:                   //MYSQL_TYPE_NEWDECIMAL:
0432         type = dt_double;
0433         break;
0434     case FIELD_TYPE_TIMESTAMP:  //MYSQL_TYPE_TIMESTAMP:
0435     case FIELD_TYPE_DATE:       //MYSQL_TYPE_DATE:
0436     case FIELD_TYPE_TIME:       //MYSQL_TYPE_TIME:
0437     case FIELD_TYPE_DATETIME:   //MYSQL_TYPE_DATETIME:
0438     case FIELD_TYPE_YEAR:       //MYSQL_TYPE_YEAR:
0439     case FIELD_TYPE_NEWDATE:    //MYSQL_TYPE_NEWDATE:
0440         type = dt_date;
0441         break;
0442 //  case MYSQL_TYPE_VARCHAR:
0443     case 245:                   //MYSQL_TYPE_JSON:
0444     case FIELD_TYPE_VAR_STRING: //MYSQL_TYPE_VAR_STRING:
0445     case FIELD_TYPE_STRING:     //MYSQL_TYPE_STRING:
0446     case FIELD_TYPE_BLOB:       // TEXT OR BLOB
0447     case FIELD_TYPE_TINY_BLOB:
0448     case FIELD_TYPE_MEDIUM_BLOB:
0449     case FIELD_TYPE_LONG_BLOB:
0450         type = dt_string;
0451         break;
0452     default:
0453         //std::cerr << "field->type: " << field->type << std::endl;
0454         throw soci_error("Unknown data type.");
0455     }
0456     columnName = field->name;
0457 }
0458 
0459 mysql_standard_into_type_backend *
0460 mysql_statement_backend::make_into_type_backend()
0461 {
0462     hasIntoElements_ = true;
0463     return new mysql_standard_into_type_backend(*this);
0464 }
0465 
0466 mysql_standard_use_type_backend *
0467 mysql_statement_backend::make_use_type_backend()
0468 {
0469     hasUseElements_ = true;
0470     return new mysql_standard_use_type_backend(*this);
0471 }
0472 
0473 mysql_vector_into_type_backend *
0474 mysql_statement_backend::make_vector_into_type_backend()
0475 {
0476     hasVectorIntoElements_ = true;
0477     return new mysql_vector_into_type_backend(*this);
0478 }
0479 
0480 mysql_vector_use_type_backend *
0481 mysql_statement_backend::make_vector_use_type_backend()
0482 {
0483     hasVectorUseElements_ = true;
0484     return new mysql_vector_use_type_backend(*this);
0485 }