fastcgi++
|
00001 00002 /*************************************************************************** 00003 * Copyright (C) 2007 Eddie Carle [eddie@erctech.org] * 00004 * * 00005 * This file is part of fastcgi++. * 00006 * * 00007 * fastcgi++ is free software: you can redistribute it and/or modify it * 00008 * under the terms of the GNU Lesser General Public License as published * 00009 * by the Free Software Foundation, either version 3 of the License, or (at * 00010 * your option) any later version. * 00011 * * 00012 * fastcgi++ is distributed in the hope that it will be useful, but WITHOUT * 00013 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * 00014 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * 00015 * License for more details. * 00016 * * 00017 * You should have received a copy of the GNU Lesser General Public License * 00018 * along with fastcgi++. If not, see <http://www.gnu.org/licenses/>. * 00019 ****************************************************************************/ 00020 00021 00022 #include <asql/mysql.hpp> 00023 #include <utf8_codecvt.hpp> 00024 #include <cstdlib> 00025 00026 void ASql::MySQL::Connection::connect(const char* host, const char* user, const char* passwd, const char* db, unsigned int port, const char* unix_socket, unsigned long client_flag, const char* const charset) 00027 { 00028 if(m_initialized) 00029 { 00030 for(unsigned int i=0; i<threads(); ++i) 00031 { 00032 mysql_stmt_close(foundRowsStatement[i]); 00033 mysql_close(&m_connection[i]); 00034 } 00035 m_initialized = false; 00036 } 00037 00038 for(unsigned int i=0; i<threads(); ++i) 00039 { 00040 if(!mysql_init(&m_connection[i])) 00041 throw Error(&m_connection[i]); 00042 00043 if(!mysql_real_connect(&m_connection[i], host, user, passwd, db, port, unix_socket, client_flag)) 00044 throw Error(&m_connection[i]); 00045 00046 if(mysql_set_character_set(&m_connection[i], charset)) 00047 throw Error(&m_connection[i]); 00048 00049 if(mysql_autocommit(&m_connection[i], 0)) 00050 throw Error(&m_connection[i]); 00051 00052 if(!(foundRowsStatement[i] = mysql_stmt_init(&m_connection[i]))) 00053 throw Error(&m_connection[i]); 00054 00055 if(mysql_stmt_prepare(foundRowsStatement[i], "SELECT FOUND_ROWS()", 19)) 00056 throw Error(foundRowsStatement[i]); 00057 00058 std::memset(&foundRowsBinding[i], 0, sizeof(MYSQL_BIND)); 00059 foundRowsBinding[i].buffer_type = MYSQL_TYPE_LONGLONG; 00060 foundRowsBinding[i].is_unsigned = 1; 00061 } 00062 00063 m_initialized = true; 00064 } 00065 00066 ASql::MySQL::Connection::~Connection() 00067 { 00068 if(m_initialized) 00069 { 00070 for(unsigned int i=0; i<threads(); ++i) 00071 { 00072 mysql_stmt_close(foundRowsStatement[i]); 00073 mysql_close(&m_connection[i]); 00074 } 00075 } 00076 } 00077 00078 void ASql::MySQL::Connection::getFoundRows(unsigned long long* const& rows, const unsigned int thread) 00079 { 00080 if(mysql_stmt_bind_param(foundRowsStatement[thread], 0)) 00081 throw Error(foundRowsStatement[thread]); 00082 00083 if(mysql_stmt_execute(foundRowsStatement[thread])) 00084 throw Error(foundRowsStatement[thread]); 00085 00086 foundRowsBinding[thread].buffer = rows; 00087 if(mysql_stmt_bind_result(foundRowsStatement[thread], &foundRowsBinding[thread])) 00088 throw Error(foundRowsStatement[thread]); 00089 00090 if(mysql_stmt_fetch(foundRowsStatement[thread])) 00091 throw Error(foundRowsStatement[thread]); 00092 mysql_stmt_free_result(foundRowsStatement[thread]); 00093 mysql_stmt_reset(foundRowsStatement[thread]); 00094 00095 } 00096 00097 void ASql::MySQL::Statement::init(const char* const& queryString, const size_t& queryLength, const Data::Set* const parameterSet, const Data::Set* const resultSet, bool customPlaceholders) 00098 { 00099 if(m_initialized) 00100 { 00101 for(unsigned int i=0; i<connection.threads(); ++i) 00102 mysql_stmt_close(stmt[i]); 00103 m_initialized = false; 00104 } 00105 00106 const char* realQueryString = queryString; 00107 size_t realQueryLength = queryLength; 00108 boost::scoped_array<char> buffer; 00109 00110 if(customPlaceholders) 00111 { 00112 buffer.reset(new char[queryLength]); 00113 std::memset(buffer.get(), 0, queryLength); 00114 realQueryString = buffer.get(); 00115 realQueryLength = 0; 00116 00117 char intBuffer[4]; 00118 size_t intBufferSize=0; 00119 std::memset(intBuffer, 0, sizeof(intBuffer)); 00120 bool inPlaceholder = false; 00121 00122 for(const char* it=queryString; it != queryString+queryLength; ++it) 00123 { 00124 if(inPlaceholder && '0' <= *it && *it <= '9' && intBufferSize < sizeof(intBuffer)-1) 00125 { 00126 intBuffer[intBufferSize++] = *it; 00127 continue; 00128 } 00129 00130 if(inPlaceholder) 00131 { 00132 paramOrder.push_back(std::atoi(intBuffer)); 00133 00134 intBufferSize=0; 00135 std::memset(intBuffer, 0, sizeof(intBuffer)); 00136 inPlaceholder = false; 00137 } 00138 00139 if(*it == '?') inPlaceholder = true; 00140 00141 buffer[realQueryLength++] = *it; 00142 } 00143 00144 if(inPlaceholder) paramOrder.push_back(std::atoi(intBuffer)); 00145 } 00146 00147 for(unsigned int i=0; i<connection.threads(); ++i) 00148 { 00149 m_stop[i]=&ConnectionPar<Statement>::s_false; 00150 stmt[i]=mysql_stmt_init(&connection.connection(i)); 00151 if(!stmt) 00152 throw Error(&connection.connection(i)); 00153 00154 if(mysql_stmt_prepare(stmt[i], realQueryString, realQueryLength)) 00155 throw Error(stmt[i]); 00156 00157 if(parameterSet) buildBindings(stmt[i], *parameterSet, paramsConversions[i], paramsBindings[i], paramOrder.size()?¶mOrder:0); 00158 if(resultSet) buildBindings(stmt[i], *resultSet, resultsConversions[i], resultsBindings[i]); 00159 } 00160 00161 m_initialized = true; 00162 } 00163 00164 void ASql::MySQL::Statement::executeParameters(const Data::Set* const& parameters, const unsigned int thread) 00165 { 00166 if(parameters) 00167 { 00168 bindBindings(*const_cast<Data::Set*>(parameters), paramsConversions[thread], paramsBindings[thread], paramOrder.size()?¶mOrder:0); 00169 for(Data::Conversions::iterator it=paramsConversions[thread].begin(); it!=paramsConversions[thread].end(); ++it) 00170 if(!(paramsBindings[thread][it->first].is_null && *paramsBindings[thread][it->first].is_null)) it->second->convertParam(); 00171 if(mysql_stmt_bind_param(stmt[thread], paramsBindings[thread].get())!=0) throw Error(stmt[thread]); 00172 } 00173 00174 if(mysql_stmt_execute(stmt[thread])!=0) throw Error(stmt[thread]); 00175 } 00176 00177 bool ASql::MySQL::Statement::executeResult(Data::Set& row, const unsigned int thread) 00178 { 00179 bindBindings(row, resultsConversions[thread], resultsBindings[thread]); 00180 if(mysql_stmt_bind_result(stmt[thread], resultsBindings[thread].get())!=0) throw Error(stmt[thread]); 00181 switch (mysql_stmt_fetch(stmt[thread])) 00182 { 00183 case 1: 00184 throw Error(stmt[thread]); 00185 case MYSQL_NO_DATA: 00186 return false; 00187 default: 00188 for(Data::Conversions::iterator it=resultsConversions[thread].begin(); it!=resultsConversions[thread].end(); ++it) 00189 if(!(resultsBindings[thread][it->first].is_null && *resultsBindings[thread][it->first].is_null)) it->second->convertResult(); 00190 return true; 00191 }; 00192 } 00193 00194 void ASql::MySQL::Statement::execute(const Data::Set* const parameters, Data::SetContainer* const results, unsigned long long int* const insertId, unsigned long long int* const rows, bool docommit, const unsigned int thread) 00195 { 00196 if(*m_stop[thread]) goto end; 00197 executeParameters(parameters, thread); 00198 00199 if(results) 00200 { 00201 Data::SetContainer& res=*results; 00202 00203 while(1) 00204 {{ 00205 Data::Set& row=res.manufacture(); 00206 bindBindings(row, resultsConversions[thread], resultsBindings[thread]); 00207 if(!executeResult(row, thread)) 00208 { 00209 res.trim(); 00210 break; 00211 } 00212 if(*m_stop[thread]) 00213 { 00214 res.trim(); 00215 goto end; 00216 } 00217 }} 00218 00219 if(*m_stop[thread]) goto end; 00220 if(rows) connection.getFoundRows(rows, thread); 00221 } 00222 else 00223 { 00224 if(*m_stop[thread]) goto end; 00225 if(rows) *rows = mysql_stmt_affected_rows(stmt[thread]); 00226 if(*m_stop[thread]) goto end; 00227 if(insertId) *insertId = mysql_stmt_insert_id(stmt[thread]); 00228 } 00229 00230 end: 00231 if(*m_stop[thread]) 00232 connection.rollback(thread); 00233 else if(docommit) 00234 connection.commit(thread); 00235 mysql_stmt_free_result(stmt[thread]); 00236 mysql_stmt_reset(stmt[thread]); 00237 } 00238 00239 bool ASql::MySQL::Statement::execute(const Data::Set* const parameters, Data::Set& results, bool docommit, const unsigned int thread) 00240 { 00241 bool retval(false); 00242 if(*m_stop[thread]) goto end; 00243 executeParameters(parameters, thread); 00244 if(*m_stop[thread]) goto end; 00245 retval=executeResult(results, thread); 00246 end: 00247 if(*m_stop[thread]) 00248 connection.rollback(thread); 00249 else if(docommit) 00250 connection.commit(thread); 00251 mysql_stmt_free_result(stmt[thread]); 00252 mysql_stmt_reset(stmt[thread]); 00253 return retval; 00254 } 00255 00256 void ASql::MySQL::Statement::execute(const Data::SetContainer& parameters, unsigned long long int* rows, bool docommit, const unsigned int thread) 00257 { 00258 if(rows) *rows = 0; 00259 00260 parameters.init(); 00261 for(const Data::Set* set=parameters.pull(); set!=0; set=parameters.pull()) 00262 { 00263 if(*m_stop[thread]) break; 00264 executeParameters(set, thread); 00265 if(*m_stop[thread]) break; 00266 if(rows) *rows += mysql_stmt_affected_rows(stmt[thread]); 00267 } 00268 if(*m_stop[thread]) 00269 connection.rollback(thread); 00270 else if(docommit) 00271 connection.commit(thread); 00272 mysql_stmt_free_result(stmt[thread]); 00273 mysql_stmt_reset(stmt[thread]); 00274 } 00275 00276 void ASql::MySQL::Statement::buildBindings(MYSQL_STMT* const& stmt, const ASql::Data::Set& set, ASql::Data::Conversions& conversions, boost::scoped_array<MYSQL_BIND>& bindings, const std::deque<unsigned char>* order) 00277 { 00278 using namespace Data; 00279 00280 conversions.clear(); 00281 00282 const int bindSize=order?order->size():set.numberOfSqlElements(); 00283 if(!bindSize) return; 00284 bindings.reset(new MYSQL_BIND[bindSize]); 00285 00286 std::memset(bindings.get(), 0, sizeof(MYSQL_BIND)*bindSize); 00287 00288 for(int i=0; i<bindSize; ++i) 00289 { 00290 const unsigned char index = order?(*order)[i]:i; 00291 Data::Index element = set.getSqlIndex(index); 00292 00293 // Handle NULL 00294 if(element.type>=U_TINY_N) 00295 element.type=Type(element.type-U_TINY_N); // Make it the same type without the nullableness 00296 00297 // Handle unsigned 00298 if(element.type<=U_BIGINT) 00299 { 00300 bindings[i].is_unsigned=1; 00301 element.type=Type(element.type+TINY); 00302 } 00303 00304 // Start decoding values 00305 switch(element.type) 00306 { 00307 case TINY: 00308 { 00309 bindings[i].buffer_type=MYSQL_TYPE_TINY; 00310 break; 00311 } 00312 00313 case SHORT: 00314 { 00315 bindings[i].buffer_type=MYSQL_TYPE_SHORT; 00316 break; 00317 } 00318 00319 case INT: 00320 { 00321 bindings[i].buffer_type=MYSQL_TYPE_LONG; 00322 break; 00323 } 00324 00325 case BIGINT: 00326 { 00327 bindings[i].buffer_type=MYSQL_TYPE_LONGLONG; 00328 break; 00329 } 00330 00331 case FLOAT: 00332 { 00333 bindings[i].buffer_type=MYSQL_TYPE_FLOAT; 00334 break; 00335 } 00336 00337 case DOUBLE: 00338 { 00339 bindings[i].buffer_type=MYSQL_TYPE_DOUBLE; 00340 break; 00341 } 00342 00343 case DATE: 00344 { 00345 TypedConversion<Date>* conv = new TypedConversion<Date>; 00346 bindings[i].buffer = &conv->internal; 00347 bindings[i].buffer_type = MYSQL_TYPE_DATE; 00348 conversions[i].reset(conv); 00349 break; 00350 } 00351 00352 case DATETIME: 00353 { 00354 TypedConversion<Datetime>* conv = new TypedConversion<Datetime>; 00355 bindings[i].buffer = &conv->internal; 00356 bindings[i].buffer_type = MYSQL_TYPE_DATETIME; 00357 conversions[i].reset(conv); 00358 break; 00359 } 00360 00361 case TIME: 00362 { 00363 TypedConversion<Time>* conv = new TypedConversion<Time>; 00364 bindings[i].buffer = &conv->internal; 00365 bindings[i].buffer_type = MYSQL_TYPE_TIME; 00366 conversions[i].reset(conv); 00367 break; 00368 } 00369 00370 case BLOB: 00371 { 00372 TypedConversion<Blob>* conv = new TypedConversion<Blob>(i, stmt, MYSQL_TYPE_BLOB, bindings[i].buffer); 00373 bindings[i].length = &conv->length; 00374 bindings[i].buffer_type = conv->bufferType; 00375 conversions[i].reset(conv); 00376 break; 00377 } 00378 00379 case TEXT: 00380 { 00381 TypedConversion<Text>* conv = new TypedConversion<Text>(i, stmt, MYSQL_TYPE_STRING, bindings[i].buffer); 00382 bindings[i].length = &conv->length; 00383 bindings[i].buffer_type = conv->bufferType; 00384 conversions[i].reset(conv); 00385 break; 00386 } 00387 00388 case WTEXT: 00389 { 00390 TypedConversion<Wtext>* conv = new TypedConversion<Wtext>(i, stmt, bindings[i].buffer); 00391 00392 bindings[i].length = &conv->length; 00393 bindings[i].buffer_type = conv->bufferType; 00394 conversions[i].reset(conv); 00395 break; 00396 } 00397 00398 case CHAR: 00399 case BINARY: 00400 { 00401 bindings[i].buffer_length = element.size; 00402 bindings[i].buffer_type = element.type==CHAR?MYSQL_TYPE_STRING:MYSQL_TYPE_BLOB; 00403 break; 00404 } 00405 00406 default: 00407 { 00408 // Invalid element type, this shouldn't happen 00409 break; 00410 } 00411 } 00412 } 00413 } 00414 00415 void ASql::MySQL::Statement::bindBindings(Data::Set& set, Data::Conversions& conversions, boost::scoped_array<MYSQL_BIND>& bindings, const std::deque<unsigned char>* order) 00416 { 00417 const int bindSize=order?order->size():set.numberOfSqlElements(); 00418 for(int i=0; i<bindSize; ++i) 00419 { 00420 const unsigned char index = order?(*order)[i]:i; 00421 Data::Index element = set.getSqlIndex(index); 00422 00423 if(element.type >= Data::U_TINY_N) 00424 { 00425 bindings[i].is_null = (my_bool*)&((Data::NullablePar*)element.data)->nullness; 00426 element.data = ((Data::NullablePar*)element.data)->getVoid(); 00427 } 00428 00429 Data::Conversions::iterator it=conversions.find(i); 00430 if(it==conversions.end()) 00431 bindings[i].buffer=element.data; 00432 else 00433 { 00434 it->second->external=element.data; 00435 bindings[i].buffer=it->second->getPointer(); 00436 } 00437 } 00438 } 00439 00440 void ASql::MySQL::TypedConversion<ASql::Data::Datetime>::convertResult() 00441 { 00442 try 00443 { 00444 *(boost::posix_time::ptime*)external=boost::posix_time::ptime(boost::gregorian::date(internal.year, internal.month, internal.day), boost::posix_time::time_duration(internal.hour, internal.minute, internal.second)); 00445 } 00446 catch(...) 00447 { 00448 *(boost::posix_time::ptime*)external==boost::posix_time::not_a_date_time; 00449 } 00450 } 00451 00452 void ASql::MySQL::TypedConversion<ASql::Data::Datetime>::convertParam() 00453 { 00454 std::memset(&internal, 0, sizeof(MYSQL_TIME)); 00455 internal.year = ((boost::posix_time::ptime*)external)->date().year(); 00456 internal.month = ((boost::posix_time::ptime*)external)->date().month(); 00457 internal.day = ((boost::posix_time::ptime*)external)->date().day(); 00458 internal.hour = ((boost::posix_time::ptime*)external)->time_of_day().hours(); 00459 internal.minute = ((boost::posix_time::ptime*)external)->time_of_day().minutes(); 00460 internal.second = ((boost::posix_time::ptime*)external)->time_of_day().seconds(); 00461 } 00462 00463 void ASql::MySQL::TypedConversion<ASql::Data::Date>::convertResult() 00464 { 00465 try 00466 { 00467 *(boost::gregorian::date*)external=boost::gregorian::date(internal.year, internal.month, internal.day); 00468 } 00469 catch(...) 00470 { 00471 *(boost::gregorian::date*)external==boost::gregorian::date(boost::gregorian::not_a_date_time); 00472 } 00473 } 00474 00475 void ASql::MySQL::TypedConversion<ASql::Data::Date>::convertParam() 00476 { 00477 std::memset(&internal, 0, sizeof(MYSQL_TIME)); 00478 internal.year = ((boost::gregorian::date*)external)->year(); 00479 internal.month = ((boost::gregorian::date*)external)->month(); 00480 internal.day = ((boost::gregorian::date*)external)->day(); 00481 } 00482 00483 void ASql::MySQL::TypedConversion<ASql::Data::Time>::convertResult() 00484 { 00485 *(boost::posix_time::time_duration*)external = boost::posix_time::time_duration(internal.neg?internal.hour*-1:internal.hour, internal.minute, internal.second); 00486 } 00487 00488 void ASql::MySQL::TypedConversion<ASql::Data::Time>::convertParam() 00489 { 00490 std::memset(&internal, 0, sizeof(MYSQL_TIME)); 00491 internal.hour = std::abs(((boost::posix_time::time_duration*)external)->hours()); 00492 internal.minute = std::abs(((boost::posix_time::time_duration*)external)->minutes()); 00493 internal.second = std::abs(((boost::posix_time::time_duration*)external)->seconds()); 00494 internal.neg = ((boost::posix_time::time_duration*)external)->hours() < 0 ? 1:0; 00495 } 00496 00497 template void ASql::MySQL::TypedConversion<ASql::Data::Blob>::grabIt(ASql::Data::Blob& data); 00498 template void ASql::MySQL::TypedConversion<ASql::Data::Text>::grabIt(ASql::Data::Text& data); 00499 template<class T> void ASql::MySQL::TypedConversion<T>::grabIt(T& data) 00500 { 00501 if(data.size() != length) data.resize(length); 00502 00503 if(length) 00504 { 00505 MYSQL_BIND bind; 00506 std::memset(&bind, 0, sizeof(bind)); 00507 bind.buffer=&data[0]; 00508 bind.buffer_length=length; 00509 bind.length=&length; 00510 bind.buffer_type=bufferType; 00511 if(mysql_stmt_fetch_column(statement, &bind, column, 0)!=0) throw Error(statement); 00512 } 00513 } 00514 00515 template void ASql::MySQL::TypedConversion<ASql::Data::Blob>::convertParam(); 00516 template void ASql::MySQL::TypedConversion<ASql::Data::Text>::convertParam(); 00517 template<class T> void ASql::MySQL::TypedConversion<T>::convertParam() 00518 { 00519 T& data = *(T*)external; 00520 00521 length = data.size(); 00522 if(length) buffer = &data[0]; 00523 else buffer=0; 00524 } 00525 00526 void ASql::MySQL::TypedConversion<ASql::Data::Wtext>::convertResult() 00527 { 00528 std::vector<char>& conversionBuffer = inputBuffer; 00529 Data::VectorBlob blob(inputBuffer); 00530 grabIt(blob); 00531 00532 std::wstring& output = *(std::wstring*)external; 00533 output.resize(conversionBuffer.size()); 00534 00535 if(conversionBuffer.size()) 00536 { 00537 wchar_t* it; 00538 const char* tmp; 00539 mbstate_t conversionState = mbstate_t(); 00540 if(std::use_facet<std::codecvt<wchar_t, char, mbstate_t> >(std::locale(std::locale::classic(), new utf8CodeCvt::utf8_codecvt_facet)).in(conversionState, (const char*)&conversionBuffer.front(), (const char*)&conversionBuffer.front() + conversionBuffer.size(), tmp, &output[0], &output[0] + output.size(), it)!=std::codecvt_base::ok) 00541 throw ASql::Error(CodeConversionErrorMsg, -1); 00542 output.resize(it-&output[0]); 00543 conversionBuffer.clear(); 00544 } 00545 } 00546 00547 void ASql::MySQL::TypedConversion<ASql::Data::Wtext>::convertParam() 00548 { 00549 using namespace std; 00550 00551 wstring& data = *(wstring*)external; 00552 00553 inputBuffer.resize(data.size()*sizeof(wchar_t)); 00554 00555 if(inputBuffer.size()) 00556 { 00557 const wchar_t* tmp; 00558 char* it; 00559 mbstate_t conversionState = mbstate_t(); 00560 if(use_facet<codecvt<wchar_t, char, mbstate_t> >(locale(locale::classic(), new utf8CodeCvt::utf8_codecvt_facet)).out(conversionState, (const wchar_t*)&data[0], (const wchar_t*)&data[0] + data.size(), tmp, &inputBuffer.front(), &inputBuffer.front() + inputBuffer.size(), it)!=codecvt_base::ok) throw ASql::Error(CodeConversionErrorMsg, -1); 00561 inputBuffer.resize(it-&inputBuffer[0]); 00562 } 00563 00564 buffer=&inputBuffer.front(); 00565 length = inputBuffer.size(); 00566 } 00567 00568 00569 //Instance the ConnectionPar functions 00570 template void ASql::ConnectionPar<ASql::MySQL::Statement>::start(); 00571 template void ASql::ConnectionPar<ASql::MySQL::Statement>::terminate(); 00572 template void ASql::ConnectionPar<ASql::MySQL::Statement>::intHandler(unsigned int id); 00573 template void ASql::ConnectionPar<ASql::MySQL::Statement>::queue(ASql::MySQL::Statement* const& statement, QueryPar& query, int instance); 00574 template void ASql::ConnectionPar<ASql::MySQL::Statement>::queue(Transaction<ASql::MySQL::Statement>& transaction, int instance); 00575 template void ASql::Transaction<ASql::MySQL::Statement>::cancel(); 00576 00577 00578 ASql::MySQL::Error::Error(MYSQL* mysql): ASql::Error(mysql_error(mysql), mysql_errno(mysql)) { } 00579 ASql::MySQL::Error::Error(MYSQL_STMT* stmt): ASql::Error(mysql_stmt_error(stmt), mysql_stmt_errno(stmt)) { } 00580 00581 const char ASql::MySQL::CodeConversionErrorMsg[]="Error in code conversion to/from MySQL server."; 00582 00583 ASql::MySQL::Statement::~Statement() 00584 { 00585 if(m_initialized) 00586 { 00587 for(unsigned int i=0; i<connection.threads(); ++i) 00588 mysql_stmt_close(stmt[i]); 00589 } 00590 }