/** @class gatsDBpp::GATS_DB_mysql @brief Employs the GATS_DB_mysql class methods. @date $Date$ @version $Rev$ @author - Lance Deaver @copyright (©) Copyright 2006 by GATS Inc. 11864 Canon Blvd., Suite 101, Newport News, VA 23606 All Rights Reserved. No part of this software or publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise without the prior written permission of GATS Inc. @see GATS_DB_mysql.h @see GATS_DB_Exception.h @bug None known @warning The assignment operator and copy constructor are protected!! $Id$ */ #include "GATS_DB_mysql.h" #include "GATS_DB_Exception.h" #include using namespace gatsDBpp; /** * Connects to the MySQL database via the \a mysql_real_connect C API. See * documentation on this function for further details of these parameters. * * @param[in] user MySQL database username. * @param[in] passwd MySQL database user password. * @param[in] database The database to initally connect into. * @param[in] host The host name or IP address. * @param[in] port The MySQL server port number. * @param[in] unix_socket Specifies the socket or named pipe that should be used * @param[in] client_flag Additional options for the database connection. See \a mysql_real_connect * documentation for further details. * * @exception DBConnectionFailed The connection to the MySQL database was not successful. */ GATS_DB_mysql::GATS_DB_mysql(const char* user, const char* passwd, const char* database, const char* host, const unsigned int port, const char* unix_socket, unsigned long int client_flag ) { if( ! (_connection = ::mysql_init(0) ) ) { THROW_GATS_EXCEPTION( DBConnectionFailed, ::mysql_error(_connection) ) ; } if( ! ::mysql_real_connect(_connection,host, user, passwd, database ,port , unix_socket , client_flag) ) { THROW_GATS_EXCEPTION( DBConnectionFailed, ::mysql_error(_connection) ) ; } } /** * Disconnects from the MySQL database using the C API \a mysql_close. * */ GATS_DB_mysql::~GATS_DB_mysql() { ::mysql_close(_connection); } /** * Provides a C++ wrapper to the \a mysql_select_db C API. * * @param[in] db_name C type string containing the database name to connect into * * @exception BadQuery Requested database does not exist or other error occurred. */ void GATS_DB_mysql::SelectDB(const char *db_name) { if( ::mysql_select_db(_connection,db_name) ) { THROW_GATS_EXCEPTION( BadQuery, ::mysql_error(_connection) ) ; } } /** * Provides a C++ wrapper to the \a mysql_query or \a mysql_real_query C functions. * If provided a non-zero length parameter this method executes \a mysql_real_query * if length is kept its default 0 this method will execute \a mysql_query. You * will need to provide a non-zero byte length if your sqlstring contains any binary data * that could be interpreted as string terminating NULL. * * @retval GAT_DB_Results A vector > containing the rows and field values from * the query statement. There is a GATS_DB_Results container available for every Query however some SQL statements * such as INSERT, UPDATE, CREATE, and DELETE will return empty containers. A SELECT will * return an empty container if it did not find any matching rows. * * @param[in] sqlstring A char* containing the verbatim SQL statement to send to the * database. * @param[in] length The byte length of the SQL string. You need to provide this if your * SQL statement contains any binary data that could be interpreted as a string terminating * NULL. * * @exception BadQuery An Error occured while executing this SQL statement. * */ GATS_DB_Results GATS_DB_mysql::Query(const char* sqlstring, const unsigned long int length) { std::vector< std::vector > x; if (length == 0) { if( ::mysql_query(_connection, sqlstring ) != 0) { THROW_GATS_EXCEPTION( BadQuery, ::mysql_error(_connection) ) ; } } else { if( ::mysql_real_query(_connection, sqlstring, length ) != 0) { THROW_GATS_EXCEPTION( BadQuery, ::mysql_error(_connection) ) ; } } MYSQL_RES* result= ::mysql_use_result(_connection) ; const unsigned int num_fields = ::mysql_field_count(_connection); if( ! result ) { if( num_fields != 0 ) { THROW_GATS_EXCEPTION( BadQuery, ::mysql_error(_connection) ) ; } } else if(num_fields > 0) { MYSQL_ROW row; while( (row = ::mysql_fetch_row(result)) ) { unsigned long *field_lengths = ::mysql_fetch_lengths(result); std::vector Cols; for(unsigned int iField=0; iField < num_fields; ++iField) { Cols.push_back(ColData(row[iField], field_lengths[iField] ) ); } x.push_back(Cols); } } ::mysql_free_result(result); return x; } /** * Retrieve a listing of table names from the currently connected database that match the wildcard. This * is similar to performing the following command SHOW TABLES [LIKE wild]; * This method is a C++ wrapper to the \a mysql_list_tables C API. * * @param[in] wild A char* containing a regular expression what may contain the '\%' or '_' wildcard characters. * If NULL the result will be all the tables in the current database. * * @retval vector A vector of strings that contain the matching tablenames. * * @exception BadQuery A server error occurred. */ std::vector GATS_DB_mysql::TableNames(const char *wild) { std::vector tablenames; MYSQL_RES* result= ::mysql_list_tables(_connection, wild) ; const unsigned int num_fields = ::mysql_field_count(_connection); if( ! result ) { if( num_fields != 0 ) { THROW_GATS_EXCEPTION( BadQuery, ::mysql_error(_connection) ) ; } } else if(num_fields > 0) { MYSQL_ROW row; while( (row = ::mysql_fetch_row(result)) ) { tablenames.push_back(std::string(row[0]) ); } } ::mysql_free_result(result); return tablenames; } /** * Retrieve a listing of database names that matching the wildcard. This * is similar to performing the following command SHOW DATABASES [LIKE wild]; * This method is a C++ wrapper to the \a mysql_list_dbs C API. * * @param[in] wild A char* containing a regular expression what may contain the '\%' or '_' wildcard characters. * If NULL the result will be all the database names on the server. * * @retval vector A vector of strings that contain the matching database names. * * @exception BadQuery A server error occurred. */ std::vector GATS_DB_mysql::DBNames(const char *wild) { std::vector dbnames; MYSQL_RES* result= ::mysql_list_dbs(_connection, wild) ; const unsigned int num_fields = ::mysql_field_count(_connection); if( ! result ) { if( num_fields != 0 ) { THROW_GATS_EXCEPTION( BadQuery, ::mysql_error(_connection) ) ; } } else if(num_fields > 0) { MYSQL_ROW row; while( (row = ::mysql_fetch_row(result)) ) { dbnames.push_back(std::string(row[0]) ); } } ::mysql_free_result(result); return dbnames; } /** * Retrieve a listing of field names in the provided table that match the wildcard. This * is similar to performing the following command SHOW COLUMNS FROM tbl_name [LIKE wild]; * This method is a C++ wrapper to the \a mysql_list_fields C API. * * @param[in] table A char* containing the table name for which you want it field names * @param[in] wild A char* containing a regular expression what may contain the '\%' or '_' wildcard characters. * If NULL the result will be all the field names for the provided table name. * * @retval vector A vector of strings that contain the matching field names. * * @exception BadQuery A server error occurred. */ std::vector GATS_DB_mysql::FieldNames(const char* table, const char *wild) { std::vector fieldnames; MYSQL_RES* result= ::mysql_list_fields(_connection, table, wild) ; if( ! result ) { THROW_GATS_EXCEPTION( BadQuery, ::mysql_error(_connection) ) ; } MYSQL_FIELD* field; while( (field = ::mysql_fetch_field(result)) ) { fieldnames.push_back(std::string(field->name) ); } ::mysql_free_result(result); return fieldnames; } /** * This method will assemble an INSERT statement and send the results to the MySQL server. * The user provides a table name and a std::vector containing a std::pair. The first item in the * pair is the fieldname, the second item is a ColData that contains the data you which to place in that * field. If the ColData object notifies this method if the data needs quoted and escaped and this * method will perform that task as it assembles the SQL statement. The insert statement will * have the following form. * @n * @n * INSERT INTO table ( field1, field2, field3) VALUES (value1, value2, value3) * @n * @n * Since INSERT does not return a result set, the return type of this method is void. * * @param[in] table The table name to INSERT data. * @param[in] fields A vector of string and colData pairs that contain the field name and the data to insert into * the field in the form of a ColData object. * * @exception BadQuery A server error occurred. */ void GATS_DB_mysql::InsertIntoTable(const char* table, const std::vector >& fields ) { std::string sql("INSERT INTO "); sql += std::string(table); sql += std::string(" ("); std::vector< std::pair< unsigned long int, boost::shared_array > > values; if( ! fields.empty() ) { std::vector >::const_iterator fieldIter = fields.begin(); for( ; fieldIter != fields.end() ; ++fieldIter) { sql += (*fieldIter).first; if( fieldIter < fields.end() -1) { sql += std::string(","); } ColData C = (*fieldIter).second; if( C.isNull() ) { boost::shared_array t(new char[5]); strcpy(t.get(), "NULL"); values.push_back(std::pair >(strlen(t.get()),t) ); } else if( C.isEmpty() ) { boost::shared_array t(new char[3]); strcpy(t.get(), "''"); values.push_back(std::pair >(strlen(t.get()),t) ); } else if( C.needsQuote() ) { const unsigned long int dlen = C.Length(); boost::shared_array tempval(new char[2*dlen + 1]); unsigned long int newlen = 2 + // room for quotations surrounding the C string ::mysql_real_escape_string(_connection, tempval.get(), C.asC_str(), dlen); boost::shared_array t(new char[newlen]); t[0] = '\''; memcpy(t.get()+1, tempval.get() , newlen-2 ); t[newlen-1] = '\''; values.push_back(std::pair >(newlen,t) ); } else { const unsigned long int dlen = C.Length(); boost::shared_array t(new char[dlen]); memcpy(t.get(), C.asC_str() , dlen ); values.push_back(std::pair >(dlen,t) ); } } //for } sql += std::string(") VALUES ("); unsigned long int length = sql.size() + 1; //leave room for closing parenthesis boost::shared_array SQLstatement; if(! values.empty() ) { length += (values.size() -1); // for commas delimiting the values for(unsigned long i=0; i< values.size(); ++i) { //determine how much array to allocate length += values[i].first; } SQLstatement.reset( new char[length+1] ); //leave room for terminating NULL strcpy(SQLstatement.get()+length-1, ")" ); char* end = SQLstatement.get(); memcpy(end, sql.c_str() , sql.size() ); end += sql.size(); for(unsigned long int i=0; i< values.size(); ++i) { memcpy(end, (values[i].second).get(), values[i].first); end += values[i].first; if(i < values.size() -1) { *end = ','; ++end; } } } else { sql += std::string(")"); SQLstatement.reset(new char[sql.size()+1] ); //leave room for terminating NULL strcpy(SQLstatement.get(), sql.c_str() ); } // std::cout << SQLstatement.get() << std::endl; Query(SQLstatement.get(), length); } /** * This method will assemble an UPDATE statement and send the results to the MySQL server. * The user provides a table name and a std::vector containing a std::pair. The first item in the * pair is the fieldname, the second item is a ColData that contains the data you which to place in that * field. If the ColData object notifies this method if the data needs quoted and escaped and this * method will perform that task as it assembles the SQL statement. The where clause is appended on the * end of the sql statement. If the where clause was set to "WHERE field1 = 32' then the final sql statement * generated by this method would have the form. * @n * @n * UPDATE table SET field1=value1, field2=value2, field3=value3 WHERE field1 = 32 * @n * @n * Since UPDATE does not return a result set, the return type of this method is void. * * @param[in] table The table name to UPDATE. * @param[in] fields A vector of string and colData pairs that contain the field name and the data to insert into * the field in the form of a ColData object. * @param[in] whereclause A C string containing the portion of the SQL statement beginning with "WHERE blah ". This string * is appended to the end of the UPDATE sql statement in an unaltered form. * @param[in] wherelen This is the byte length of the where string. If non-zero the method will use the first wherelen * bytes of the where string you would need to provide this length of your where string contained binary data which * perhaps contained NULL characters in the middle of the string. If kept the default or set to zero the method will * append up to the where terminating NULL. * * * @exception BadQuery A server error occurred. */ void GATS_DB_mysql::UpdateTable(const char* table, const std::vector >& fields, const char* whereclause, const unsigned long wherelen) { if(fields.empty() ) { THROW_GATS_EXCEPTION( BadQuery, "UPDATE statement must contain at least one col_name = expression" ) ; } std::string sql("UPDATE "); sql += std::string(table); sql += std::string(" SET "); unsigned long int length = sql.size() ; std::vector< std::pair< unsigned long int, boost::shared_array > > values; std::vector >::const_iterator fieldIter = fields.begin(); for( ; fieldIter != fields.end() ; ++fieldIter) { length += ((*fieldIter).first).size()+1; // Leave room for equal sign if( fieldIter < fields.end() -1) { ++length; // for the comma } ColData C = (*fieldIter).second; if( C.isNull() ) { boost::shared_array t(new char[5]); strcpy(t.get(), "NULL"); values.push_back(std::pair >(strlen(t.get()),t) ); } else if( C.isEmpty() ) { boost::shared_array t(new char[3]); strcpy(t.get(), "''"); values.push_back(std::pair >(strlen(t.get()),t) ); } else if( C.needsQuote() ) { const unsigned long int dlen = C.Length(); boost::shared_array tempval(new char[2*dlen + 1]); unsigned long int newlen = 2 + // room for quotations surrounding the C string ::mysql_real_escape_string(_connection, tempval.get(), C.asC_str(), dlen); boost::shared_array t(new char[newlen]); t[0] = '\''; memcpy(t.get()+1, tempval.get() , newlen-2 ); t[newlen-1] = '\''; values.push_back(std::pair >(newlen,t) ); } else { const unsigned long int dlen = C.Length(); boost::shared_array t(new char[dlen]); memcpy(t.get(), C.asC_str() , dlen ); values.push_back(std::pair >(dlen,t) ); } length += (values.back()).first; } //for if(whereclause) { if(wherelen == 0) { length += strlen(whereclause); } else { length += wherelen; } ++length; //include a space before whereclause } boost::shared_array SQLstatement(new char[length+1]); //include a terminating NULL char* end = SQLstatement.get() ; memcpy(end, sql.c_str() , sql.size() ); end += sql.size(); for(unsigned long int i=0; i< values.size(); ++i) { memcpy(end, (fields[i].first).c_str(), (fields[i].first).size() ); end += (fields[i].first).size(); *end ='='; ++end; memcpy(end, (values[i].second).get(), values[i].first); end += values[i].first; if(i < values.size() -1) { *end = ','; ++end; } } if(whereclause) { *end = ' '; ++end; unsigned long l = (wherelen==0) ? strlen(whereclause) : wherelen; memcpy(end, whereclause, l); end += l; } *end = '\0'; //finally a terminating null; Query(SQLstatement.get(), length); } /** * This method returns the number of rows that were affected by the last sql statement sent to the server. * If the statement had been a DELETE, UPDATE, INSERT, REPLACE the results from this method would be * the number of rows that were modified. * @warning This method may not return the expect value following a SELECT statement. Intuitively this routine * should return the number of rows that were selected from a SELECT statement but in reality this routine * probably will not so you should alway check the size of the GATS_DB_Results containter for the number of rows returned. * This method provides a wrapper around the \a mysql_affected_rows C API. */ unsigned long long int GATS_DB_mysql::AffectedRows() { return static_cast(::mysql_affected_rows(_connection) ); }