/* Copyright (C) 2003-2013 Runtime Revolution Ltd.
This file is part of LiveCode.
LiveCode is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License v3 as published by the Free
Software Foundation.
LiveCode is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with LiveCode. If not see . */
#include "dbmysql.h"
static bool s_ssl_loaded = false;
#if !defined(_SERVER)
extern "C" int initialise_weak_link_crypto(void);
extern "C" int initialise_weak_link_ssl(void);
bool load_ssl_library()
{
if (s_ssl_loaded)
return true;
s_ssl_loaded = initialise_weak_link_crypto() && initialise_weak_link_ssl();
return s_ssl_loaded;
}
#elif defined(_SERVER)
bool load_ssl_library()
{
return true;
}
#endif
#ifdef TARGET_SUBPLATFORM_IPHONE
#if defined(__i386__) || defined(__x86_64__)
#include
extern "C" void *IOS_LoadModule(const char *mod)
{
return dlopen(mod, RTLD_NOW);
}
extern "C" void *IOS_ResolveSymbol(void *mod, const char *sym)
{
return dlsym(mod, sym);
}
#else
extern "C" void *load_module(const char *);
extern "C" void *resolve_symbol(void *, const char *);
extern "C" void *IOS_LoadModule(const char *mod)
{
return load_module(mod);
}
extern "C" void *IOS_ResolveSymbol(void *mod, const char *sym)
{
return resolve_symbol(mod, sym);
}
#endif
#endif
#if defined(_WINDOWS)
#define strcasecmp stricmp
#endif
Bool DBConnection_MYSQL::connect(char **args, int numargs)
{
if (isConnected)
return True;
if (numargs < 4)
return False;
char *t_dbname;
t_dbname = args[1];
char *t_user;
t_user = args[2];
char *t_password;
t_password = args[3];
char *t_use_ssl_string;
t_use_ssl_string = NULL;
if (numargs >= 5)
t_use_ssl_string = args[4];
// Parse args[0] to obtain the host and port.
char *t_delimiter;
t_delimiter = strchr(args[0], ':');
char *t_host;
t_host = args[0];
char *t_port_string;
t_port_string = NULL;
if (t_delimiter != NULL)
{
t_port_string = (t_delimiter + (1 * sizeof(char)));
*t_delimiter = NULL;
}
int t_port;
t_port = 0;
if (t_port_string != NULL)
t_port = atoi(t_port_string);
int t_use_ssl;
t_use_ssl = 0;
if (t_use_ssl_string != NULL && (strlen(t_use_ssl_string) != 0))
if (strcasecmp(t_use_ssl_string, "true") == 0)
t_use_ssl = 1;
else
t_use_ssl = atoi(t_use_ssl_string);
else
t_use_ssl = 0;
if (t_use_ssl && !load_ssl_library())
{
errorMessageSet("Unable to load SSL library");
return false;
}
//initialize mysql data structure for connection
if (!mysql_init(getMySQL()))
return False;
// MM-2011-09-09: [[ BZ 9712]] Allow the user to specify the socket or named pipe to connect with
char *t_socket;
if (numargs >= 6 && strlen(args[5]) != 0)
t_socket = args[5];
else
t_socket = NULL;
// Set timeout value for TCP/IP connections to 20 seconds.
// According to API docs, total effective timeout is 3 times this due to retries,
// but testing on windows shows connections timing out after 20 seconds
// MM-2011-09-09: Updated to allow the user to specify the read/write timeout
int t_read_write_timeout;
if(numargs < 7 || args[6] == NULL || strlen(args[6]) == 0 || sscanf(args[6], "%d", &t_read_write_timeout) == 0 || t_read_write_timeout < 0)
t_read_write_timeout = 20;
if (mysql_options(getMySQL(), MYSQL_OPT_READ_TIMEOUT, &t_read_write_timeout) ||
mysql_options(getMySQL(), MYSQL_OPT_WRITE_TIMEOUT, &t_read_write_timeout))
return false;
// MM-2011-09-09: Allow the user to specify the auto-reconnect option. If connection to the server has been lost,
// the driver will automatically try to reconnect. To prevent the reconnect hanging, set the reconnect timeout also.
bool t_auto_reconnect;
t_auto_reconnect = (numargs >= 8 && args[7] != NULL && strlen(args[7]) != 0 && strcasecmp(args[7], "true") == 0);
if (t_auto_reconnect)
if (mysql_options(getMySQL(), MYSQL_OPT_RECONNECT, &t_auto_reconnect) ||
mysql_options(getMySQL(), MYSQL_OPT_CONNECT_TIMEOUT, &t_read_write_timeout))
return false;
//connect to mysql database.
if (!mysql_real_connect(getMySQL(), t_host, t_user, t_password, t_dbname, t_port, t_socket, t_use_ssl > 0 ? CLIENT_SSL : 0))
return False;
connectionType = CT_MYSQL,
isConnected = True;
return True;
}
const char *DBConnection_MYSQL::getconnectionstring()
{
return DB_MYSQL_STRING;
}
/*Method to disconnect from MySQL server*/
void DBConnection_MYSQL::disconnect()
{
//if not connected then exit early
if (!isConnected)
return;
//close all open cursors from this connection
closeCursors();
//close mysql connection
mysql_close(getMySQL());
isConnected = False;
}
/*Method to execute quick and fast sql statements like UPDATE and INSERT
Inputs:
query- string containing sql query
qlength - length of query (for binary data). if 0 then assume null terminated.
affectedrows - will recieve number of rows updated or inserted
Output: False on error
*/
Bool DBConnection_MYSQL::sqlExecute(char *p_query, DBString *p_arguments, int p_argument_count, unsigned int &p_affected_rows)
{
int t_affected_rows;
t_affected_rows = 0;
Bool t_result;
t_result = False;
if (!ExecuteQuery(p_query, p_arguments, p_argument_count) || mysql_errno(getMySQL()))
{
// OK-2007-09-10 : Bug 5360
errorMessageSet(mysql_error(getMySQL()));
return t_result;
}
// First we need to establish if the query returned a result set. This will be the case if it was
// a select query. If there is a result set, we need to store it temporarily and free it to avoid
// errors ocurring in subsequent queries.
unsigned int t_field_count;
t_field_count = mysql_field_count(getMySQL());
MYSQL_RES *t_result_set;
t_result_set = NULL;
if (t_field_count != 0)
{
t_result_set = mysql_store_result(getMySQL());
if (t_result_set == NULL)
return False;
t_affected_rows = 0;
}
else
t_affected_rows = (int)mysql_affected_rows(&mysql);
if (t_affected_rows != -1)
t_result = True;
if (t_result_set != NULL)
{
mysql_free_result(t_result_set);
}
p_affected_rows = t_affected_rows;
// OK-2007-09-10 : Bug 5360
if (t_result)
errorMessageSet(NULL);
else
errorMessageSet(mysql_error(getMySQL()));
return t_result;
}
/*Method to execute sql statements like SELECT and return Cursor
Inputs:
query- string containing sql query
qlength - length of query (for binary data). if 0 then assume null terminated.
Output: NULL cursor on error
*/
DBCursor *DBConnection_MYSQL::sqlQuery(char *p_query, DBString *p_arguments, int p_argument_count, int p_rows)
{
DBCursor_MYSQL *t_cursor;
t_cursor = NULL;
if (ExecuteQuery(p_query, p_arguments, p_argument_count))
{
t_cursor = new DBCursor_MYSQL();
if (!t_cursor -> open((DBConnection *)this))
{
delete t_cursor;
t_cursor = NULL;
}
else
addCursor(t_cursor);
}
// OK-2007-09-10 : Bug 5360
if (t_cursor != NULL)
errorMessageSet(NULL);
else
errorMessageSet(mysql_error(getMySQL()));
return (DBCursor *)t_cursor;
}
bool queryCallback(void *p_context, int p_placeholder, DBBuffer &p_output)
{
QueryMetadata *t_query_metadata;
t_query_metadata = (QueryMetadata *)p_context;
if (p_placeholder > t_query_metadata -> argument_count)
{
// This means that the user has specified more placeholders in their query than they have provided variable names
// to bind with, an error is the result
return false;
}
DBString t_parameter_value;
t_parameter_value = t_query_metadata -> arguments[p_placeholder - 1];
char *t_escaped_string;
t_escaped_string = (char *)malloc((t_parameter_value . length * 2) + 1);
if (t_escaped_string == NULL)
return false;
unsigned long t_escaped_string_length;
t_escaped_string_length = mysql_real_escape_string((MYSQL *)t_query_metadata -> connection, t_escaped_string, t_parameter_value . sptr, t_parameter_value . length);
p_output . ensure(t_escaped_string_length + 2);
memcpy(p_output . getFrontier(), "\"", 1);
p_output . advance(1);
memcpy(p_output . getFrontier(), t_escaped_string, t_escaped_string_length);
p_output . advance(t_escaped_string_length);
memcpy(p_output . getFrontier(), "\"", 1);
p_output . advance(1);
free(t_escaped_string);
return true;
}
bool DBConnection_MYSQL::ExecuteQuery(char *p_query, DBString *p_arguments, int p_argument_count)
{
if (!isConnected)
return false;
unsigned int t_query_length;
t_query_length = strlen(p_query);
char *t_parsed_query;
t_parsed_query = p_query;
int *t_placeholders;
t_placeholders = NULL;
int t_placeholder_count;
t_placeholder_count = 0;
bool t_success;
t_success = true;
if (p_argument_count != 0)
{
QueryMetadata t_query_metadata;
t_query_metadata . argument_count = p_argument_count;
t_query_metadata . arguments = p_arguments;
t_query_metadata . connection = getMySQL();
DBBuffer t_query_buffer(t_query_length + 1);
t_success = processQuery(p_query, t_query_buffer, queryCallback, &t_query_metadata);
t_query_length = t_query_buffer . getSize();
t_parsed_query = t_query_buffer . grab();
}
int t_error_code;
if (t_success)
{
if (t_error_code = mysql_real_query(getMySQL(), t_parsed_query, t_query_length))
t_success = false;
}
if (p_argument_count != 0)
free(t_parsed_query);
return t_success;
}
/*IsError-True on error*/
Bool DBConnection_MYSQL::IsError()
{
if (m_error != NULL || mysql_errno(getMySQL()))
return True;
else
return False;
}
/*getErrorMessage- return error string*/
char *DBConnection_MYSQL::getErrorMessage(Bool p_last)
{
// AL-2013-11-08 [[ Bug 11149 ]] Make sure most recent error string is available to revDatabaseConnectResult
if (p_last || IsError() == True)
{
if (m_error != NULL)
return m_error;
else
return (char *)mysql_error(getMySQL());
}
else
return (char *)DBNullValue;
}
bool DBConnection_MYSQL::BindVariables(MYSQL_STMT *p_statement, DBString *p_arguments, int p_argument_count, int *p_placeholders, int p_placeholders_count, MYSQL_BIND **p_bind)
{
if (p_placeholders_count == 0)
return true;
MYSQL_BIND *t_bind;
t_bind = *p_bind;
for (int i = 0; i < p_placeholders_count; i++)
{
if (p_placeholders[i] < 1 || p_placeholders[i] > p_argument_count)
return false;
DBString *t_parameter_value;
t_parameter_value = &(p_arguments[p_placeholders[i] - 1]);
t_bind[i] . buffer = (void *)t_parameter_value -> sptr;
t_bind[i] . buffer_length = t_parameter_value -> length;
t_bind[i] . length = (unsigned long *)&t_parameter_value -> length;
if (t_parameter_value -> isbinary)
t_bind[i] . buffer_type = MYSQL_TYPE_BLOB;
else
t_bind[i] . buffer_type = MYSQL_TYPE_STRING;
}
bool t_result;
t_result = (mysql_stmt_bind_param(p_statement, t_bind) == 0);
return t_result;
}
void DBConnection_MYSQL::getTables(char *buffer, int *bufsize)
{
int rowseplen = 1;
char rowsep[] = "\n";
if (!buffer) {
*bufsize = 2500;
return;
}
char tsql[] = "SHOW tables";
DBCursor *newcursor = sqlQuery(tsql, NULL, 0, 0);
if (newcursor) {
char *result = buffer;
char *resultptr = result;
if (!newcursor->getEOF()){
while (True){
unsigned int colsize;
char *coldata = newcursor->getFieldDataBinary(1,colsize);
colsize = strlen(coldata);
if (((resultptr-result) + colsize + rowseplen + 16 )
> *bufsize)
break;
memcpy(resultptr,coldata,colsize);
resultptr+=colsize;
newcursor->next();
if (newcursor->getEOF()) break;
memcpy(resultptr,rowsep,rowseplen);
resultptr+=rowseplen;
}
}
deleteCursor(newcursor->GetID());
*resultptr++ = '\0';
*bufsize = resultptr-result;
}
newcursor = NULL;
}
/*MYSQL doesn't support transactions so leave blank for now*/
void DBConnection_MYSQL::transBegin() {}
void DBConnection_MYSQL::transCommit(){}
void DBConnection_MYSQL::transRollback(){}