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 }