fastcgi++
mysql.cpp
Go to the documentation of this file.
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()?&paramOrder: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()?&paramOrder: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 }