diff options
Diffstat (limited to 'Frameworks/SPMySQLFramework')
60 files changed, 10146 insertions, 0 deletions
diff --git a/Frameworks/SPMySQLFramework/English.lproj/InfoPlist.strings b/Frameworks/SPMySQLFramework/English.lproj/InfoPlist.strings new file mode 100644 index 00000000..88f65cf6 --- /dev/null +++ b/Frameworks/SPMySQLFramework/English.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/my_alloc.h b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/my_alloc.h new file mode 100644 index 00000000..93b7438a --- /dev/null +++ b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/my_alloc.h @@ -0,0 +1,51 @@ +/* Copyright (C) 2000 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + Data structures for mysys/my_alloc.c (root memory allocator) +*/ + +#ifndef _my_alloc_h +#define _my_alloc_h + +#define ALLOC_MAX_BLOCK_TO_DROP 4096 +#define ALLOC_MAX_BLOCK_USAGE_BEFORE_DROP 10 + +typedef struct st_used_mem +{ /* struct for once_alloc (block) */ + struct st_used_mem *next; /* Next block in use */ + unsigned int left; /* memory left in block */ + unsigned int size; /* size of block */ +} USED_MEM; + + +typedef struct st_mem_root +{ + USED_MEM *free; /* blocks with free memory in it */ + USED_MEM *used; /* blocks almost without free memory */ + USED_MEM *pre_alloc; /* preallocated block */ + /* if block have less memory it will be put in 'used' list */ + size_t min_malloc; + size_t block_size; /* initial block size */ + unsigned int block_num; /* allocated blocks counter */ + /* + first free block in queue test counter (if it exceed + MAX_BLOCK_USAGE_BEFORE_DROP block will be dropped in 'used' list) + */ + unsigned int first_block_usage; + + void (*error_handler)(void); +} MEM_ROOT; +#endif diff --git a/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/my_list.h b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/my_list.h new file mode 100644 index 00000000..775b5658 --- /dev/null +++ b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/my_list.h @@ -0,0 +1,45 @@ +/* Copyright (C) 2000 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef _list_h_ +#define _list_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct st_list { + struct st_list *prev,*next; + void *data; +} LIST; + +typedef int (*list_walk_action)(void *,void *); + +extern LIST *list_add(LIST *root,LIST *element); +extern LIST *list_delete(LIST *root,LIST *element); +extern LIST *list_cons(void *data,LIST *root); +extern LIST *list_reverse(LIST *root); +extern void list_free(LIST *root,unsigned int free_data); +extern unsigned int list_length(LIST *); +extern int list_walk(LIST *,list_walk_action action,unsigned char * argument); + +#define list_rest(a) ((a)->next) +#define list_push(a,b) (a)=list_cons((b),(a)) +#define list_pop(A) {LIST *old=(A); (A)=list_delete(old,old) ; my_free((unsigned char *) old,MYF(MY_FAE)); } + +#ifdef __cplusplus +} +#endif +#endif diff --git a/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql.h b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql.h new file mode 100644 index 00000000..da477278 --- /dev/null +++ b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql.h @@ -0,0 +1,872 @@ +/* + Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* + This file defines the client API to MySQL and also the ABI of the + dynamically linked libmysqlclient. + + The ABI should never be changed in a released product of MySQL + thus you need to take great care when changing the file. In case + the file is changed so the ABI is broken, you must also + update the SHAREDLIB_MAJOR_VERSION in configure.in . + +*/ + +#ifndef _mysql_h +#define _mysql_h + +#ifdef _AIX /* large-file support will break without this */ +#include <standards.h> +#endif + +#ifdef __CYGWIN__ /* CYGWIN implements a UNIX API */ +#undef WIN +#undef _WIN +#undef _WIN32 +#undef _WIN64 +#undef __WIN__ +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _global_h /* If not standard header */ +#ifndef MYSQL_ABI_CHECK +#include <sys/types.h> +#endif +#ifdef __LCC__ +#include <winsock2.h> /* For windows */ +#endif +typedef char my_bool; +#if (defined(_WIN32) || defined(_WIN64)) && !defined(__WIN__) +#define __WIN__ +#endif +#if !defined(__WIN__) +#define STDCALL +#else +#define STDCALL __stdcall +#endif + +#ifndef my_socket_defined +#ifdef __WIN__ +#define my_socket SOCKET +#else +typedef int my_socket; +#endif /* __WIN__ */ +#endif /* my_socket_defined */ +#endif /* _global_h */ + +#include "mysql_version.h" +#include "mysql_com.h" +#include "mysql_time.h" + +#include "my_list.h" /* for LISTs used in 'MYSQL' and 'MYSQL_STMT' */ + +extern unsigned int mysql_port; +extern char *mysql_unix_port; + +#define CLIENT_NET_READ_TIMEOUT 365*24*3600 /* Timeout on read */ +#define CLIENT_NET_WRITE_TIMEOUT 365*24*3600 /* Timeout on write */ + +#ifdef __NETWARE__ +#pragma pack(push, 8) /* 8 byte alignment */ +#endif + +#define IS_PRI_KEY(n) ((n) & PRI_KEY_FLAG) +#define IS_NOT_NULL(n) ((n) & NOT_NULL_FLAG) +#define IS_BLOB(n) ((n) & BLOB_FLAG) +#define IS_NUM(t) ((t) <= MYSQL_TYPE_INT24 || (t) == MYSQL_TYPE_YEAR || (t) == MYSQL_TYPE_NEWDECIMAL) +#define IS_NUM_FIELD(f) ((f)->flags & NUM_FLAG) +#define INTERNAL_NUM_FIELD(f) (((f)->type <= MYSQL_TYPE_INT24 && ((f)->type != MYSQL_TYPE_TIMESTAMP || (f)->length == 14 || (f)->length == 8)) || (f)->type == MYSQL_TYPE_YEAR) +#define IS_LONGDATA(t) ((t) >= MYSQL_TYPE_TINY_BLOB && (t) <= MYSQL_TYPE_STRING) + + +typedef struct st_mysql_field { + char *name; /* Name of column */ + char *org_name; /* Original column name, if an alias */ + char *table; /* Table of column if column was a field */ + char *org_table; /* Org table name, if table was an alias */ + char *db; /* Database for table */ + char *catalog; /* Catalog for table */ + char *def; /* Default value (set by mysql_list_fields) */ + unsigned long length; /* Width of column (create length) */ + unsigned long max_length; /* Max width for selected set */ + unsigned int name_length; + unsigned int org_name_length; + unsigned int table_length; + unsigned int org_table_length; + unsigned int db_length; + unsigned int catalog_length; + unsigned int def_length; + unsigned int flags; /* Div flags */ + unsigned int decimals; /* Number of decimals in field */ + unsigned int charsetnr; /* Character set */ + enum enum_field_types type; /* Type of field. See mysql_com.h for types */ + void *extension; +} MYSQL_FIELD; + +typedef char **MYSQL_ROW; /* return data as array of strings */ +typedef unsigned int MYSQL_FIELD_OFFSET; /* offset to current field */ + +#ifndef _global_h +#if defined(NO_CLIENT_LONG_LONG) +typedef unsigned long my_ulonglong; +#elif defined (__WIN__) +typedef unsigned __int64 my_ulonglong; +#else +typedef unsigned long long my_ulonglong; +#endif +#endif + +#include "typelib.h" + +#define MYSQL_COUNT_ERROR (~(my_ulonglong) 0) + +/* backward compatibility define - to be removed eventually */ +#define ER_WARN_DATA_TRUNCATED WARN_DATA_TRUNCATED + +typedef struct st_mysql_rows { + struct st_mysql_rows *next; /* list of rows */ + MYSQL_ROW data; + unsigned long length; +} MYSQL_ROWS; + +typedef MYSQL_ROWS *MYSQL_ROW_OFFSET; /* offset to current row */ + +#include "my_alloc.h" + +typedef struct embedded_query_result EMBEDDED_QUERY_RESULT; +typedef struct st_mysql_data { + MYSQL_ROWS *data; + struct embedded_query_result *embedded_info; + MEM_ROOT alloc; + my_ulonglong rows; + unsigned int fields; + /* extra info for embedded library */ + void *extension; +} MYSQL_DATA; + +enum mysql_option +{ + MYSQL_OPT_CONNECT_TIMEOUT, MYSQL_OPT_COMPRESS, MYSQL_OPT_NAMED_PIPE, + MYSQL_INIT_COMMAND, MYSQL_READ_DEFAULT_FILE, MYSQL_READ_DEFAULT_GROUP, + MYSQL_SET_CHARSET_DIR, MYSQL_SET_CHARSET_NAME, MYSQL_OPT_LOCAL_INFILE, + MYSQL_OPT_PROTOCOL, MYSQL_SHARED_MEMORY_BASE_NAME, MYSQL_OPT_READ_TIMEOUT, + MYSQL_OPT_WRITE_TIMEOUT, MYSQL_OPT_USE_RESULT, + MYSQL_OPT_USE_REMOTE_CONNECTION, MYSQL_OPT_USE_EMBEDDED_CONNECTION, + MYSQL_OPT_GUESS_CONNECTION, MYSQL_SET_CLIENT_IP, MYSQL_SECURE_AUTH, + MYSQL_REPORT_DATA_TRUNCATION, MYSQL_OPT_RECONNECT, + MYSQL_OPT_SSL_VERIFY_SERVER_CERT +}; + +struct st_mysql_options { + unsigned int connect_timeout, read_timeout, write_timeout; + unsigned int port, protocol; + unsigned long client_flag; + char *host,*user,*password,*unix_socket,*db; + struct st_dynamic_array *init_commands; + char *my_cnf_file,*my_cnf_group, *charset_dir, *charset_name; + char *ssl_key; /* PEM key file */ + char *ssl_cert; /* PEM cert file */ + char *ssl_ca; /* PEM CA file */ + char *ssl_capath; /* PEM directory of CA-s? */ + char *ssl_cipher; /* cipher to use */ + char *shared_memory_base_name; + unsigned long max_allowed_packet; + my_bool use_ssl; /* if to use SSL or not */ + my_bool compress,named_pipe; + /* + On connect, find out the replication role of the server, and + establish connections to all the peers + */ + my_bool rpl_probe; + /* + Each call to mysql_real_query() will parse it to tell if it is a read + or a write, and direct it to the slave or the master + */ + my_bool rpl_parse; + /* + If set, never read from a master, only from slave, when doing + a read that is replication-aware + */ + my_bool no_master_reads; +#if !defined(CHECK_EMBEDDED_DIFFERENCES) || defined(EMBEDDED_LIBRARY) + my_bool separate_thread; +#endif + enum mysql_option methods_to_use; + char *client_ip; + /* Refuse client connecting to server if it uses old (pre-4.1.1) protocol */ + my_bool secure_auth; + /* 0 - never report, 1 - always report (default) */ + my_bool report_data_truncation; + + /* function pointers for local infile support */ + int (*local_infile_init)(void **, const char *, void *); + int (*local_infile_read)(void *, char *, unsigned int); + void (*local_infile_end)(void *); + int (*local_infile_error)(void *, char *, unsigned int); + void *local_infile_userdata; + void *extension; +}; + +enum mysql_status +{ + MYSQL_STATUS_READY, MYSQL_STATUS_GET_RESULT, MYSQL_STATUS_USE_RESULT, + MYSQL_STATUS_STATEMENT_GET_RESULT +}; + +enum mysql_protocol_type +{ + MYSQL_PROTOCOL_DEFAULT, MYSQL_PROTOCOL_TCP, MYSQL_PROTOCOL_SOCKET, + MYSQL_PROTOCOL_PIPE, MYSQL_PROTOCOL_MEMORY +}; +/* + There are three types of queries - the ones that have to go to + the master, the ones that go to a slave, and the adminstrative + type which must happen on the pivot connectioin +*/ +enum mysql_rpl_type +{ + MYSQL_RPL_MASTER, MYSQL_RPL_SLAVE, MYSQL_RPL_ADMIN +}; + +typedef struct character_set +{ + unsigned int number; /* character set number */ + unsigned int state; /* character set state */ + const char *csname; /* collation name */ + const char *name; /* character set name */ + const char *comment; /* comment */ + const char *dir; /* character set directory */ + unsigned int mbminlen; /* min. length for multibyte strings */ + unsigned int mbmaxlen; /* max. length for multibyte strings */ +} MY_CHARSET_INFO; + +struct st_mysql_methods; +struct st_mysql_stmt; + +typedef struct st_mysql +{ + NET net; /* Communication parameters */ + unsigned char *connector_fd; /* ConnectorFd for SSL */ + char *host,*user,*passwd,*unix_socket,*server_version,*host_info; + char *info, *db; + struct charset_info_st *charset; + MYSQL_FIELD *fields; + MEM_ROOT field_alloc; + my_ulonglong affected_rows; + my_ulonglong insert_id; /* id if insert on table with NEXTNR */ + my_ulonglong extra_info; /* Not used */ + unsigned long thread_id; /* Id for connection in server */ + unsigned long packet_length; + unsigned int port; + unsigned long client_flag,server_capabilities; + unsigned int protocol_version; + unsigned int field_count; + unsigned int server_status; + unsigned int server_language; + unsigned int warning_count; + struct st_mysql_options options; + enum mysql_status status; + my_bool free_me; /* If free in mysql_close */ + my_bool reconnect; /* set to 1 if automatic reconnect */ + + /* session-wide random string */ + char scramble[SCRAMBLE_LENGTH+1]; + + /* + Set if this is the original connection, not a master or a slave we have + added though mysql_rpl_probe() or mysql_set_master()/ mysql_add_slave() + */ + my_bool rpl_pivot; + /* + Pointers to the master, and the next slave connections, points to + itself if lone connection. + */ + struct st_mysql* master, *next_slave; + + struct st_mysql* last_used_slave; /* needed for round-robin slave pick */ + /* needed for send/read/store/use result to work correctly with replication */ + struct st_mysql* last_used_con; + + LIST *stmts; /* list of all statements */ + const struct st_mysql_methods *methods; + void *thd; + /* + Points to boolean flag in MYSQL_RES or MYSQL_STMT. We set this flag + from mysql_stmt_close if close had to cancel result set of this object. + */ + my_bool *unbuffered_fetch_owner; + /* needed for embedded server - no net buffer to store the 'info' */ + char *info_buffer; + void *extension; +} MYSQL; + + +typedef struct st_mysql_res { + my_ulonglong row_count; + MYSQL_FIELD *fields; + MYSQL_DATA *data; + MYSQL_ROWS *data_cursor; + unsigned long *lengths; /* column lengths of current row */ + MYSQL *handle; /* for unbuffered reads */ + const struct st_mysql_methods *methods; + MYSQL_ROW row; /* If unbuffered read */ + MYSQL_ROW current_row; /* buffer to current row */ + MEM_ROOT field_alloc; + unsigned int field_count, current_field; + my_bool eof; /* Used by mysql_fetch_row */ + /* mysql_stmt_close() had to cancel this result */ + my_bool unbuffered_fetch_cancelled; + void *extension; +} MYSQL_RES; + +#define MAX_MYSQL_MANAGER_ERR 256 +#define MAX_MYSQL_MANAGER_MSG 256 + +#define MANAGER_OK 200 +#define MANAGER_INFO 250 +#define MANAGER_ACCESS 401 +#define MANAGER_CLIENT_ERR 450 +#define MANAGER_INTERNAL_ERR 500 + +#if !defined(MYSQL_SERVER) && !defined(MYSQL_CLIENT) +#define MYSQL_CLIENT +#endif + + +typedef struct st_mysql_manager +{ + NET net; + char *host, *user, *passwd; + char *net_buf, *net_buf_pos, *net_data_end; + unsigned int port; + int cmd_status; + int last_errno; + int net_buf_size; + my_bool free_me; + my_bool eof; + char last_error[MAX_MYSQL_MANAGER_ERR]; + void *extension; +} MYSQL_MANAGER; + +typedef struct st_mysql_parameters +{ + unsigned long *p_max_allowed_packet; + unsigned long *p_net_buffer_length; + void *extension; +} MYSQL_PARAMETERS; + +#if !defined(MYSQL_SERVER) && !defined(EMBEDDED_LIBRARY) +#define max_allowed_packet (*mysql_get_parameters()->p_max_allowed_packet) +#define net_buffer_length (*mysql_get_parameters()->p_net_buffer_length) +#endif + +/* + Set up and bring down the server; to ensure that applications will + work when linked against either the standard client library or the + embedded server library, these functions should be called. +*/ +int STDCALL mysql_server_init(int argc, char **argv, char **groups); +void STDCALL mysql_server_end(void); + +/* + mysql_server_init/end need to be called when using libmysqld or + libmysqlclient (exactly, mysql_server_init() is called by mysql_init() so + you don't need to call it explicitely; but you need to call + mysql_server_end() to free memory). The names are a bit misleading + (mysql_SERVER* to be used when using libmysqlCLIENT). So we add more general + names which suit well whether you're using libmysqld or libmysqlclient. We + intend to promote these aliases over the mysql_server* ones. +*/ +#define mysql_library_init mysql_server_init +#define mysql_library_end mysql_server_end + +MYSQL_PARAMETERS *STDCALL mysql_get_parameters(void); + +/* + Set up and bring down a thread; these function should be called + for each thread in an application which opens at least one MySQL + connection. All uses of the connection(s) should be between these + function calls. +*/ +my_bool STDCALL mysql_thread_init(void); +void STDCALL mysql_thread_end(void); + +/* + Functions to get information from the MYSQL and MYSQL_RES structures + Should definitely be used if one uses shared libraries. +*/ + +my_ulonglong STDCALL mysql_num_rows(MYSQL_RES *res); +unsigned int STDCALL mysql_num_fields(MYSQL_RES *res); +my_bool STDCALL mysql_eof(MYSQL_RES *res); +MYSQL_FIELD *STDCALL mysql_fetch_field_direct(MYSQL_RES *res, + unsigned int fieldnr); +MYSQL_FIELD * STDCALL mysql_fetch_fields(MYSQL_RES *res); +MYSQL_ROW_OFFSET STDCALL mysql_row_tell(MYSQL_RES *res); +MYSQL_FIELD_OFFSET STDCALL mysql_field_tell(MYSQL_RES *res); + +unsigned int STDCALL mysql_field_count(MYSQL *mysql); +my_ulonglong STDCALL mysql_affected_rows(MYSQL *mysql); +my_ulonglong STDCALL mysql_insert_id(MYSQL *mysql); +unsigned int STDCALL mysql_errno(MYSQL *mysql); +const char * STDCALL mysql_error(MYSQL *mysql); +const char *STDCALL mysql_sqlstate(MYSQL *mysql); +unsigned int STDCALL mysql_warning_count(MYSQL *mysql); +const char * STDCALL mysql_info(MYSQL *mysql); +unsigned long STDCALL mysql_thread_id(MYSQL *mysql); +const char * STDCALL mysql_character_set_name(MYSQL *mysql); +int STDCALL mysql_set_character_set(MYSQL *mysql, const char *csname); + +MYSQL * STDCALL mysql_init(MYSQL *mysql); +my_bool STDCALL mysql_ssl_set(MYSQL *mysql, const char *key, + const char *cert, const char *ca, + const char *capath, const char *cipher); +const char * STDCALL mysql_get_ssl_cipher(MYSQL *mysql); +my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, + const char *passwd, const char *db); +MYSQL * STDCALL mysql_real_connect(MYSQL *mysql, const char *host, + const char *user, + const char *passwd, + const char *db, + unsigned int port, + const char *unix_socket, + unsigned long clientflag); +int STDCALL mysql_select_db(MYSQL *mysql, const char *db); +int STDCALL mysql_query(MYSQL *mysql, const char *q); +int STDCALL mysql_send_query(MYSQL *mysql, const char *q, + unsigned long length); +int STDCALL mysql_real_query(MYSQL *mysql, const char *q, + unsigned long length); +MYSQL_RES * STDCALL mysql_store_result(MYSQL *mysql); +MYSQL_RES * STDCALL mysql_use_result(MYSQL *mysql); + +/* perform query on master */ +my_bool STDCALL mysql_master_query(MYSQL *mysql, const char *q, + unsigned long length); +my_bool STDCALL mysql_master_send_query(MYSQL *mysql, const char *q, + unsigned long length); +/* perform query on slave */ +my_bool STDCALL mysql_slave_query(MYSQL *mysql, const char *q, + unsigned long length); +my_bool STDCALL mysql_slave_send_query(MYSQL *mysql, const char *q, + unsigned long length); +void STDCALL mysql_get_character_set_info(MYSQL *mysql, + MY_CHARSET_INFO *charset); + +/* local infile support */ + +#define LOCAL_INFILE_ERROR_LEN 512 + +void +mysql_set_local_infile_handler(MYSQL *mysql, + int (*local_infile_init)(void **, const char *, + void *), + int (*local_infile_read)(void *, char *, + unsigned int), + void (*local_infile_end)(void *), + int (*local_infile_error)(void *, char*, + unsigned int), + void *); + +void +mysql_set_local_infile_default(MYSQL *mysql); + + +/* + enable/disable parsing of all queries to decide if they go on master or + slave +*/ +void STDCALL mysql_enable_rpl_parse(MYSQL* mysql); +void STDCALL mysql_disable_rpl_parse(MYSQL* mysql); +/* get the value of the parse flag */ +int STDCALL mysql_rpl_parse_enabled(MYSQL* mysql); + +/* enable/disable reads from master */ +void STDCALL mysql_enable_reads_from_master(MYSQL* mysql); +void STDCALL mysql_disable_reads_from_master(MYSQL* mysql); +/* get the value of the master read flag */ +my_bool STDCALL mysql_reads_from_master_enabled(MYSQL* mysql); + +enum mysql_rpl_type STDCALL mysql_rpl_query_type(const char* q, int len); + +/* discover the master and its slaves */ +my_bool STDCALL mysql_rpl_probe(MYSQL* mysql); + +/* set the master, close/free the old one, if it is not a pivot */ +int STDCALL mysql_set_master(MYSQL* mysql, const char* host, + unsigned int port, + const char* user, + const char* passwd); +int STDCALL mysql_add_slave(MYSQL* mysql, const char* host, + unsigned int port, + const char* user, + const char* passwd); + +int STDCALL mysql_shutdown(MYSQL *mysql, + enum mysql_enum_shutdown_level + shutdown_level); +int STDCALL mysql_dump_debug_info(MYSQL *mysql); +int STDCALL mysql_refresh(MYSQL *mysql, + unsigned int refresh_options); +int STDCALL mysql_kill(MYSQL *mysql,unsigned long pid); +int STDCALL mysql_set_server_option(MYSQL *mysql, + enum enum_mysql_set_option + option); +int STDCALL mysql_ping(MYSQL *mysql); +const char * STDCALL mysql_stat(MYSQL *mysql); +const char * STDCALL mysql_get_server_info(MYSQL *mysql); +const char * STDCALL mysql_get_client_info(void); +unsigned long STDCALL mysql_get_client_version(void); +const char * STDCALL mysql_get_host_info(MYSQL *mysql); +unsigned long STDCALL mysql_get_server_version(MYSQL *mysql); +unsigned int STDCALL mysql_get_proto_info(MYSQL *mysql); +MYSQL_RES * STDCALL mysql_list_dbs(MYSQL *mysql,const char *wild); +MYSQL_RES * STDCALL mysql_list_tables(MYSQL *mysql,const char *wild); +MYSQL_RES * STDCALL mysql_list_processes(MYSQL *mysql); +int STDCALL mysql_options(MYSQL *mysql,enum mysql_option option, + const void *arg); +void STDCALL mysql_free_result(MYSQL_RES *result); +void STDCALL mysql_data_seek(MYSQL_RES *result, + my_ulonglong offset); +MYSQL_ROW_OFFSET STDCALL mysql_row_seek(MYSQL_RES *result, + MYSQL_ROW_OFFSET offset); +MYSQL_FIELD_OFFSET STDCALL mysql_field_seek(MYSQL_RES *result, + MYSQL_FIELD_OFFSET offset); +MYSQL_ROW STDCALL mysql_fetch_row(MYSQL_RES *result); +unsigned long * STDCALL mysql_fetch_lengths(MYSQL_RES *result); +MYSQL_FIELD * STDCALL mysql_fetch_field(MYSQL_RES *result); +MYSQL_RES * STDCALL mysql_list_fields(MYSQL *mysql, const char *table, + const char *wild); +unsigned long STDCALL mysql_escape_string(char *to,const char *from, + unsigned long from_length); +unsigned long STDCALL mysql_hex_string(char *to,const char *from, + unsigned long from_length); +unsigned long STDCALL mysql_real_escape_string(MYSQL *mysql, + char *to,const char *from, + unsigned long length); +void STDCALL mysql_debug(const char *debug); +void STDCALL myodbc_remove_escape(MYSQL *mysql,char *name); +unsigned int STDCALL mysql_thread_safe(void); +my_bool STDCALL mysql_embedded(void); +MYSQL_MANAGER* STDCALL mysql_manager_init(MYSQL_MANAGER* con); +MYSQL_MANAGER* STDCALL mysql_manager_connect(MYSQL_MANAGER* con, + const char* host, + const char* user, + const char* passwd, + unsigned int port); +void STDCALL mysql_manager_close(MYSQL_MANAGER* con); +int STDCALL mysql_manager_command(MYSQL_MANAGER* con, + const char* cmd, int cmd_len); +int STDCALL mysql_manager_fetch_line(MYSQL_MANAGER* con, + char* res_buf, + int res_buf_size); +my_bool STDCALL mysql_read_query_result(MYSQL *mysql); + + +/* + The following definitions are added for the enhanced + client-server protocol +*/ + +/* statement state */ +enum enum_mysql_stmt_state +{ + MYSQL_STMT_INIT_DONE= 1, MYSQL_STMT_PREPARE_DONE, MYSQL_STMT_EXECUTE_DONE, + MYSQL_STMT_FETCH_DONE +}; + + +/* + This structure is used to define bind information, and + internally by the client library. + Public members with their descriptions are listed below + (conventionally `On input' refers to the binds given to + mysql_stmt_bind_param, `On output' refers to the binds given + to mysql_stmt_bind_result): + + buffer_type - One of the MYSQL_* types, used to describe + the host language type of buffer. + On output: if column type is different from + buffer_type, column value is automatically converted + to buffer_type before it is stored in the buffer. + buffer - On input: points to the buffer with input data. + On output: points to the buffer capable to store + output data. + The type of memory pointed by buffer must correspond + to buffer_type. See the correspondence table in + the comment to mysql_stmt_bind_param. + + The two above members are mandatory for any kind of bind. + + buffer_length - the length of the buffer. You don't have to set + it for any fixed length buffer: float, double, + int, etc. It must be set however for variable-length + types, such as BLOBs or STRINGs. + + length - On input: in case when lengths of input values + are different for each execute, you can set this to + point at a variable containining value length. This + way the value length can be different in each execute. + If length is not NULL, buffer_length is not used. + Note, length can even point at buffer_length if + you keep bind structures around while fetching: + this way you can change buffer_length before + each execution, everything will work ok. + On output: if length is set, mysql_stmt_fetch will + write column length into it. + + is_null - On input: points to a boolean variable that should + be set to TRUE for NULL values. + This member is useful only if your data may be + NULL in some but not all cases. + If your data is never NULL, is_null should be set to 0. + If your data is always NULL, set buffer_type + to MYSQL_TYPE_NULL, and is_null will not be used. + + is_unsigned - On input: used to signify that values provided for one + of numeric types are unsigned. + On output describes signedness of the output buffer. + If, taking into account is_unsigned flag, column data + is out of range of the output buffer, data for this column + is regarded truncated. Note that this has no correspondence + to the sign of result set column, if you need to find it out + use mysql_stmt_result_metadata. + error - where to write a truncation error if it is present. + possible error value is: + 0 no truncation + 1 value is out of range or buffer is too small + + Please note that MYSQL_BIND also has internals members. +*/ + +typedef struct st_mysql_bind +{ + unsigned long *length; /* output length pointer */ + my_bool *is_null; /* Pointer to null indicator */ + void *buffer; /* buffer to get/put data */ + /* set this if you want to track data truncations happened during fetch */ + my_bool *error; + unsigned char *row_ptr; /* for the current data position */ + void (*store_param_func)(NET *net, struct st_mysql_bind *param); + void (*fetch_result)(struct st_mysql_bind *, MYSQL_FIELD *, + unsigned char **row); + void (*skip_result)(struct st_mysql_bind *, MYSQL_FIELD *, + unsigned char **row); + /* output buffer length, must be set when fetching str/binary */ + unsigned long buffer_length; + unsigned long offset; /* offset position for char/binary fetch */ + unsigned long length_value; /* Used if length is 0 */ + unsigned int param_number; /* For null count and error messages */ + unsigned int pack_length; /* Internal length for packed data */ + enum enum_field_types buffer_type; /* buffer type */ + my_bool error_value; /* used if error is 0 */ + my_bool is_unsigned; /* set if integer type is unsigned */ + my_bool long_data_used; /* If used with mysql_send_long_data */ + my_bool is_null_value; /* Used if is_null is 0 */ + void *extension; +} MYSQL_BIND; + + +/* statement handler */ +typedef struct st_mysql_stmt +{ + MEM_ROOT mem_root; /* root allocations */ + LIST list; /* list to keep track of all stmts */ + MYSQL *mysql; /* connection handle */ + MYSQL_BIND *params; /* input parameters */ + MYSQL_BIND *bind; /* output parameters */ + MYSQL_FIELD *fields; /* result set metadata */ + MYSQL_DATA result; /* cached result set */ + MYSQL_ROWS *data_cursor; /* current row in cached result */ + /* + mysql_stmt_fetch() calls this function to fetch one row (it's different + for buffered, unbuffered and cursor fetch). + */ + int (*read_row_func)(struct st_mysql_stmt *stmt, + unsigned char **row); + /* copy of mysql->affected_rows after statement execution */ + my_ulonglong affected_rows; + my_ulonglong insert_id; /* copy of mysql->insert_id */ + unsigned long stmt_id; /* Id for prepared statement */ + unsigned long flags; /* i.e. type of cursor to open */ + unsigned long prefetch_rows; /* number of rows per one COM_FETCH */ + /* + Copied from mysql->server_status after execute/fetch to know + server-side cursor status for this statement. + */ + unsigned int server_status; + unsigned int last_errno; /* error code */ + unsigned int param_count; /* input parameter count */ + unsigned int field_count; /* number of columns in result set */ + enum enum_mysql_stmt_state state; /* statement state */ + char last_error[MYSQL_ERRMSG_SIZE]; /* error message */ + char sqlstate[SQLSTATE_LENGTH+1]; + /* Types of input parameters should be sent to server */ + my_bool send_types_to_server; + my_bool bind_param_done; /* input buffers were supplied */ + unsigned char bind_result_done; /* output buffers were supplied */ + /* mysql_stmt_close() had to cancel this result */ + my_bool unbuffered_fetch_cancelled; + /* + Is set to true if we need to calculate field->max_length for + metadata fields when doing mysql_stmt_store_result. + */ + my_bool update_max_length; + void *extension; +} MYSQL_STMT; + +enum enum_stmt_attr_type +{ + /* + When doing mysql_stmt_store_result calculate max_length attribute + of statement metadata. This is to be consistent with the old API, + where this was done automatically. + In the new API we do that only by request because it slows down + mysql_stmt_store_result sufficiently. + */ + STMT_ATTR_UPDATE_MAX_LENGTH, + /* + unsigned long with combination of cursor flags (read only, for update, + etc) + */ + STMT_ATTR_CURSOR_TYPE, + /* + Amount of rows to retrieve from server per one fetch if using cursors. + Accepts unsigned long attribute in the range 1 - ulong_max + */ + STMT_ATTR_PREFETCH_ROWS +}; + + +typedef struct st_mysql_methods +{ + my_bool (*read_query_result)(MYSQL *mysql); + my_bool (*advanced_command)(MYSQL *mysql, + enum enum_server_command command, + const unsigned char *header, + unsigned long header_length, + const unsigned char *arg, + unsigned long arg_length, + my_bool skip_check, + MYSQL_STMT *stmt); + MYSQL_DATA *(*read_rows)(MYSQL *mysql,MYSQL_FIELD *mysql_fields, + unsigned int fields); + MYSQL_RES * (*use_result)(MYSQL *mysql); + void (*fetch_lengths)(unsigned long *to, + MYSQL_ROW column, unsigned int field_count); + void (*flush_use_result)(MYSQL *mysql); +#if !defined(MYSQL_SERVER) || defined(EMBEDDED_LIBRARY) + MYSQL_FIELD * (*list_fields)(MYSQL *mysql); + my_bool (*read_prepare_result)(MYSQL *mysql, MYSQL_STMT *stmt); + int (*stmt_execute)(MYSQL_STMT *stmt); + int (*read_binary_rows)(MYSQL_STMT *stmt); + int (*unbuffered_fetch)(MYSQL *mysql, char **row); + void (*free_embedded_thd)(MYSQL *mysql); + const char *(*read_statistics)(MYSQL *mysql); + my_bool (*next_result)(MYSQL *mysql); + int (*read_change_user_result)(MYSQL *mysql, char *buff, const char *passwd); + int (*read_rows_from_cursor)(MYSQL_STMT *stmt); +#endif +} MYSQL_METHODS; + + +MYSQL_STMT * STDCALL mysql_stmt_init(MYSQL *mysql); +int STDCALL mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, + unsigned long length); +int STDCALL mysql_stmt_execute(MYSQL_STMT *stmt); +int STDCALL mysql_stmt_fetch(MYSQL_STMT *stmt); +int STDCALL mysql_stmt_fetch_column(MYSQL_STMT *stmt, MYSQL_BIND *bind_arg, + unsigned int column, + unsigned long offset); +int STDCALL mysql_stmt_store_result(MYSQL_STMT *stmt); +unsigned long STDCALL mysql_stmt_param_count(MYSQL_STMT * stmt); +my_bool STDCALL mysql_stmt_attr_set(MYSQL_STMT *stmt, + enum enum_stmt_attr_type attr_type, + const void *attr); +my_bool STDCALL mysql_stmt_attr_get(MYSQL_STMT *stmt, + enum enum_stmt_attr_type attr_type, + void *attr); +my_bool STDCALL mysql_stmt_bind_param(MYSQL_STMT * stmt, MYSQL_BIND * bnd); +my_bool STDCALL mysql_stmt_bind_result(MYSQL_STMT * stmt, MYSQL_BIND * bnd); +my_bool STDCALL mysql_stmt_close(MYSQL_STMT * stmt); +my_bool STDCALL mysql_stmt_reset(MYSQL_STMT * stmt); +my_bool STDCALL mysql_stmt_free_result(MYSQL_STMT *stmt); +my_bool STDCALL mysql_stmt_send_long_data(MYSQL_STMT *stmt, + unsigned int param_number, + const char *data, + unsigned long length); +MYSQL_RES *STDCALL mysql_stmt_result_metadata(MYSQL_STMT *stmt); +MYSQL_RES *STDCALL mysql_stmt_param_metadata(MYSQL_STMT *stmt); +unsigned int STDCALL mysql_stmt_errno(MYSQL_STMT * stmt); +const char *STDCALL mysql_stmt_error(MYSQL_STMT * stmt); +const char *STDCALL mysql_stmt_sqlstate(MYSQL_STMT * stmt); +MYSQL_ROW_OFFSET STDCALL mysql_stmt_row_seek(MYSQL_STMT *stmt, + MYSQL_ROW_OFFSET offset); +MYSQL_ROW_OFFSET STDCALL mysql_stmt_row_tell(MYSQL_STMT *stmt); +void STDCALL mysql_stmt_data_seek(MYSQL_STMT *stmt, my_ulonglong offset); +my_ulonglong STDCALL mysql_stmt_num_rows(MYSQL_STMT *stmt); +my_ulonglong STDCALL mysql_stmt_affected_rows(MYSQL_STMT *stmt); +my_ulonglong STDCALL mysql_stmt_insert_id(MYSQL_STMT *stmt); +unsigned int STDCALL mysql_stmt_field_count(MYSQL_STMT *stmt); + +my_bool STDCALL mysql_commit(MYSQL * mysql); +my_bool STDCALL mysql_rollback(MYSQL * mysql); +my_bool STDCALL mysql_autocommit(MYSQL * mysql, my_bool auto_mode); +my_bool STDCALL mysql_more_results(MYSQL *mysql); +int STDCALL mysql_next_result(MYSQL *mysql); +void STDCALL mysql_close(MYSQL *sock); + + +/* status return codes */ +#define MYSQL_NO_DATA 100 +#define MYSQL_DATA_TRUNCATED 101 + +#define mysql_reload(mysql) mysql_refresh((mysql),REFRESH_GRANT) + +#ifdef USE_OLD_FUNCTIONS +MYSQL * STDCALL mysql_connect(MYSQL *mysql, const char *host, + const char *user, const char *passwd); +int STDCALL mysql_create_db(MYSQL *mysql, const char *DB); +int STDCALL mysql_drop_db(MYSQL *mysql, const char *DB); +#define mysql_reload(mysql) mysql_refresh((mysql),REFRESH_GRANT) +#endif +#define HAVE_MYSQL_REAL_CONNECT + +/* + The following functions are mainly exported because of mysqlbinlog; + They are not for general usage +*/ + +#define simple_command(mysql, command, arg, length, skip_check) \ + (*(mysql)->methods->advanced_command)(mysql, command, 0, \ + 0, arg, length, skip_check, NULL) +#define stmt_command(mysql, command, arg, length, stmt) \ + (*(mysql)->methods->advanced_command)(mysql, command, 0, \ + 0, arg, length, 1, stmt) + +#ifdef __NETWARE__ +#pragma pack(pop) /* restore alignment */ +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _mysql_h */ diff --git a/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_com.h b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_com.h new file mode 100644 index 00000000..357519d5 --- /dev/null +++ b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_com.h @@ -0,0 +1,532 @@ +/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* +** Common definition between mysql server & client +*/ + +#ifndef _mysql_com_h +#define _mysql_com_h + +#define HOSTNAME_LENGTH 60 +#define SYSTEM_CHARSET_MBMAXLEN 3 +#define NAME_CHAR_LEN 64 /* Field/table name length */ +#define USERNAME_CHAR_LENGTH 16 +#define NAME_LEN (NAME_CHAR_LEN*SYSTEM_CHARSET_MBMAXLEN) +#define USERNAME_LENGTH (USERNAME_CHAR_LENGTH*SYSTEM_CHARSET_MBMAXLEN) + +#define SERVER_VERSION_LENGTH 60 +#define SQLSTATE_LENGTH 5 + +/* + USER_HOST_BUFF_SIZE -- length of string buffer, that is enough to contain + username and hostname parts of the user identifier with trailing zero in + MySQL standard format: + user_name_part@host_name_part\0 +*/ +#define USER_HOST_BUFF_SIZE HOSTNAME_LENGTH + USERNAME_LENGTH + 2 + +#define LOCAL_HOST "localhost" +#define LOCAL_HOST_NAMEDPIPE "." + + +#if defined(__WIN__) && !defined( _CUSTOMCONFIG_) +#define MYSQL_NAMEDPIPE "MySQL" +#define MYSQL_SERVICENAME "MySQL" +#endif /* __WIN__ */ + +/* + You should add new commands to the end of this list, otherwise old + servers won't be able to handle them as 'unsupported'. +*/ + +enum enum_server_command +{ + COM_SLEEP, COM_QUIT, COM_INIT_DB, COM_QUERY, COM_FIELD_LIST, + COM_CREATE_DB, COM_DROP_DB, COM_REFRESH, COM_SHUTDOWN, COM_STATISTICS, + COM_PROCESS_INFO, COM_CONNECT, COM_PROCESS_KILL, COM_DEBUG, COM_PING, + COM_TIME, COM_DELAYED_INSERT, COM_CHANGE_USER, COM_BINLOG_DUMP, + COM_TABLE_DUMP, COM_CONNECT_OUT, COM_REGISTER_SLAVE, + COM_STMT_PREPARE, COM_STMT_EXECUTE, COM_STMT_SEND_LONG_DATA, COM_STMT_CLOSE, + COM_STMT_RESET, COM_SET_OPTION, COM_STMT_FETCH, COM_DAEMON, + /* don't forget to update const char *command_name[] in sql_parse.cc */ + + /* Must be last */ + COM_END +}; + + +/* + Length of random string sent by server on handshake; this is also length of + obfuscated password, recieved from client +*/ +#define SCRAMBLE_LENGTH 20 +#define SCRAMBLE_LENGTH_323 8 +/* length of password stored in the db: new passwords are preceeded with '*' */ +#define SCRAMBLED_PASSWORD_CHAR_LENGTH (SCRAMBLE_LENGTH*2+1) +#define SCRAMBLED_PASSWORD_CHAR_LENGTH_323 (SCRAMBLE_LENGTH_323*2) + + +#define NOT_NULL_FLAG 1 /* Field can't be NULL */ +#define PRI_KEY_FLAG 2 /* Field is part of a primary key */ +#define UNIQUE_KEY_FLAG 4 /* Field is part of a unique key */ +#define MULTIPLE_KEY_FLAG 8 /* Field is part of a key */ +#define BLOB_FLAG 16 /* Field is a blob */ +#define UNSIGNED_FLAG 32 /* Field is unsigned */ +#define ZEROFILL_FLAG 64 /* Field is zerofill */ +#define BINARY_FLAG 128 /* Field is binary */ + +/* The following are only sent to new clients */ +#define ENUM_FLAG 256 /* field is an enum */ +#define AUTO_INCREMENT_FLAG 512 /* field is a autoincrement field */ +#define TIMESTAMP_FLAG 1024 /* Field is a timestamp */ +#define SET_FLAG 2048 /* field is a set */ +#define NO_DEFAULT_VALUE_FLAG 4096 /* Field doesn't have default value */ +#define ON_UPDATE_NOW_FLAG 8192 /* Field is set to NOW on UPDATE */ +#define NUM_FLAG 32768 /* Field is num (for clients) */ +#define PART_KEY_FLAG 16384 /* Intern; Part of some key */ +#define GROUP_FLAG 32768 /* Intern: Group field */ +#define UNIQUE_FLAG 65536 /* Intern: Used by sql_yacc */ +#define BINCMP_FLAG 131072 /* Intern: Used by sql_yacc */ +#define GET_FIXED_FIELDS_FLAG (1 << 18) /* Used to get fields in item tree */ +#define FIELD_IN_PART_FUNC_FLAG (1 << 19)/* Field part of partition func */ +#define FIELD_IN_ADD_INDEX (1<< 20) /* Intern: Field used in ADD INDEX */ +#define FIELD_IS_RENAMED (1<< 21) /* Intern: Field is being renamed */ + +#define REFRESH_GRANT 1 /* Refresh grant tables */ +#define REFRESH_LOG 2 /* Start on new log file */ +#define REFRESH_TABLES 4 /* close all tables */ +#define REFRESH_HOSTS 8 /* Flush host cache */ +#define REFRESH_STATUS 16 /* Flush status variables */ +#define REFRESH_THREADS 32 /* Flush thread cache */ +#define REFRESH_SLAVE 64 /* Reset master info and restart slave + thread */ +#define REFRESH_MASTER 128 /* Remove all bin logs in the index + and truncate the index */ + +/* The following can't be set with mysql_refresh() */ +#define REFRESH_READ_LOCK 16384 /* Lock tables for read */ +#define REFRESH_FAST 32768 /* Intern flag */ + +/* RESET (remove all queries) from query cache */ +#define REFRESH_QUERY_CACHE 65536 +#define REFRESH_QUERY_CACHE_FREE 0x20000L /* pack query cache */ +#define REFRESH_DES_KEY_FILE 0x40000L +#define REFRESH_USER_RESOURCES 0x80000L + +#define CLIENT_LONG_PASSWORD 1 /* new more secure passwords */ +#define CLIENT_FOUND_ROWS 2 /* Found instead of affected rows */ +#define CLIENT_LONG_FLAG 4 /* Get all column flags */ +#define CLIENT_CONNECT_WITH_DB 8 /* One can specify db on connect */ +#define CLIENT_NO_SCHEMA 16 /* Don't allow database.table.column */ +#define CLIENT_COMPRESS 32 /* Can use compression protocol */ +#define CLIENT_ODBC 64 /* Odbc client */ +#define CLIENT_LOCAL_FILES 128 /* Can use LOAD DATA LOCAL */ +#define CLIENT_IGNORE_SPACE 256 /* Ignore spaces before '(' */ +#define CLIENT_PROTOCOL_41 512 /* New 4.1 protocol */ +#define CLIENT_INTERACTIVE 1024 /* This is an interactive client */ +#define CLIENT_SSL 2048 /* Switch to SSL after handshake */ +#define CLIENT_IGNORE_SIGPIPE 4096 /* IGNORE sigpipes */ +#define CLIENT_TRANSACTIONS 8192 /* Client knows about transactions */ +#define CLIENT_RESERVED 16384 /* Old flag for 4.1 protocol */ +#define CLIENT_SECURE_CONNECTION 32768 /* New 4.1 authentication */ +#define CLIENT_MULTI_STATEMENTS (1UL << 16) /* Enable/disable multi-stmt support */ +#define CLIENT_MULTI_RESULTS (1UL << 17) /* Enable/disable multi-results */ + +#define CLIENT_SSL_VERIFY_SERVER_CERT (1UL << 30) +#define CLIENT_REMEMBER_OPTIONS (1UL << 31) + +/* Gather all possible capabilites (flags) supported by the server */ +#define CLIENT_ALL_FLAGS (CLIENT_LONG_PASSWORD | \ + CLIENT_FOUND_ROWS | \ + CLIENT_LONG_FLAG | \ + CLIENT_CONNECT_WITH_DB | \ + CLIENT_NO_SCHEMA | \ + CLIENT_COMPRESS | \ + CLIENT_ODBC | \ + CLIENT_LOCAL_FILES | \ + CLIENT_IGNORE_SPACE | \ + CLIENT_PROTOCOL_41 | \ + CLIENT_INTERACTIVE | \ + CLIENT_SSL | \ + CLIENT_IGNORE_SIGPIPE | \ + CLIENT_TRANSACTIONS | \ + CLIENT_RESERVED | \ + CLIENT_SECURE_CONNECTION | \ + CLIENT_MULTI_STATEMENTS | \ + CLIENT_MULTI_RESULTS | \ + CLIENT_SSL_VERIFY_SERVER_CERT | \ + CLIENT_REMEMBER_OPTIONS) + +/* + Switch off the flags that are optional and depending on build flags + If any of the optional flags is supported by the build it will be switched + on before sending to the client during the connection handshake. +*/ +#define CLIENT_BASIC_FLAGS (((CLIENT_ALL_FLAGS & ~CLIENT_SSL) \ + & ~CLIENT_COMPRESS) \ + & ~CLIENT_SSL_VERIFY_SERVER_CERT) + +#define SERVER_STATUS_IN_TRANS 1 /* Transaction has started */ +#define SERVER_STATUS_AUTOCOMMIT 2 /* Server in auto_commit mode */ +#define SERVER_MORE_RESULTS_EXISTS 8 /* Multi query - next query exists */ +#define SERVER_QUERY_NO_GOOD_INDEX_USED 16 +#define SERVER_QUERY_NO_INDEX_USED 32 +/** + The server was able to fulfill the clients request and opened a + read-only non-scrollable cursor for a query. This flag comes + in reply to COM_STMT_EXECUTE and COM_STMT_FETCH commands. +*/ +#define SERVER_STATUS_CURSOR_EXISTS 64 +/** + This flag is sent when a read-only cursor is exhausted, in reply to + COM_STMT_FETCH command. +*/ +#define SERVER_STATUS_LAST_ROW_SENT 128 +#define SERVER_STATUS_DB_DROPPED 256 /* A database was dropped */ +#define SERVER_STATUS_NO_BACKSLASH_ESCAPES 512 +/** + Sent to the client if after a prepared statement reprepare + we discovered that the new statement returns a different + number of result set columns. +*/ +#define SERVER_STATUS_METADATA_CHANGED 1024 + +/** + Server status flags that must be cleared when starting + execution of a new SQL statement. + Flags from this set are only added to the + current server status by the execution engine, but + never removed -- the execution engine expects them + to disappear automagically by the next command. +*/ +#define SERVER_STATUS_CLEAR_SET (SERVER_QUERY_NO_GOOD_INDEX_USED| \ + SERVER_QUERY_NO_INDEX_USED|\ + SERVER_MORE_RESULTS_EXISTS|\ + SERVER_STATUS_METADATA_CHANGED) + +#define MYSQL_ERRMSG_SIZE 512 +#define NET_READ_TIMEOUT 30 /* Timeout on read */ +#define NET_WRITE_TIMEOUT 60 /* Timeout on write */ +#define NET_WAIT_TIMEOUT 8*60*60 /* Wait for new query */ + +#define ONLY_KILL_QUERY 1 + + +struct st_vio; /* Only C */ +typedef struct st_vio Vio; + +#define MAX_TINYINT_WIDTH 3 /* Max width for a TINY w.o. sign */ +#define MAX_SMALLINT_WIDTH 5 /* Max width for a SHORT w.o. sign */ +#define MAX_MEDIUMINT_WIDTH 8 /* Max width for a INT24 w.o. sign */ +#define MAX_INT_WIDTH 10 /* Max width for a LONG w.o. sign */ +#define MAX_BIGINT_WIDTH 20 /* Max width for a LONGLONG */ +#define MAX_CHAR_WIDTH 255 /* Max length for a CHAR colum */ +#define MAX_BLOB_WIDTH 16777216 /* Default width for blob */ + +typedef struct st_net { +#if !defined(CHECK_EMBEDDED_DIFFERENCES) || !defined(EMBEDDED_LIBRARY) + Vio *vio; + unsigned char *buff,*buff_end,*write_pos,*read_pos; + my_socket fd; /* For Perl DBI/dbd */ + /* + The following variable is set if we are doing several queries in one + command ( as in LOAD TABLE ... FROM MASTER ), + and do not want to confuse the client with OK at the wrong time + */ + unsigned long remain_in_buf,length, buf_length, where_b; + unsigned long max_packet,max_packet_size; + unsigned int pkt_nr,compress_pkt_nr; + unsigned int write_timeout, read_timeout, retry_count; + int fcntl; + unsigned int *return_status; + unsigned char reading_or_writing; + char save_char; + my_bool unused0; /* Please remove with the next incompatible ABI change. */ + my_bool unused; /* Please remove with the next incompatible ABI change */ + my_bool compress; + my_bool unused1; /* Please remove with the next incompatible ABI change. */ + /* + Pointer to query object in query cache, do not equal NULL (0) for + queries in cache that have not stored its results yet + */ +#endif + /* + 'query_cache_query' should be accessed only via query cache + functions and methods to maintain proper locking. + */ + unsigned char *query_cache_query; + unsigned int last_errno; + unsigned char error; + my_bool unused2; /* Please remove with the next incompatible ABI change. */ + my_bool return_errno; + /** Client library error message buffer. Actually belongs to struct MYSQL. */ + char last_error[MYSQL_ERRMSG_SIZE]; + /** Client library sqlstate buffer. Set along with the error message. */ + char sqlstate[SQLSTATE_LENGTH+1]; + void *extension; +#if defined(MYSQL_SERVER) && !defined(EMBEDDED_LIBRARY) + /* + Controls whether a big packet should be skipped. + + Initially set to FALSE by default. Unauthenticated sessions must have + this set to FALSE so that the server can't be tricked to read packets + indefinitely. + */ + my_bool skip_big_packet; +#endif +} NET; + + +#define packet_error (~(unsigned long) 0) + +enum enum_field_types { MYSQL_TYPE_DECIMAL, MYSQL_TYPE_TINY, + MYSQL_TYPE_SHORT, MYSQL_TYPE_LONG, + MYSQL_TYPE_FLOAT, MYSQL_TYPE_DOUBLE, + MYSQL_TYPE_NULL, MYSQL_TYPE_TIMESTAMP, + MYSQL_TYPE_LONGLONG,MYSQL_TYPE_INT24, + MYSQL_TYPE_DATE, MYSQL_TYPE_TIME, + MYSQL_TYPE_DATETIME, MYSQL_TYPE_YEAR, + MYSQL_TYPE_NEWDATE, MYSQL_TYPE_VARCHAR, + MYSQL_TYPE_BIT, + MYSQL_TYPE_NEWDECIMAL=246, + MYSQL_TYPE_ENUM=247, + MYSQL_TYPE_SET=248, + MYSQL_TYPE_TINY_BLOB=249, + MYSQL_TYPE_MEDIUM_BLOB=250, + MYSQL_TYPE_LONG_BLOB=251, + MYSQL_TYPE_BLOB=252, + MYSQL_TYPE_VAR_STRING=253, + MYSQL_TYPE_STRING=254, + MYSQL_TYPE_GEOMETRY=255 + +}; + +/* For backward compatibility */ +#define CLIENT_MULTI_QUERIES CLIENT_MULTI_STATEMENTS +#define FIELD_TYPE_DECIMAL MYSQL_TYPE_DECIMAL +#define FIELD_TYPE_NEWDECIMAL MYSQL_TYPE_NEWDECIMAL +#define FIELD_TYPE_TINY MYSQL_TYPE_TINY +#define FIELD_TYPE_SHORT MYSQL_TYPE_SHORT +#define FIELD_TYPE_LONG MYSQL_TYPE_LONG +#define FIELD_TYPE_FLOAT MYSQL_TYPE_FLOAT +#define FIELD_TYPE_DOUBLE MYSQL_TYPE_DOUBLE +#define FIELD_TYPE_NULL MYSQL_TYPE_NULL +#define FIELD_TYPE_TIMESTAMP MYSQL_TYPE_TIMESTAMP +#define FIELD_TYPE_LONGLONG MYSQL_TYPE_LONGLONG +#define FIELD_TYPE_INT24 MYSQL_TYPE_INT24 +#define FIELD_TYPE_DATE MYSQL_TYPE_DATE +#define FIELD_TYPE_TIME MYSQL_TYPE_TIME +#define FIELD_TYPE_DATETIME MYSQL_TYPE_DATETIME +#define FIELD_TYPE_YEAR MYSQL_TYPE_YEAR +#define FIELD_TYPE_NEWDATE MYSQL_TYPE_NEWDATE +#define FIELD_TYPE_ENUM MYSQL_TYPE_ENUM +#define FIELD_TYPE_SET MYSQL_TYPE_SET +#define FIELD_TYPE_TINY_BLOB MYSQL_TYPE_TINY_BLOB +#define FIELD_TYPE_MEDIUM_BLOB MYSQL_TYPE_MEDIUM_BLOB +#define FIELD_TYPE_LONG_BLOB MYSQL_TYPE_LONG_BLOB +#define FIELD_TYPE_BLOB MYSQL_TYPE_BLOB +#define FIELD_TYPE_VAR_STRING MYSQL_TYPE_VAR_STRING +#define FIELD_TYPE_STRING MYSQL_TYPE_STRING +#define FIELD_TYPE_CHAR MYSQL_TYPE_TINY +#define FIELD_TYPE_INTERVAL MYSQL_TYPE_ENUM +#define FIELD_TYPE_GEOMETRY MYSQL_TYPE_GEOMETRY +#define FIELD_TYPE_BIT MYSQL_TYPE_BIT + + +/* Shutdown/kill enums and constants */ + +/* Bits for THD::killable. */ +#define MYSQL_SHUTDOWN_KILLABLE_CONNECT (unsigned char)(1 << 0) +#define MYSQL_SHUTDOWN_KILLABLE_TRANS (unsigned char)(1 << 1) +#define MYSQL_SHUTDOWN_KILLABLE_LOCK_TABLE (unsigned char)(1 << 2) +#define MYSQL_SHUTDOWN_KILLABLE_UPDATE (unsigned char)(1 << 3) + +enum mysql_enum_shutdown_level { + /* + We want levels to be in growing order of hardness (because we use number + comparisons). Note that DEFAULT does not respect the growing property, but + it's ok. + */ + SHUTDOWN_DEFAULT = 0, + /* wait for existing connections to finish */ + SHUTDOWN_WAIT_CONNECTIONS= MYSQL_SHUTDOWN_KILLABLE_CONNECT, + /* wait for existing trans to finish */ + SHUTDOWN_WAIT_TRANSACTIONS= MYSQL_SHUTDOWN_KILLABLE_TRANS, + /* wait for existing updates to finish (=> no partial MyISAM update) */ + SHUTDOWN_WAIT_UPDATES= MYSQL_SHUTDOWN_KILLABLE_UPDATE, + /* flush InnoDB buffers and other storage engines' buffers*/ + SHUTDOWN_WAIT_ALL_BUFFERS= (MYSQL_SHUTDOWN_KILLABLE_UPDATE << 1), + /* don't flush InnoDB buffers, flush other storage engines' buffers*/ + SHUTDOWN_WAIT_CRITICAL_BUFFERS= (MYSQL_SHUTDOWN_KILLABLE_UPDATE << 1) + 1, + /* Now the 2 levels of the KILL command */ +#if MYSQL_VERSION_ID >= 50000 + KILL_QUERY= 254, +#endif + KILL_CONNECTION= 255 +}; + + +enum enum_cursor_type +{ + CURSOR_TYPE_NO_CURSOR= 0, + CURSOR_TYPE_READ_ONLY= 1, + CURSOR_TYPE_FOR_UPDATE= 2, + CURSOR_TYPE_SCROLLABLE= 4 +}; + + +/* options for mysql_set_option */ +enum enum_mysql_set_option +{ + MYSQL_OPTION_MULTI_STATEMENTS_ON, + MYSQL_OPTION_MULTI_STATEMENTS_OFF +}; + +#define net_new_transaction(net) ((net)->pkt_nr=0) + +#ifdef __cplusplus +extern "C" { +#endif + +my_bool my_net_init(NET *net, Vio* vio); +void my_net_local_init(NET *net); +void net_end(NET *net); + void net_clear(NET *net, my_bool clear_buffer); +my_bool net_realloc(NET *net, size_t length); +my_bool net_flush(NET *net); +my_bool my_net_write(NET *net,const unsigned char *packet, size_t len); +my_bool net_write_command(NET *net,unsigned char command, + const unsigned char *header, size_t head_len, + const unsigned char *packet, size_t len); +int net_real_write(NET *net,const unsigned char *packet, size_t len); +unsigned long my_net_read(NET *net); + +#ifdef _global_h +void my_net_set_write_timeout(NET *net, uint timeout); +void my_net_set_read_timeout(NET *net, uint timeout); +#endif + +/* + The following function is not meant for normal usage + Currently it's used internally by manager.c +*/ +struct sockaddr; +int my_connect(my_socket s, const struct sockaddr *name, unsigned int namelen, + unsigned int timeout); + +struct rand_struct { + unsigned long seed1,seed2,max_value; + double max_value_dbl; +}; + +#ifdef __cplusplus +} +#endif + + /* The following is for user defined functions */ + +enum Item_result {STRING_RESULT=0, REAL_RESULT, INT_RESULT, ROW_RESULT, + DECIMAL_RESULT}; + +typedef struct st_udf_args +{ + unsigned int arg_count; /* Number of arguments */ + enum Item_result *arg_type; /* Pointer to item_results */ + char **args; /* Pointer to argument */ + unsigned long *lengths; /* Length of string arguments */ + char *maybe_null; /* Set to 1 for all maybe_null args */ + char **attributes; /* Pointer to attribute name */ + unsigned long *attribute_lengths; /* Length of attribute arguments */ + void *extension; +} UDF_ARGS; + + /* This holds information about the result */ + +typedef struct st_udf_init +{ + my_bool maybe_null; /* 1 if function can return NULL */ + unsigned int decimals; /* for real functions */ + unsigned long max_length; /* For string functions */ + char *ptr; /* free pointer for function data */ + my_bool const_item; /* 1 if function always returns the same value */ + void *extension; +} UDF_INIT; +/* + TODO: add a notion for determinism of the UDF. + See Item_udf_func::update_used_tables () +*/ + + /* Constants when using compression */ +#define NET_HEADER_SIZE 4 /* standard header size */ +#define COMP_HEADER_SIZE 3 /* compression header extra size */ + + /* Prototypes to password functions */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + These functions are used for authentication by client and server and + implemented in sql/password.c +*/ + +void randominit(struct rand_struct *, unsigned long seed1, + unsigned long seed2); +double my_rnd(struct rand_struct *); +void create_random_string(char *to, unsigned int length, struct rand_struct *rand_st); + +void hash_password(unsigned long *to, const char *password, unsigned int password_len); +void make_scrambled_password_323(char *to, const char *password); +void scramble_323(char *to, const char *message, const char *password); +my_bool check_scramble_323(const char *, const char *message, + unsigned long *salt); +void get_salt_from_password_323(unsigned long *res, const char *password); +void make_password_from_salt_323(char *to, const unsigned long *salt); + +void make_scrambled_password(char *to, const char *password); +void scramble(char *to, const char *message, const char *password); +my_bool check_scramble(const char *reply, const char *message, + const unsigned char *hash_stage2); +void get_salt_from_password(unsigned char *res, const char *password); +void make_password_from_salt(char *to, const unsigned char *hash_stage2); +char *octet2hex(char *to, const char *str, unsigned int len); + +/* end of password.c */ + +char *get_tty_password(const char *opt_message); +const char *mysql_errno_to_sqlstate(unsigned int mysql_errno); + +/* Some other useful functions */ + +my_bool my_thread_init(void); +void my_thread_end(void); + +#ifdef _global_h +ulong STDCALL net_field_length(uchar **packet); +my_ulonglong net_field_length_ll(uchar **packet); +uchar *net_store_length(uchar *pkg, ulonglong length); +#endif + +#ifdef __cplusplus +} +#endif + +#define NULL_LENGTH ((unsigned long) ~0) /* For net_store_length */ +#define MYSQL_STMT_HEADER 4 +#define MYSQL_LONG_DATA_HEADER 6 + +#endif diff --git a/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_embed.h b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_embed.h new file mode 100644 index 00000000..e3318864 --- /dev/null +++ b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_embed.h @@ -0,0 +1,31 @@ +/* + Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* Defines that are unique to the embedded version of MySQL */ + +#ifdef EMBEDDED_LIBRARY + +/* Things we don't need in the embedded version of MySQL */ +/* TODO HF add #undef HAVE_VIO if we don't want client in embedded library */ + +#undef HAVE_OPENSSL +#undef HAVE_SMEM /* No shared memory */ +#undef HAVE_NDBCLUSTER_DB /* No NDB cluster */ + +#define DONT_USE_RAID + +#endif /* EMBEDDED_LIBRARY */ diff --git a/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_time.h b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_time.h new file mode 100644 index 00000000..0a3f17a8 --- /dev/null +++ b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_time.h @@ -0,0 +1,55 @@ +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef _mysql_time_h_ +#define _mysql_time_h_ + +/* + Time declarations shared between the server and client API: + you should not add anything to this header unless it's used + (and hence should be visible) in mysql.h. + If you're looking for a place to add new time-related declaration, + it's most likely my_time.h. See also "C API Handling of Date + and Time Values" chapter in documentation. +*/ + +enum enum_mysql_timestamp_type +{ + MYSQL_TIMESTAMP_NONE= -2, MYSQL_TIMESTAMP_ERROR= -1, + MYSQL_TIMESTAMP_DATE= 0, MYSQL_TIMESTAMP_DATETIME= 1, MYSQL_TIMESTAMP_TIME= 2 +}; + + +/* + Structure which is used to represent datetime values inside MySQL. + + We assume that values in this structure are normalized, i.e. year <= 9999, + month <= 12, day <= 31, hour <= 23, hour <= 59, hour <= 59. Many functions + in server such as my_system_gmt_sec() or make_time() family of functions + rely on this (actually now usage of make_*() family relies on a bit weaker + restriction). Also functions that produce MYSQL_TIME as result ensure this. + There is one exception to this rule though if this structure holds time + value (time_type == MYSQL_TIMESTAMP_TIME) days and hour member can hold + bigger values. +*/ +typedef struct st_mysql_time +{ + unsigned int year, month, day, hour, minute, second; + unsigned long second_part; + my_bool neg; + enum enum_mysql_timestamp_type time_type; +} MYSQL_TIME; + +#endif /* _mysql_time_h_ */ diff --git a/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_version.h b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_version.h new file mode 100644 index 00000000..dc8c5b3f --- /dev/null +++ b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_version.h @@ -0,0 +1,30 @@ +/* Copyright Abandoned 1996, 1999, 2001 MySQL AB + This file is public domain and comes with NO WARRANTY of any kind */ + +/* Version numbers for protocol & mysqld */ + +#ifndef _mysql_version_h +#define _mysql_version_h +#ifdef _CUSTOMCONFIG_ +#include <custom_conf.h> +#else +#define PROTOCOL_VERSION 10 +#define MYSQL_SERVER_VERSION "5.1.61" +#define MYSQL_BASE_VERSION "mysqld-5.1" +#define MYSQL_SERVER_SUFFIX_DEF "" +#define FRM_VER 6 +#define MYSQL_VERSION_ID 50161 +#define MYSQL_PORT 3306 +#define MYSQL_PORT_DEFAULT 0 +#define MYSQL_UNIX_ADDR "/tmp/mysql.sock" +#define MYSQL_CONFIG_NAME "my" +#define MYSQL_COMPILATION_COMMENT "Source distribution" + +/* mysqld compile time options */ +#endif /* _CUSTOMCONFIG_ */ + +#ifndef LICENSE +#define LICENSE GPL +#endif /* LICENSE */ + +#endif /* _mysql_version_h */ diff --git a/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/typelib.h b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/typelib.h new file mode 100644 index 00000000..46106d1b --- /dev/null +++ b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/typelib.h @@ -0,0 +1,39 @@ +/* Copyright (C) 2000 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#ifndef _typelib_h +#define _typelib_h + +#include "my_alloc.h" + +typedef struct st_typelib { /* Different types saved here */ + unsigned int count; /* How many types */ + const char *name; /* Name of typelib */ + const char **type_names; + unsigned int *type_lengths; +} TYPELIB; + +extern my_ulonglong find_typeset(char *x, TYPELIB *typelib,int *error_position); +extern int find_type_or_exit(const char *x, TYPELIB *typelib, + const char *option); +extern int find_type(char *x, const TYPELIB *typelib, unsigned int full_name); +extern void make_type(char *to,unsigned int nr,TYPELIB *typelib); +extern const char *get_type(TYPELIB *typelib,unsigned int nr); +extern TYPELIB *copy_typelib(MEM_ROOT *root, TYPELIB *from); + +extern TYPELIB sql_protocol_typelib; + +#endif /* _typelib_h */ diff --git a/Frameworks/SPMySQLFramework/MySQL Client Libraries/lib/libmysqlclient.a b/Frameworks/SPMySQLFramework/MySQL Client Libraries/lib/libmysqlclient.a Binary files differnew file mode 100644 index 00000000..5b7d405a --- /dev/null +++ b/Frameworks/SPMySQLFramework/MySQL Client Libraries/lib/libmysqlclient.a diff --git a/Frameworks/SPMySQLFramework/Readme.txt b/Frameworks/SPMySQLFramework/Readme.txt new file mode 100644 index 00000000..de828890 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Readme.txt @@ -0,0 +1,44 @@ +The SPMySQL Framework is intended to provide a stable MySQL connection framework, with the ability to run text-based queries and rapidly retrieve result sets with conversion from MySQL data types to Cocoa objects. + +SPMySQL.framework has an interface loosely based around that provided by MCPKit by Serge Cohen and Bertrand Mansion (http://mysql-cocoa.sourceforge.net/), and in particular the heavily modified Sequel Pro version (http://www.sequelpro.com/). It is a full rewrite of the original framework, although it includes code from patches implementing the following Sequel Pro functionality, largely contributed by Hans-Jörg Bibiko, Stuart Connolly, Jakob Egger, and Rowan Beentje: + - Connection locking (Jakob et al) + - Ping & keepalive (Rowan et al) + - Query cancellation (Rowan et al) + - Delegate setup (Stuart et al) + - SSL support (Rowan et al) + - Connection checking (Rowan et al) + - Version state (Stuart et al) + - Maximum packet size control (Hans et al) + - Result multithreading and streaming (Rowan et al) + - Improved encoding support & switching (Rowan et al) + - Database structure; moved to inside the app (Hans et al) + - Query reattempts and error-handling approach (Rowan et al) + - Geometry result class (Hans et al) + - Connection proxy (Stuart et al) + + +INTEGRATION + +SPMySQL.framework can be added to your project as a standard Cocoa framework, or the entire project can be added as a subproject in Xcode. +To add as a subproject in Xcode: + 1) Add the SPMySQL framework's .xcodeproj to your current project + 2) Choose an existing target, Get Info, and under direct dependenies add a new dependency. Choose the SPMySQL.framework target from the sub-project + 3) Expand the subproject to see its child target - SPMySQL.framework. Drag this to the "Link Binary With Libraries" build phase of any targets using the framework. + 4) If you don't have a Copy Frameworks phase, add one; drag the SPMySQL.framework child target to this phase. + 5) In your build settings, add a User Header Search Path; make it a recursive path to the SPMySQL project folder location (for example ${PROJECT_DIR}/Frameworks/SPMySQLFramework). This should allow you to #include "SPMySQL.h" and have everything function. + +As a last resort jump onto IRC and join #sequel-pro on irc.freenode.net and any of the +developers will be more than happy to help you out. + + +LICENSE + +Copyright (c) 2012 Rowan Beentje (rowan.beent.je) and the Sequel Pro team. + +The SPMySQL framework is offered under the MIT license: + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file diff --git a/Frameworks/SPMySQLFramework/Resources/Info.plist b/Frameworks/SPMySQLFramework/Resources/Info.plist new file mode 100644 index 00000000..392b428f --- /dev/null +++ b/Frameworks/SPMySQLFramework/Resources/Info.plist @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIconFile</key> + <string></string> + <key>CFBundleIdentifier</key> + <string>com.yourcompany.${PRODUCT_NAME:rfc1034Identifier}</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundlePackageType</key> + <string>FMWK</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>SPDT</string> + <key>CFBundleVersion</key> + <string>1</string> + <key>NSPrincipalClass</key> + <string></string> +</dict> +</plist> diff --git a/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj new file mode 100644 index 00000000..2f8d300c --- /dev/null +++ b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj @@ -0,0 +1,695 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 580A331E14D75CF7000D6933 /* SPMySQLGeometryData.h in Headers */ = {isa = PBXBuildFile; fileRef = 580A331C14D75CF7000D6933 /* SPMySQLGeometryData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 580A331F14D75CF7000D6933 /* SPMySQLGeometryData.m in Sources */ = {isa = PBXBuildFile; fileRef = 580A331D14D75CF7000D6933 /* SPMySQLGeometryData.m */; }; + 58428E0014BA5FAE000F8438 /* SPMySQLConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = 58428DFE14BA5FAE000F8438 /* SPMySQLConnection.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 58428E0114BA5FAE000F8438 /* SPMySQLConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 58428DFF14BA5FAE000F8438 /* SPMySQLConnection.m */; }; + 5842929F14C34B36000F8438 /* my_alloc.h in Headers */ = {isa = PBXBuildFile; fileRef = 5842929414C34B36000F8438 /* my_alloc.h */; settings = {ATTRIBUTES = (); }; }; + 584292A014C34B36000F8438 /* my_list.h in Headers */ = {isa = PBXBuildFile; fileRef = 5842929514C34B36000F8438 /* my_list.h */; settings = {ATTRIBUTES = (); }; }; + 584292A114C34B36000F8438 /* mysql.h in Headers */ = {isa = PBXBuildFile; fileRef = 5842929614C34B36000F8438 /* mysql.h */; }; + 584292A214C34B36000F8438 /* mysql_com.h in Headers */ = {isa = PBXBuildFile; fileRef = 5842929714C34B36000F8438 /* mysql_com.h */; }; + 584292A314C34B36000F8438 /* mysql_embed.h in Headers */ = {isa = PBXBuildFile; fileRef = 5842929814C34B36000F8438 /* mysql_embed.h */; }; + 584292A414C34B36000F8438 /* mysql_time.h in Headers */ = {isa = PBXBuildFile; fileRef = 5842929914C34B36000F8438 /* mysql_time.h */; }; + 584292A514C34B36000F8438 /* mysql_version.h in Headers */ = {isa = PBXBuildFile; fileRef = 5842929A14C34B36000F8438 /* mysql_version.h */; }; + 584292A614C34B36000F8438 /* typelib.h in Headers */ = {isa = PBXBuildFile; fileRef = 5842929B14C34B36000F8438 /* typelib.h */; }; + 584292A714C34B36000F8438 /* libmysqlclient.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5842929D14C34B36000F8438 /* libmysqlclient.a */; }; + 584294E414CB8002000F8438 /* SPMySQLConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 584294E314CB8002000F8438 /* SPMySQLConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 584294F014CB8002000F8438 /* Ping & KeepAlive.h in Headers */ = {isa = PBXBuildFile; fileRef = 584294EE14CB8002000F8438 /* Ping & KeepAlive.h */; }; + 584294F114CB8002000F8438 /* Ping & KeepAlive.m in Sources */ = {isa = PBXBuildFile; fileRef = 584294EF14CB8002000F8438 /* Ping & KeepAlive.m */; }; + 584294F614CB8002000F8438 /* Querying & Preparation.h in Headers */ = {isa = PBXBuildFile; fileRef = 584294F414CB8002000F8438 /* Querying & Preparation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 584294FA14CB8002000F8438 /* Encoding.h in Headers */ = {isa = PBXBuildFile; fileRef = 584294F814CB8002000F8438 /* Encoding.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 584294FB14CB8002000F8438 /* Encoding.m in Sources */ = {isa = PBXBuildFile; fileRef = 584294F914CB8002000F8438 /* Encoding.m */; }; + 584294FE14CB8002000F8438 /* Server Info.h in Headers */ = {isa = PBXBuildFile; fileRef = 584294FC14CB8002000F8438 /* Server Info.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 584294FF14CB8002000F8438 /* Server Info.m in Sources */ = {isa = PBXBuildFile; fileRef = 584294FD14CB8002000F8438 /* Server Info.m */; }; + 584D812E15057ECD00F24774 /* SPMySQLKeepAliveTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 584D812C15057ECD00F24774 /* SPMySQLKeepAliveTimer.h */; }; + 584D812F15057ECD00F24774 /* SPMySQLKeepAliveTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 584D812D15057ECD00F24774 /* SPMySQLKeepAliveTimer.m */; }; + 584D82551509775000F24774 /* Copying.h in Headers */ = {isa = PBXBuildFile; fileRef = 584D82531509775000F24774 /* Copying.h */; }; + 584D82561509775000F24774 /* Copying.m in Sources */ = {isa = PBXBuildFile; fileRef = 584D82541509775000F24774 /* Copying.m */; }; + 586A99FB14F02E21007F82BF /* SPMySQLStreamingResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 586A99F914F02E21007F82BF /* SPMySQLStreamingResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 586A99FC14F02E21007F82BF /* SPMySQLStreamingResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 586A99FA14F02E21007F82BF /* SPMySQLStreamingResult.m */; }; + 586AA16714F30C5F007F82BF /* Convenience Methods.h in Headers */ = {isa = PBXBuildFile; fileRef = 586AA16514F30C5F007F82BF /* Convenience Methods.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 586AA16814F30C5F007F82BF /* Convenience Methods.m in Sources */ = {isa = PBXBuildFile; fileRef = 586AA16614F30C5F007F82BF /* Convenience Methods.m */; }; + 586AA81414F6C648007F82BF /* SPMySQLArrayAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 586AA81214F6C648007F82BF /* SPMySQLArrayAdditions.h */; }; + 5884127714CC63830078027F /* SPMySQL.h in Headers */ = {isa = PBXBuildFile; fileRef = 5884127614CC63830078027F /* SPMySQL.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 588412A814CC7A4D0078027F /* Locking.h in Headers */ = {isa = PBXBuildFile; fileRef = 588412A614CC7A4D0078027F /* Locking.h */; }; + 5884133C14CCEC6B0078027F /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5884133B14CCEC6B0078027F /* libz.dylib */; }; + 5884142714CCF5190078027F /* Conversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 5884142514CCF5190078027F /* Conversion.h */; }; + 5884142814CCF5190078027F /* Conversion.m in Sources */ = {isa = PBXBuildFile; fileRef = 5884142614CCF5190078027F /* Conversion.m */; }; + 588414BD14CE3B110078027F /* SPMySQLConnectionDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 588414BC14CE3B110078027F /* SPMySQLConnectionDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5884159414D1A6760078027F /* Locking.m in Sources */ = {isa = PBXBuildFile; fileRef = 588412A714CC7A4D0078027F /* Locking.m */; }; + 5884159514D1A6880078027F /* Querying & Preparation.m in Sources */ = {isa = PBXBuildFile; fileRef = 584294F514CB8002000F8438 /* Querying & Preparation.m */; }; + 5884165514D2306A0078027F /* SPMySQLResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 5884165314D2306A0078027F /* SPMySQLResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5884165614D2306A0078027F /* SPMySQLResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 5884165414D2306A0078027F /* SPMySQLResult.m */; }; + 58C006C814E0B18A00AC489A /* SPMySQLUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 58C006C714E0B18A00AC489A /* SPMySQLUtilities.h */; }; + 58C008CD14E2AC7D00AC489A /* SPMySQLConnectionProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 58C008CC14E2AC7D00AC489A /* SPMySQLConnectionProxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 58C009D514E31D3800AC489A /* SPMySQLStringAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 58C009D314E31D3800AC489A /* SPMySQLStringAdditions.h */; }; + 58C009D614E31D3800AC489A /* SPMySQLStringAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C009D414E31D3800AC489A /* SPMySQLStringAdditions.m */; }; + 58C00AA914E4869C00AC489A /* Max Packet Size.h in Headers */ = {isa = PBXBuildFile; fileRef = 58C00AA714E4869C00AC489A /* Max Packet Size.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 58C00AAA14E4869C00AC489A /* Max Packet Size.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C00AA814E4869C00AC489A /* Max Packet Size.m */; }; + 58C00AB514E4892E00AC489A /* Delegate & Proxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 58C00AB314E4892E00AC489A /* Delegate & Proxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 58C00AB614E4892E00AC489A /* Delegate & Proxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C00AB414E4892E00AC489A /* Delegate & Proxy.m */; }; + 58C00ADA14E4959A00AC489A /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58C00AD914E4959A00AC489A /* SystemConfiguration.framework */; }; + 58C00BD114E7459600AC489A /* Databases & Tables.h in Headers */ = {isa = PBXBuildFile; fileRef = 58C00BCF14E7459600AC489A /* Databases & Tables.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 58C00BD214E7459600AC489A /* Databases & Tables.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C00BD014E7459600AC489A /* Databases & Tables.m */; }; + 58C00CA514E845D800AC489A /* SPMySQL Private APIs.h in Headers */ = {isa = PBXBuildFile; fileRef = 58C00CA414E845D800AC489A /* SPMySQL Private APIs.h */; }; + 58C7C1E414DB6E4C00436315 /* SPMySQLFastStreamingResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 58C7C1E214DB6E4C00436315 /* SPMySQLFastStreamingResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 58C7C1E514DB6E4C00436315 /* SPMySQLFastStreamingResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C7C1E314DB6E4C00436315 /* SPMySQLFastStreamingResult.m */; }; + 58C7C1E814DB6E8600436315 /* Field Definitions.h in Headers */ = {isa = PBXBuildFile; fileRef = 58C7C1E614DB6E8600436315 /* Field Definitions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 58C7C1E914DB6E8600436315 /* Field Definitions.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C7C1E714DB6E8600436315 /* Field Definitions.m */; }; + 8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C1666FE841158C02AAC07 /* InfoPlist.strings */; }; + 8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0867D69BFE84028FC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; }; + 0867D6A5FE840307C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; }; + 089C1667FE841158C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; }; + 32DBCF5E0370ADEE00C91783 /* SPMySQLFramework_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLFramework_Prefix.pch; path = Source/SPMySQLFramework_Prefix.pch; sourceTree = "<group>"; }; + 580A331C14D75CF7000D6933 /* SPMySQLGeometryData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLGeometryData.h; path = Source/SPMySQLGeometryData.h; sourceTree = "<group>"; }; + 580A331D14D75CF7000D6933 /* SPMySQLGeometryData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPMySQLGeometryData.m; path = Source/SPMySQLGeometryData.m; sourceTree = "<group>"; }; + 58428DF614BA5A13000F8438 /* build-mysql-client.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "build-mysql-client.sh"; sourceTree = "<group>"; }; + 58428DFE14BA5FAE000F8438 /* SPMySQLConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLConnection.h; path = Source/SPMySQLConnection.h; sourceTree = "<group>"; }; + 58428DFF14BA5FAE000F8438 /* SPMySQLConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPMySQLConnection.m; path = Source/SPMySQLConnection.m; sourceTree = "<group>"; }; + 5842929414C34B36000F8438 /* my_alloc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = my_alloc.h; sourceTree = "<group>"; }; + 5842929514C34B36000F8438 /* my_list.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = my_list.h; sourceTree = "<group>"; }; + 5842929614C34B36000F8438 /* mysql.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mysql.h; sourceTree = "<group>"; }; + 5842929714C34B36000F8438 /* mysql_com.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mysql_com.h; sourceTree = "<group>"; }; + 5842929814C34B36000F8438 /* mysql_embed.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mysql_embed.h; sourceTree = "<group>"; }; + 5842929914C34B36000F8438 /* mysql_time.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mysql_time.h; sourceTree = "<group>"; }; + 5842929A14C34B36000F8438 /* mysql_version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mysql_version.h; sourceTree = "<group>"; }; + 5842929B14C34B36000F8438 /* typelib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = typelib.h; sourceTree = "<group>"; }; + 5842929D14C34B36000F8438 /* libmysqlclient.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libmysqlclient.a; sourceTree = "<group>"; }; + 584294E314CB8002000F8438 /* SPMySQLConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLConstants.h; path = Source/SPMySQLConstants.h; sourceTree = "<group>"; }; + 584294EE14CB8002000F8438 /* Ping & KeepAlive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Ping & KeepAlive.h"; path = "Source/SPMySQLConnection Categories/Ping & KeepAlive.h"; sourceTree = "<group>"; }; + 584294EF14CB8002000F8438 /* Ping & KeepAlive.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "Ping & KeepAlive.m"; path = "Source/SPMySQLConnection Categories/Ping & KeepAlive.m"; sourceTree = "<group>"; }; + 584294F414CB8002000F8438 /* Querying & Preparation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Querying & Preparation.h"; path = "Source/SPMySQLConnection Categories/Querying & Preparation.h"; sourceTree = "<group>"; }; + 584294F514CB8002000F8438 /* Querying & Preparation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "Querying & Preparation.m"; path = "Source/SPMySQLConnection Categories/Querying & Preparation.m"; sourceTree = "<group>"; }; + 584294F814CB8002000F8438 /* Encoding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Encoding.h; path = "Source/SPMySQLConnection Categories/Encoding.h"; sourceTree = "<group>"; }; + 584294F914CB8002000F8438 /* Encoding.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Encoding.m; path = "Source/SPMySQLConnection Categories/Encoding.m"; sourceTree = "<group>"; }; + 584294FC14CB8002000F8438 /* Server Info.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Server Info.h"; path = "Source/SPMySQLConnection Categories/Server Info.h"; sourceTree = "<group>"; }; + 584294FD14CB8002000F8438 /* Server Info.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "Server Info.m"; path = "Source/SPMySQLConnection Categories/Server Info.m"; sourceTree = "<group>"; }; + 584D812C15057ECD00F24774 /* SPMySQLKeepAliveTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLKeepAliveTimer.h; path = Source/SPMySQLKeepAliveTimer.h; sourceTree = "<group>"; }; + 584D812D15057ECD00F24774 /* SPMySQLKeepAliveTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPMySQLKeepAliveTimer.m; path = Source/SPMySQLKeepAliveTimer.m; sourceTree = "<group>"; }; + 584D82531509775000F24774 /* Copying.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Copying.h; path = "Source/SPMySQLConnection Categories/Copying.h"; sourceTree = "<group>"; }; + 584D82541509775000F24774 /* Copying.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Copying.m; path = "Source/SPMySQLConnection Categories/Copying.m"; sourceTree = "<group>"; }; + 586A99F914F02E21007F82BF /* SPMySQLStreamingResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLStreamingResult.h; path = Source/SPMySQLStreamingResult.h; sourceTree = "<group>"; }; + 586A99FA14F02E21007F82BF /* SPMySQLStreamingResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPMySQLStreamingResult.m; path = Source/SPMySQLStreamingResult.m; sourceTree = "<group>"; }; + 586AA0E714F1CEC8007F82BF /* Readme.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Readme.txt; sourceTree = "<group>"; }; + 586AA16514F30C5F007F82BF /* Convenience Methods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Convenience Methods.h"; path = "Source/SPMySQLResult Categories/Convenience Methods.h"; sourceTree = "<group>"; }; + 586AA16614F30C5F007F82BF /* Convenience Methods.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "Convenience Methods.m"; path = "Source/SPMySQLResult Categories/Convenience Methods.m"; sourceTree = "<group>"; }; + 586AA81214F6C648007F82BF /* SPMySQLArrayAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLArrayAdditions.h; path = Source/SPMySQLArrayAdditions.h; sourceTree = "<group>"; }; + 5884127614CC63830078027F /* SPMySQL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQL.h; path = Source/SPMySQL.h; sourceTree = "<group>"; }; + 588412A614CC7A4D0078027F /* Locking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Locking.h; path = "Source/SPMySQLConnection Categories/Locking.h"; sourceTree = "<group>"; }; + 588412A714CC7A4D0078027F /* Locking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Locking.m; path = "Source/SPMySQLConnection Categories/Locking.m"; sourceTree = "<group>"; }; + 5884133B14CCEC6B0078027F /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; + 5884142514CCF5190078027F /* Conversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Conversion.h; path = "Source/SPMySQLConnection Categories/Conversion.h"; sourceTree = "<group>"; }; + 5884142614CCF5190078027F /* Conversion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Conversion.m; path = "Source/SPMySQLConnection Categories/Conversion.m"; sourceTree = "<group>"; }; + 588414BC14CE3B110078027F /* SPMySQLConnectionDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLConnectionDelegate.h; path = Source/SPMySQLConnectionDelegate.h; sourceTree = "<group>"; }; + 5884165314D2306A0078027F /* SPMySQLResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLResult.h; path = Source/SPMySQLResult.h; sourceTree = "<group>"; }; + 5884165414D2306A0078027F /* SPMySQLResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPMySQLResult.m; path = Source/SPMySQLResult.m; sourceTree = "<group>"; }; + 58C006C714E0B18A00AC489A /* SPMySQLUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLUtilities.h; path = Source/SPMySQLUtilities.h; sourceTree = "<group>"; }; + 58C008CC14E2AC7D00AC489A /* SPMySQLConnectionProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLConnectionProxy.h; path = Source/SPMySQLConnectionProxy.h; sourceTree = "<group>"; }; + 58C009D314E31D3800AC489A /* SPMySQLStringAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLStringAdditions.h; path = Source/SPMySQLStringAdditions.h; sourceTree = "<group>"; }; + 58C009D414E31D3800AC489A /* SPMySQLStringAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPMySQLStringAdditions.m; path = Source/SPMySQLStringAdditions.m; sourceTree = "<group>"; }; + 58C00AA714E4869C00AC489A /* Max Packet Size.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Max Packet Size.h"; path = "Source/SPMySQLConnection Categories/Max Packet Size.h"; sourceTree = "<group>"; }; + 58C00AA814E4869C00AC489A /* Max Packet Size.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "Max Packet Size.m"; path = "Source/SPMySQLConnection Categories/Max Packet Size.m"; sourceTree = "<group>"; }; + 58C00AB314E4892E00AC489A /* Delegate & Proxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Delegate & Proxy.h"; path = "Source/SPMySQLConnection Categories/Delegate & Proxy.h"; sourceTree = "<group>"; }; + 58C00AB414E4892E00AC489A /* Delegate & Proxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "Delegate & Proxy.m"; path = "Source/SPMySQLConnection Categories/Delegate & Proxy.m"; sourceTree = "<group>"; }; + 58C00AD914E4959A00AC489A /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + 58C00BCF14E7459600AC489A /* Databases & Tables.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Databases & Tables.h"; path = "Source/SPMySQLConnection Categories/Databases & Tables.h"; sourceTree = "<group>"; }; + 58C00BD014E7459600AC489A /* Databases & Tables.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "Databases & Tables.m"; path = "Source/SPMySQLConnection Categories/Databases & Tables.m"; sourceTree = "<group>"; }; + 58C00CA414E845D800AC489A /* SPMySQL Private APIs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SPMySQL Private APIs.h"; path = "Source/SPMySQL Private APIs.h"; sourceTree = "<group>"; }; + 58C7C1E214DB6E4C00436315 /* SPMySQLFastStreamingResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLFastStreamingResult.h; path = Source/SPMySQLFastStreamingResult.h; sourceTree = "<group>"; }; + 58C7C1E314DB6E4C00436315 /* SPMySQLFastStreamingResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPMySQLFastStreamingResult.m; path = Source/SPMySQLFastStreamingResult.m; sourceTree = "<group>"; }; + 58C7C1E614DB6E8600436315 /* Field Definitions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Field Definitions.h"; path = "Source/SPMySQLResult Categories/Field Definitions.h"; sourceTree = "<group>"; }; + 58C7C1E714DB6E8600436315 /* Field Definitions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "Field Definitions.m"; path = "Source/SPMySQLResult Categories/Field Definitions.m"; sourceTree = "<group>"; }; + 8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Resources/Info.plist; sourceTree = "<group>"; }; + 8DC2EF5B0486A6940098B216 /* SPMySQL.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SPMySQL.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D2F7E79907B2D74100F64583 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8DC2EF560486A6940098B216 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */, + 584292A714C34B36000F8438 /* libmysqlclient.a in Frameworks */, + 5884133C14CCEC6B0078027F /* libz.dylib in Frameworks */, + 58C00ADA14E4959A00AC489A /* SystemConfiguration.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 034768DFFF38A50411DB9C8B /* Products */ = { + isa = PBXGroup; + children = ( + 8DC2EF5B0486A6940098B216 /* SPMySQL.framework */, + ); + name = Products; + sourceTree = "<group>"; + }; + 0867D691FE84028FC02AAC07 /* SPMySQLFramework */ = { + isa = PBXGroup; + children = ( + 586AA0E714F1CEC8007F82BF /* Readme.txt */, + 5884127614CC63830078027F /* SPMySQL.h */, + 58C0077714E1DFFF00AC489A /* Protocols */, + 08FB77AEFE84172EC02AAC07 /* Classes */, + 58C009D214E31D1300AC489A /* Category Additions */, + 32C88DFF0371C24200C91783 /* Other Sources */, + 089C1665FE841158C02AAC07 /* Resources */, + 58428DF514BA5A03000F8438 /* Scripts */, + 0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */, + 034768DFFF38A50411DB9C8B /* Products */, + ); + name = SPMySQLFramework; + sourceTree = "<group>"; + }; + 0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */ = { + isa = PBXGroup; + children = ( + 1058C7B0FEA5585E11CA2CBB /* Linked Frameworks */, + 1058C7B2FEA5585E11CA2CBB /* Other Frameworks */, + ); + name = "External Frameworks and Libraries"; + sourceTree = "<group>"; + }; + 089C1665FE841158C02AAC07 /* Resources */ = { + isa = PBXGroup; + children = ( + 5842929214C34B36000F8438 /* MySQL Client Libraries */, + 8DC2EF5A0486A6940098B216 /* Info.plist */, + 089C1666FE841158C02AAC07 /* InfoPlist.strings */, + ); + name = Resources; + sourceTree = "<group>"; + }; + 08FB77AEFE84172EC02AAC07 /* Classes */ = { + isa = PBXGroup; + children = ( + 58C00CA414E845D800AC489A /* SPMySQL Private APIs.h */, + 58428DFE14BA5FAE000F8438 /* SPMySQLConnection.h */, + 58428DFF14BA5FAE000F8438 /* SPMySQLConnection.m */, + 584294EB14CB8002000F8438 /* Connection Categories */, + 5884165314D2306A0078027F /* SPMySQLResult.h */, + 5884165414D2306A0078027F /* SPMySQLResult.m */, + 586A99F914F02E21007F82BF /* SPMySQLStreamingResult.h */, + 586A99FA14F02E21007F82BF /* SPMySQLStreamingResult.m */, + 58C7C1E214DB6E4C00436315 /* SPMySQLFastStreamingResult.h */, + 58C7C1E314DB6E4C00436315 /* SPMySQLFastStreamingResult.m */, + 58C7C1E114DB6E3000436315 /* Result Categories */, + 580A331B14D75CCF000D6933 /* Result types */, + 584D812C15057ECD00F24774 /* SPMySQLKeepAliveTimer.h */, + 584D812D15057ECD00F24774 /* SPMySQLKeepAliveTimer.m */, + ); + name = Classes; + sourceTree = "<group>"; + }; + 1058C7B0FEA5585E11CA2CBB /* Linked Frameworks */ = { + isa = PBXGroup; + children = ( + 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */, + 5884133B14CCEC6B0078027F /* libz.dylib */, + ); + name = "Linked Frameworks"; + sourceTree = "<group>"; + }; + 1058C7B2FEA5585E11CA2CBB /* Other Frameworks */ = { + isa = PBXGroup; + children = ( + 0867D6A5FE840307C02AAC07 /* AppKit.framework */, + D2F7E79907B2D74100F64583 /* CoreData.framework */, + 0867D69BFE84028FC02AAC07 /* Foundation.framework */, + 58C00AD914E4959A00AC489A /* SystemConfiguration.framework */, + ); + name = "Other Frameworks"; + sourceTree = "<group>"; + }; + 32C88DFF0371C24200C91783 /* Other Sources */ = { + isa = PBXGroup; + children = ( + 584294E314CB8002000F8438 /* SPMySQLConstants.h */, + 58C006C714E0B18A00AC489A /* SPMySQLUtilities.h */, + 32DBCF5E0370ADEE00C91783 /* SPMySQLFramework_Prefix.pch */, + ); + name = "Other Sources"; + sourceTree = "<group>"; + }; + 580A331B14D75CCF000D6933 /* Result types */ = { + isa = PBXGroup; + children = ( + 580A331C14D75CF7000D6933 /* SPMySQLGeometryData.h */, + 580A331D14D75CF7000D6933 /* SPMySQLGeometryData.m */, + ); + name = "Result types"; + sourceTree = "<group>"; + }; + 58428DF514BA5A03000F8438 /* Scripts */ = { + isa = PBXGroup; + children = ( + 58428DF614BA5A13000F8438 /* build-mysql-client.sh */, + ); + name = Scripts; + sourceTree = "<group>"; + }; + 5842929214C34B36000F8438 /* MySQL Client Libraries */ = { + isa = PBXGroup; + children = ( + 5842929314C34B36000F8438 /* include */, + 5842929C14C34B36000F8438 /* lib */, + ); + path = "MySQL Client Libraries"; + sourceTree = "<group>"; + }; + 5842929314C34B36000F8438 /* include */ = { + isa = PBXGroup; + children = ( + 5842929414C34B36000F8438 /* my_alloc.h */, + 5842929514C34B36000F8438 /* my_list.h */, + 5842929614C34B36000F8438 /* mysql.h */, + 5842929714C34B36000F8438 /* mysql_com.h */, + 5842929814C34B36000F8438 /* mysql_embed.h */, + 5842929914C34B36000F8438 /* mysql_time.h */, + 5842929A14C34B36000F8438 /* mysql_version.h */, + 5842929B14C34B36000F8438 /* typelib.h */, + ); + path = include; + sourceTree = "<group>"; + }; + 5842929C14C34B36000F8438 /* lib */ = { + isa = PBXGroup; + children = ( + 5842929D14C34B36000F8438 /* libmysqlclient.a */, + ); + path = lib; + sourceTree = "<group>"; + }; + 584294EB14CB8002000F8438 /* Connection Categories */ = { + isa = PBXGroup; + children = ( + 584D82531509775000F24774 /* Copying.h */, + 584D82541509775000F24774 /* Copying.m */, + 58C00AB314E4892E00AC489A /* Delegate & Proxy.h */, + 58C00AB414E4892E00AC489A /* Delegate & Proxy.m */, + 58C00BCF14E7459600AC489A /* Databases & Tables.h */, + 58C00BD014E7459600AC489A /* Databases & Tables.m */, + 584294F414CB8002000F8438 /* Querying & Preparation.h */, + 584294F514CB8002000F8438 /* Querying & Preparation.m */, + 584294F814CB8002000F8438 /* Encoding.h */, + 584294F914CB8002000F8438 /* Encoding.m */, + 584294FC14CB8002000F8438 /* Server Info.h */, + 584294FD14CB8002000F8438 /* Server Info.m */, + 58C00AA714E4869C00AC489A /* Max Packet Size.h */, + 58C00AA814E4869C00AC489A /* Max Packet Size.m */, + 5884142414CCF4E60078027F /* Private */, + ); + name = "Connection Categories"; + sourceTree = "<group>"; + }; + 5884142414CCF4E60078027F /* Private */ = { + isa = PBXGroup; + children = ( + 584294EE14CB8002000F8438 /* Ping & KeepAlive.h */, + 584294EF14CB8002000F8438 /* Ping & KeepAlive.m */, + 588412A614CC7A4D0078027F /* Locking.h */, + 588412A714CC7A4D0078027F /* Locking.m */, + 5884142514CCF5190078027F /* Conversion.h */, + 5884142614CCF5190078027F /* Conversion.m */, + ); + name = Private; + sourceTree = "<group>"; + }; + 58C0077714E1DFFF00AC489A /* Protocols */ = { + isa = PBXGroup; + children = ( + 588414BC14CE3B110078027F /* SPMySQLConnectionDelegate.h */, + 58C008CC14E2AC7D00AC489A /* SPMySQLConnectionProxy.h */, + ); + name = Protocols; + sourceTree = "<group>"; + }; + 58C009D214E31D1300AC489A /* Category Additions */ = { + isa = PBXGroup; + children = ( + 58C009D314E31D3800AC489A /* SPMySQLStringAdditions.h */, + 58C009D414E31D3800AC489A /* SPMySQLStringAdditions.m */, + 586AA81214F6C648007F82BF /* SPMySQLArrayAdditions.h */, + ); + name = "Category Additions"; + sourceTree = "<group>"; + }; + 58C7C1E114DB6E3000436315 /* Result Categories */ = { + isa = PBXGroup; + children = ( + 58C7C1E614DB6E8600436315 /* Field Definitions.h */, + 58C7C1E714DB6E8600436315 /* Field Definitions.m */, + 586AA16514F30C5F007F82BF /* Convenience Methods.h */, + 586AA16614F30C5F007F82BF /* Convenience Methods.m */, + ); + name = "Result Categories"; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 8DC2EF500486A6940098B216 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 58428E0014BA5FAE000F8438 /* SPMySQLConnection.h in Headers */, + 584294E414CB8002000F8438 /* SPMySQLConstants.h in Headers */, + 584294F614CB8002000F8438 /* Querying & Preparation.h in Headers */, + 584294F014CB8002000F8438 /* Ping & KeepAlive.h in Headers */, + 584294FA14CB8002000F8438 /* Encoding.h in Headers */, + 584294FE14CB8002000F8438 /* Server Info.h in Headers */, + 5842929F14C34B36000F8438 /* my_alloc.h in Headers */, + 584292A014C34B36000F8438 /* my_list.h in Headers */, + 584292A114C34B36000F8438 /* mysql.h in Headers */, + 584292A214C34B36000F8438 /* mysql_com.h in Headers */, + 584292A314C34B36000F8438 /* mysql_embed.h in Headers */, + 584292A414C34B36000F8438 /* mysql_time.h in Headers */, + 584292A514C34B36000F8438 /* mysql_version.h in Headers */, + 584292A614C34B36000F8438 /* typelib.h in Headers */, + 5884127714CC63830078027F /* SPMySQL.h in Headers */, + 588412A814CC7A4D0078027F /* Locking.h in Headers */, + 5884142714CCF5190078027F /* Conversion.h in Headers */, + 588414BD14CE3B110078027F /* SPMySQLConnectionDelegate.h in Headers */, + 5884165514D2306A0078027F /* SPMySQLResult.h in Headers */, + 580A331E14D75CF7000D6933 /* SPMySQLGeometryData.h in Headers */, + 58C7C1E414DB6E4C00436315 /* SPMySQLFastStreamingResult.h in Headers */, + 58C7C1E814DB6E8600436315 /* Field Definitions.h in Headers */, + 58C006C814E0B18A00AC489A /* SPMySQLUtilities.h in Headers */, + 58C008CD14E2AC7D00AC489A /* SPMySQLConnectionProxy.h in Headers */, + 58C009D514E31D3800AC489A /* SPMySQLStringAdditions.h in Headers */, + 58C00AA914E4869C00AC489A /* Max Packet Size.h in Headers */, + 58C00AB514E4892E00AC489A /* Delegate & Proxy.h in Headers */, + 58C00BD114E7459600AC489A /* Databases & Tables.h in Headers */, + 58C00CA514E845D800AC489A /* SPMySQL Private APIs.h in Headers */, + 586A99FB14F02E21007F82BF /* SPMySQLStreamingResult.h in Headers */, + 586AA16714F30C5F007F82BF /* Convenience Methods.h in Headers */, + 586AA81414F6C648007F82BF /* SPMySQLArrayAdditions.h in Headers */, + 584D812E15057ECD00F24774 /* SPMySQLKeepAliveTimer.h in Headers */, + 584D82551509775000F24774 /* Copying.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 8DC2EF4F0486A6940098B216 /* SPMySQL.framework */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "SPMySQL.framework" */; + buildPhases = ( + 8DC2EF500486A6940098B216 /* Headers */, + 8DC2EF520486A6940098B216 /* Resources */, + 8DC2EF540486A6940098B216 /* Sources */, + 8DC2EF560486A6940098B216 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SPMySQL.framework; + productInstallPath = "$(HOME)/Library/Frameworks"; + productName = SPMySQLFramework; + productReference = 8DC2EF5B0486A6940098B216 /* SPMySQL.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0867D690FE84028FC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "SPMySQLFramework" */; + compatibilityVersion = "Xcode 3.1"; + developmentRegion = English; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + ); + mainGroup = 0867D691FE84028FC02AAC07 /* SPMySQLFramework */; + productRefGroup = 034768DFFF38A50411DB9C8B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8DC2EF4F0486A6940098B216 /* SPMySQL.framework */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8DC2EF520486A6940098B216 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8DC2EF540486A6940098B216 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 58428E0114BA5FAE000F8438 /* SPMySQLConnection.m in Sources */, + 584294F114CB8002000F8438 /* Ping & KeepAlive.m in Sources */, + 584294FB14CB8002000F8438 /* Encoding.m in Sources */, + 584294FF14CB8002000F8438 /* Server Info.m in Sources */, + 5884142814CCF5190078027F /* Conversion.m in Sources */, + 5884159514D1A6880078027F /* Querying & Preparation.m in Sources */, + 5884159414D1A6760078027F /* Locking.m in Sources */, + 5884165614D2306A0078027F /* SPMySQLResult.m in Sources */, + 580A331F14D75CF7000D6933 /* SPMySQLGeometryData.m in Sources */, + 58C7C1E514DB6E4C00436315 /* SPMySQLFastStreamingResult.m in Sources */, + 58C7C1E914DB6E8600436315 /* Field Definitions.m in Sources */, + 58C009D614E31D3800AC489A /* SPMySQLStringAdditions.m in Sources */, + 58C00AAA14E4869C00AC489A /* Max Packet Size.m in Sources */, + 58C00AB614E4892E00AC489A /* Delegate & Proxy.m in Sources */, + 58C00BD214E7459600AC489A /* Databases & Tables.m in Sources */, + 586A99FC14F02E21007F82BF /* SPMySQLStreamingResult.m in Sources */, + 586AA16814F30C5F007F82BF /* Convenience Methods.m in Sources */, + 584D812F15057ECD00F24774 /* SPMySQLKeepAliveTimer.m in Sources */, + 584D82561509775000F24774 /* Copying.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 089C1666FE841158C02AAC07 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 089C1667FE841158C02AAC07 /* English */, + ); + name = InfoPlist.strings; + sourceTree = "<group>"; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 1DEB91AE08733DA50010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + DEAD_CODE_STRIPPING = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Source/SPMySQLFramework_Prefix.pch; + INFOPLIST_FILE = Resources/Info.plist; + INSTALL_PATH = "@executable_path/../Frameworks"; + LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/MySQL Client Libraries/lib\"", + ); + PRODUCT_NAME = SPMySQL; + WRAPPER_EXTENSION = framework; + }; + name = Debug; + }; + 1DEB91AF08733DA50010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + DEAD_CODE_STRIPPING = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Source/SPMySQLFramework_Prefix.pch; + INFOPLIST_FILE = Resources/Info.plist; + INSTALL_PATH = "@executable_path/../Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/MySQL Client Libraries/lib\"", + ); + PRODUCT_NAME = SPMySQL; + WRAPPER_EXTENSION = framework; + }; + name = Release; + }; + 1DEB91B208733DA50010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + i386, + ppc, + x86_64, + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_MISSING_PARENTHESES = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACH_O_TYPE = mh_dylib; + ONLY_ACTIVE_ARCH = YES; + PREBINDING = NO; + SDKROOT = macosx10.5; + VALID_ARCHS = "i386 ppc x86_64"; + }; + name = Debug; + }; + 1DEB91B308733DA50010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + i386, + ppc, + x86_64, + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_MISSING_PARENTHESES = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACH_O_TYPE = mh_dylib; + PREBINDING = NO; + SDKROOT = macosx10.5; + VALID_ARCHS = "i386 ppc x86_64"; + }; + name = Release; + }; + 586AA55214F5D599007F82BF /* Distribution */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + i386, + ppc, + x86_64, + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_MISSING_PARENTHESES = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACH_O_TYPE = mh_dylib; + PREBINDING = NO; + SDKROOT = macosx10.5; + VALID_ARCHS = "i386 ppc x86_64"; + }; + name = Distribution; + }; + 586AA55314F5D599007F82BF /* Distribution */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + DEAD_CODE_STRIPPING = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Source/SPMySQLFramework_Prefix.pch; + INFOPLIST_FILE = Resources/Info.plist; + INSTALL_PATH = "@executable_path/../Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/MySQL Client Libraries/lib\"", + ); + PRODUCT_NAME = SPMySQL; + WRAPPER_EXTENSION = framework; + }; + name = Distribution; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "SPMySQL.framework" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB91AE08733DA50010E9CD /* Debug */, + 1DEB91AF08733DA50010E9CD /* Release */, + 586AA55314F5D599007F82BF /* Distribution */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "SPMySQLFramework" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB91B208733DA50010E9CD /* Debug */, + 1DEB91B308733DA50010E9CD /* Release */, + 586AA55214F5D599007F82BF /* Distribution */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0867D690FE84028FC02AAC07 /* Project object */; +} diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h b/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h new file mode 100644 index 00000000..5772eb72 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h @@ -0,0 +1,110 @@ +// +// $Id$ +// +// SPMySQLConnection_PrivateAPI.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 12, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +/** + * A collection of Private APIs from the various categories, to simplify + * inclusion across the categories. + */ + +#import "Ping & KeepAlive.h" +#import "Locking.h" +#import "Conversion.h" + + +@interface SPMySQLConnection (PrivateAPI) + +- (MYSQL *)_makeRawMySQLConnectionWithEncoding:(NSString *)encodingName isMasterConnection:(BOOL)isMaster; +- (BOOL)_waitForNetworkConnectionWithTimeout:(double)timeoutSeconds; +- (void)_updateConnectionVariables; +- (void)_restoreConnectionVariables; +- (BOOL)_checkConnectionIfNecessary; + +@end + + +@interface SPMySQLConnection (Delegate_and_Proxy_Private_API) + +- (void)_proxyStateChange:(NSObject <SPMySQLConnectionProxy> *)aProxy; +- (SPMySQLConnectionLostDecision)_delegateDecisionForLostConnection; + +@end + + +@interface SPMySQLConnection (Databases_and_Tables_Private_API) + +- (BOOL)_storeAndAlterEncodingToUTF8IfRequired; + +@end + + +@interface SPMySQLConnection (Max_Packet_Size_Private_API) + +- (void)_updateMaxQuerySize; +- (void)_updateMaxQuerySizeEditability; +- (BOOL)_attemptMaxQuerySizeIncreaseTo:(NSUInteger)targetSize; +- (void)_restoreMaximumQuerySizeAfterQuery; + +@end + + +@interface SPMySQLConnection (Querying_and_Preparation_Private_API) + +- (void)_flushMultipleResultSets; +- (void)_updateLastErrorMessage:(NSString *)theErrorMessage; +- (void)_updateLastErrorID:(NSUInteger)theErrorID; + +@end + + +// SPMySQLResult Private API +@interface SPMySQLResult (Private_API) + +- (NSString *)_stringWithBytes:(const void *)bytes length:(NSUInteger)length; +- (void)_setQueryExecutionTime:(double)theExecutionTime; +- (id)_getObjectFromBytes:(char *)bytes ofLength:(NSUInteger)length fieldType:(unsigned int)fieldType fieldDefinitionIndex:(NSUInteger)fieldIndex; + +@end + +/** + * Set up a static function to allow fast calling of SPMySQLResult data conversion with cached selectors + */ +static inline id SPMySQLResultGetObject(SPMySQLResult* self, char* bytes, NSUInteger length, unsigned int fieldType, NSUInteger fieldIndex) +{ + typedef id (*SPMySQLResultGetObjectMethodPtr)(SPMySQLResult*, SEL, char*, NSUInteger, unsigned int, NSUInteger); + static SPMySQLResultGetObjectMethodPtr cachedMethodPointer; + static SEL cachedSelector; + + if (!cachedSelector) cachedSelector = @selector(_getObjectFromBytes:ofLength:fieldType:fieldDefinitionIndex:); + if (!cachedMethodPointer) cachedMethodPointer = (SPMySQLResultGetObjectMethodPtr)[self methodForSelector:cachedSelector]; + + return cachedMethodPointer(self, cachedSelector, bytes, length, fieldType, fieldIndex); +}
\ No newline at end of file diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQL.h b/Frameworks/SPMySQLFramework/Source/SPMySQL.h new file mode 100644 index 00000000..3c4c78f8 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQL.h @@ -0,0 +1,63 @@ +// +// $Id$ +// +// SPMySQL.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 22, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +@class SPMySQLConnection, SPMySQLResult, SPMySQLStreamingResult, SPMySQLFastStreamingResult; + +// Global include file for the framework. +// Constants +#import "SPMySQLConstants.h" + +// Required category additions +#import "SPMySQLStringAdditions.h" + +// MySQL Connection Delegate and Proxy protocols +#import "SPMySQLConnectionDelegate.h" +#import "SPMySQLConnectionProxy.h" + +// MySQL Connection class and public categories +#import "SPMySQLConnection.h" +#import "Delegate & Proxy.h" +#import "Databases & Tables.h" +#import "Max Packet Size.h" +#import "Querying & Preparation.h" +#import "Encoding.h" +#import "Server Info.h" + +// MySQL result set, streaming subclasses of same, and associated categories +#import "SPMySQLResult.h" +#import "SPMySQLStreamingResult.h" +#import "SPMySQLFastStreamingResult.h" +#import "Field Definitions.h" +#import "Convenience Methods.h" + +// Result data objects +#import "SPMySQLGeometryData.h" diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLArrayAdditions.h b/Frameworks/SPMySQLFramework/Source/SPMySQLArrayAdditions.h new file mode 100644 index 00000000..46c6d8e0 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLArrayAdditions.h @@ -0,0 +1,50 @@ +// +// $Id$ +// +// SPMySQLArrayAdditions.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 23, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +/** + * Set up a static function to allow fast mutable array insertion using + * cached selectors. + * At least in 10.7, inserting items into an array at a known point + * using NSMutableArray methods appears to be the fastest way of adding + * items to a CF/NSMutableArray. + */ +static inline void SPMySQLMutableArrayInsertObject(NSMutableArray *self, id anObject, NSUInteger anIndex) +{ + typedef id (*SPMySQLMutableArrayInsertObjectPtr)(NSMutableArray*, SEL, id, NSUInteger); + static SPMySQLMutableArrayInsertObjectPtr cachedMethodPointer; + static SEL cachedSelector; + + if (!cachedSelector) cachedSelector = @selector(insertObject:atIndex:); + if (!cachedMethodPointer) cachedMethodPointer = (SPMySQLMutableArrayInsertObjectPtr)[self methodForSelector:cachedSelector]; + + cachedMethodPointer(self, cachedSelector, anObject, anIndex); +} diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Conversion.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Conversion.h new file mode 100644 index 00000000..0309ebdb --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Conversion.h @@ -0,0 +1,60 @@ +// +// $Id$ +// +// Encoding.m +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 22, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://code.google.com/p/sequel-pro/> + +// This class is private to the framework. + +@interface SPMySQLConnection (Conversion) + ++ (const char *)_cStringForString:(NSString *)aString usingEncoding:(NSStringEncoding)anEncoding returningLengthAs:(NSUInteger *)cStringLengthPointer; + +- (const char *)_cStringForString:(NSString *)aString; +- (NSString *)_stringForCString:(const char *)cString; + +@end + + +/** + * Set up a static function to allow fast calling with cached selectors + */ +static inline const char* _cStringForStringWithEncoding(NSString* aString, NSStringEncoding anEncoding, NSUInteger *cStringLengthPointer) +{ + static Class cachedClass; + static IMP cachedMethodPointer; + static SEL cachedSelector; + + if (!cachedClass) cachedClass = [SPMySQLConnection class]; + if (!cachedSelector) cachedSelector = @selector(_cStringForString:usingEncoding:returningLengthAs:); + if (!cachedMethodPointer) cachedMethodPointer = [SPMySQLConnection methodForSelector:cachedSelector]; + + return (const char *)(*cachedMethodPointer)(cachedClass, cachedSelector, aString, anEncoding, cStringLengthPointer); +} diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Conversion.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Conversion.m new file mode 100644 index 00000000..0a3cf99b --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Conversion.m @@ -0,0 +1,97 @@ +// +// $Id$ +// +// Encoding.m +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 22, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +// This class is private to the framework. + +#import "Conversion.h" + +@implementation SPMySQLConnection (Conversion) + +/** + * Converts an NSString to a null-terminated C string, using the supplied encoding. + * Uses lossy conversion, so if a string cannot be entirely converted using + * the current encoding, a representation will be returned rather than null. + * The returned cString will correctly preserve any nul characters within the string, + * which prevents the use of faster functions like [NSString cStringUsingEncoding:]. + * Pass in the third parameter to receive the length of the converted string, or pass + * in NULL if you do not want this information. + */ ++ (const char *)_cStringForString:(NSString *)aString usingEncoding:(NSStringEncoding)anEncoding returningLengthAs:(NSUInteger *)cStringLengthPointer +{ + + // Don't try and convert nil strings + if (!aString) return NULL; + + // Perform a lossy conversion, using NSData to do the hard work + NSData *convertedData = [aString dataUsingEncoding:anEncoding allowLossyConversion:YES]; + NSUInteger convertedDataLength = [convertedData length]; + + // Take the converted data - not null-terminated - and copy it to a null-terminated buffer + char *cStringBytes = malloc(convertedDataLength + 1); + memcpy(cStringBytes, [convertedData bytes], convertedDataLength); + cStringBytes[convertedDataLength] = 0L; + + if (cStringLengthPointer) *cStringLengthPointer = convertedDataLength+1; + + // Ensure the memory is autoreleased when needed, and return. + [NSData dataWithBytesNoCopy:cStringBytes length:convertedDataLength+1 freeWhenDone:YES]; + return cStringBytes; +} + +/** + * Converts an NSString to a null-terminated C string, using the current + * connection encoding. + */ +- (const char *)_cStringForString:(NSString *)aString +{ + + // Use a cached reference to avoid dynamic method overhead + return _cStringForStringWithEncoding(aString, stringEncoding, NULL); +} + +/** + * Converts a C string to an NSString using the supplied encoding. + * This method *will not* correctly preserve nul characters within c strings; instead + * the first nul character within the string will be treated as the line ending. This + * is unavoidable without supplying a string length, so this method should not be widely + * used for actual data conversion. + */ +- (NSString *)_stringForCString:(const char *)cString +{ + + // Don't try and convert null strings + if (cString == NULL) return nil; + + return [NSString stringWithCString:cString encoding:stringEncoding]; +} + +@end
\ No newline at end of file diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Copying.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Copying.h new file mode 100644 index 00000000..83d055d5 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Copying.h @@ -0,0 +1,36 @@ +// +// $Id$ +// +// Copying.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on March 8, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +@interface SPMySQLConnection (Copying) <NSCopying> + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Copying.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Copying.m new file mode 100644 index 00000000..c2df2a4b --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Copying.m @@ -0,0 +1,71 @@ +// +// $Id$ +// +// Copying.m +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on March 8, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "Copying.h" + +@implementation SPMySQLConnection (Copying) + +/** + * Provide a copy of the SPMySQLConnection instance. + * The copy should inherit the full setup, but will not inherit + * the connection state - it will not be connected, and any connection + * details such as the selected database/encoding will not be inherited. + * Note that any proxy will not be referenced in the new connection, and + * should also be set if desired. + */ +- (id)copyWithZone:(NSZone *)zone +{ + SPMySQLConnection *copy = [[[self class] allocWithZone:zone] init]; + + // Synthesized details + [copy setDelegate:delegate]; + [copy setHost:host]; + [copy setUsername:username]; + [copy setPassword:password]; + [copy setPort:port]; + [copy setUseSocket:useSocket]; + [copy setSocketPath:socketPath]; + [copy setUseSSL:useSSL]; + [copy setSslKeyFilePath:sslKeyFilePath]; + [copy setSslCertificatePath:sslCertificatePath]; + [copy setSslCACertificatePath:sslCACertificatePath]; + [copy setTimeout:timeout]; + [copy setUseKeepAlive:useKeepAlive]; + [copy setRetryQueriesOnConnectionFailure:retryQueriesOnConnectionFailure]; + [copy setDelegateQueryLogging:delegateQueryLogging]; + + // Active connection state details, like selected database and encoding, are *not* copied. + + return copy; +} + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Databases & Tables.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Databases & Tables.h new file mode 100644 index 00000000..332b2680 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Databases & Tables.h @@ -0,0 +1,49 @@ +// +// $Id$ +// +// Databases & Tables.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 11, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +@interface SPMySQLConnection (Databases_and_Tables) + +// Database selection +- (BOOL)selectDatabase:(NSString *)aDatabase; + +// Database lists +- (NSArray *)databases; +- (NSArray *)databasesLike:(NSString *)nameLikeString; + +// Table lists +- (NSArray *)tables; +- (NSArray *)tablesLike:(NSString *)nameLikeString; +- (NSArray *)tablesFromDatabase:(NSString *)aDatabase; +- (NSArray *)tablesLike:(NSString *)nameLikeString fromDatabase:(NSString *)aDatabase; + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Databases & Tables.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Databases & Tables.m new file mode 100644 index 00000000..a95e060e --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Databases & Tables.m @@ -0,0 +1,258 @@ +// +// $Id$ +// +// Databases & Tables.m +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 11, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "Databases & Tables.h" +#import "SPMySQL Private APIs.h" + +@implementation SPMySQLConnection (Databases_and_Tables) + +#pragma mark - +#pragma mark Database selection + +/** + * Selects the database the connection should work with. Typically, a database should be + * set on a connection before any database-specific queries are run. + * Returns whether the database was correctly set or not. + * As MySQL does not support deselecting databases, a nil databaseName will return NO. + */ +- (BOOL)selectDatabase:(NSString *)aDatabase +{ + + // If no database was supplied, can't deselected - return NO. + if (!aDatabase) return NO; + + // Database selection should be made in UTF8 to avoid name encoding issues + BOOL encodingChangeRequired = [self _storeAndAlterEncodingToUTF8IfRequired]; + + // Attempt to select the supplied database + [self queryString:[NSString stringWithFormat:@"USE %@", [aDatabase mySQLBacktickQuotedString]]]; + + // If selecting the database failed, return failure. + if ([self queryErrored]) { + + // If the encoding needs to be restored, the error message and ID have to be stored so the + // actual error is still available to inspect on the class... + if (encodingChangeRequired) { + NSString *theErrorString = [self lastErrorMessage]; + NSUInteger theErrorID = [self lastErrorID]; + + [self restoreStoredEncoding]; + + [self _updateLastErrorMessage:theErrorString]; + [self _updateLastErrorID:theErrorID]; + } + + return NO; + } + + // Restore the connection encoding if necessary + if (encodingChangeRequired) [self restoreStoredEncoding]; + + // Store new database name and return success + if (database) [database release]; + database = [[NSString alloc] initWithString:aDatabase]; + + return YES; +} + +#pragma mark - +#pragma mark Database lists + +/** + * Retrieve an array of databases available to the current user, ordered as MySQL + * returns them. + * If an error occurred while retrieving the list of databases, nil will be returned; + * if no databases are available, an empty array will be returned. + */ +- (NSArray *)databases +{ + + // Wrap the related databasesLike: function to avoid code duplication + return [self databasesLike:nil]; +} + +/** + * Retrieve an array of databases whose names are matched against the supplied name + * using MySQL LIKE syntax (with wildcard support for % and _). If no name is supplied, + * all databases will be returned, in the order that MySQL returns them. + * If an error occurred while retrieving the list of databases, nil will be returned; + * if no matching databases are available, an empty array will be returned. + */ +- (NSArray *)databasesLike:(NSString *)nameLikeString +{ + NSMutableArray *databaseList = nil; + + // Database display should be made in UTF8 to avoid name encoding issues + BOOL encodingChangeRequired = [self _storeAndAlterEncodingToUTF8IfRequired]; + + // Build the query as appropriate + NSMutableString *databaseQuery = [NSMutableString stringWithString:@"SHOW DATABASES"]; + if ([nameLikeString length]) { + [databaseQuery appendFormat:@" LIKE %@", [nameLikeString mySQLTickQuotedString]]; + } + + // Perform the query and record state + SPMySQLResult *databaseResult = [self queryString:databaseQuery]; + [databaseResult setDefaultRowReturnType:SPMySQLResultRowAsArray]; + + // Retrieve the result into an array if the query was successful + if (![self queryErrored]) { + databaseList = [NSMutableArray arrayWithCapacity:(NSUInteger)[databaseResult numberOfRows]]; + for (NSArray *dbRow in databaseResult) { + [databaseList addObject:[dbRow objectAtIndex:0]]; + } + } + + // Restore the connection encoding if necessary + if (encodingChangeRequired) [self restoreStoredEncoding]; + + return databaseList; +} + +#pragma mark - +#pragma mark Table lists + +/** + * Retrieve an array of tables in the currently selected database, ordered as MySQL + * returns them. + * If an error occurred while retrieving the list of tables, nil will be returned; + * if no tables are present, an empty array will be returned. + */ +- (NSArray *)tables +{ + + // Wrap the related tablesLike:fromDatabase: function to avoid code duplication + return [self tablesLike:nil fromDatabase:nil]; +} + +/** + * Retrieve an array of tables in the currently selected database whose names are + * matched against the supplied name using MySQL LIKE syntax (with wildcard + * support for % and _). If no name is supplied, all tables in the selected + * database will be returned, in the order that MySQL returns them. + * If an error occurred while retrieving the list of tables, nil will be returned; + * if no matching tables are present, an empty array will be returned. + */ +- (NSArray *)tablesLike:(NSString *)nameLikeString +{ + + // Wrap the related tablesLike:fromDatabase: function to avoid code duplication + return [self tablesLike:nameLikeString fromDatabase:nil]; + +} + +/** + * Retrieve an array of tables in the specified database, ordered as MySQL returns them. + * If no database is specified, the current database will be used. + * If an error occurred while retrieving the list of tables, nil will be returned; + * if no tables are present in the specified database, an empty array will be returned. + */ +- (NSArray *)tablesFromDatabase:(NSString *)aDatabase +{ + + // Wrap the related tablesLike:fromDatabase: function to avoid code duplication + return [self tablesLike:nil fromDatabase:aDatabase]; + +} + +/** + * Retrieve an array of tables in the specified database whose names are matched + * against the supplied name using MySQL LIKE syntax (with wildcard support + * for % and _). If no name is supplied, all tables in the specified database + * will be returned, in the order that MySQL returns them. + * If no database is specified, the current database will be used. + * If an error occurred while retrieving the list of tables, nil will be returned; + * if no matching tables are present in the specified database, an empty array + * will be returned. + */ +- (NSArray *)tablesLike:(NSString *)nameLikeString fromDatabase:(NSString *)aDatabase +{ + NSMutableArray *tableList = nil; + + // Table display should be made in UTF8 to avoid name encoding issues + BOOL encodingChangeRequired = [self _storeAndAlterEncodingToUTF8IfRequired]; + + // Build up the table lookup query + NSMutableString *tableQuery = [NSMutableString stringWithString:@"SHOW TABLES"]; + if ([aDatabase length]) { + [tableQuery appendFormat:@" FROM %@", [aDatabase mySQLBacktickQuotedString]]; + } + if ([nameLikeString length]) { + [tableQuery appendFormat:@" LIKE %@", [nameLikeString mySQLTickQuotedString]]; + } + + // Perform the query and record state + SPMySQLResult *tableResult = [self queryString:tableQuery]; + [tableResult setDefaultRowReturnType:SPMySQLResultRowAsArray]; + + // Retrieve the result into an array if the query was successful + if (![self queryErrored]) { + tableList = [NSMutableArray arrayWithCapacity:(NSUInteger)[tableResult numberOfRows]]; + for (NSArray *tableRow in tableResult) { + [tableList addObject:[tableRow objectAtIndex:0]]; + } + } + + // Restore the connection encoding if necessary + if (encodingChangeRequired) [self restoreStoredEncoding]; + + return tableList; +} + +@end + +#pragma mark - +#pragma mark Private API + +@implementation SPMySQLConnection (Databases_and_Tables_Private_API) + +/** + * A number of queries regarding database or table information have to be made in UTF8, not + * in the connection encoding, so that names can be fully displayed and used even if they + * use a different encoding. This provides a convenience method to check whether a change + * is required; if so, the current encoding is stored, the encoding is changed, and YES is + * returned so the process can be reversed afterwards. + */ +- (BOOL)_storeAndAlterEncodingToUTF8IfRequired +{ + + // If the encoding is already UTF8, no change is required. + if ([encoding isEqualToString:@"utf8"] && !encodingUsesLatin1Transport) return NO; + + // Store the current encoding for restoration afterwards, and update encoding + [self storeEncodingForRestoration]; + [self setEncoding:@"utf8"]; + + return YES; +} + +@end
\ No newline at end of file diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Delegate & Proxy.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Delegate & Proxy.h new file mode 100644 index 00000000..cf132fcf --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Delegate & Proxy.h @@ -0,0 +1,36 @@ +// +// $Id$ +// +// Delegate & Proxy.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 9, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +@interface SPMySQLConnection (Delegate_and_Proxy) + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Delegate & Proxy.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Delegate & Proxy.m new file mode 100644 index 00000000..3ac013cc --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Delegate & Proxy.m @@ -0,0 +1,133 @@ +// +// $Id$ +// +// Delegate & Proxy.m +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 9, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "Delegate & Proxy.h" +#import "SPMySQL Private APIs.h" + +@implementation SPMySQLConnection (Delegate_and_Proxy) + +#pragma mark - +#pragma mark Connection delegate + +/** + * Override the synthesized delegate setter, to allow optimisations to oft-made + * checks by precacheing availability. + */ +- (void)setDelegate:(NSObject <SPMySQLConnectionDelegate> *)aDelegate +{ + delegate = aDelegate; + + // Cache whether the delegate implements certain delegate methods + delegateSupportsWillQueryString = [delegate respondsToSelector:@selector(willQueryString:connection:)]; + delegateSupportsConnectionLost = [delegate respondsToSelector:@selector(connectionLost:)]; +} + +#pragma mark - +#pragma mark Connection proxy + +/** + * Override the synthesized proxy setter, to record the initial state and to + * set the state change selector. + */ +- (void)setProxy:(NSObject <SPMySQLConnectionProxy> *)aProxy +{ + proxy = [aProxy retain]; + previousProxyState = [aProxy state]; + + [proxy setConnectionStateChangeSelector:@selector(_proxyStateChange:) delegate:self]; +} + +@end + +#pragma mark - + +@implementation SPMySQLConnection (Delegate_and_Proxy_Private_API) + +/** + * Handle any state changes in the associated connection proxy. + */ +- (void)_proxyStateChange:(NSObject <SPMySQLConnectionProxy> *)aProxy +{ + + // Perform no actions if this isn't the current connection proxy, or if notifications + // are currently set to be ignored + if (aProxy != proxy || proxyStateChangeNotificationsIgnored) return; + + SPMySQLConnectionProxyState newState = [aProxy state]; + + // If the connection proxy disconnects, trigger a reconnect; use a new thread to allow the + // main thread to process events as required. + if (state == SPMySQLConnected && newState == SPMySQLProxyIdle && previousProxyState == SPMySQLProxyConnected) { + + // Clear the state change selector on the proxy until a connection is re-established + proxyStateChangeNotificationsIgnored = YES; + + // Trigger a reconnect + [NSThread detachNewThreadSelector:@selector(reconnect) toTarget:self withObject:nil]; + } + + // Update the state record + previousProxyState = newState; +} + +/** + * Ask the delegate for the connection lost decision. This can be called from + * any thread, and will call itself on the main thread if necessary, updating a global + * variable which is then returned on the child thread. + */ +- (SPMySQLConnectionLostDecision)_delegateDecisionForLostConnection +{ + SPMySQLConnectionLostDecision theDecision = SPMySQLConnectionLostDisconnect; + + // If on the main thread, ask the delegate directly. + if ([NSThread isMainThread]) { + [delegateDecisionLock lock]; + lastDelegateDecisionForLostConnection = [delegate connectionLost:self]; + theDecision = lastDelegateDecisionForLostConnection; + [delegateDecisionLock unlock]; + + // Otherwise call ourself on the main thread, waiting until the reply is received. + } else { + + // First check whether the application is in a modal state; if so, wait + while ([NSApp modalWindow]) usleep(100000); + + [self performSelectorOnMainThread:@selector(_delegateDecisionForLostConnection) withObject:nil waitUntilDone:YES]; + [delegateDecisionLock lock]; + theDecision = lastDelegateDecisionForLostConnection; + [delegateDecisionLock unlock]; + } + + return theDecision; +} + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.h new file mode 100644 index 00000000..bb5bf25d --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.h @@ -0,0 +1,53 @@ +// +// $Id$ +// +// Encoding.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 14, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +@interface SPMySQLConnection (Encoding) + +// Current connection encoding information +- (NSString *)encoding; +- (NSStringEncoding)stringEncoding; +- (BOOL)encodingUsesLatin1Transport; + +// Setting connection encoding +- (BOOL)setEncoding:(NSString *)theEncoding; +- (BOOL)setEncodingUsesLatin1Transport:(BOOL)useLatin1; + +// Encoding storage and restoration +- (void)storeEncodingForRestoration; +- (void)restoreStoredEncoding; + +// Encoding conversion ++ (NSStringEncoding)stringEncodingForMySQLCharset:(const char *)mysqlCharset; ++ (NSString *)mySQLCharsetForStringEncoding:(NSStringEncoding)aStringEncoding; + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m new file mode 100644 index 00000000..8f684f29 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m @@ -0,0 +1,414 @@ +// +// $Id$ +// +// Encoding.m +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 14, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +#import "Encoding.h" + +@implementation SPMySQLConnection (Encoding) + +#pragma mark - +#pragma mark Current connection encoding information + +/** + * Returns the name of the current encoding - the MySQL character set - in + * use by the connection. + */ +- (NSString *)encoding +{ + return [NSString stringWithString:encoding]; +} + +/** + * Returns the NSStringEncoding currently in use by the connection to process + * queries and results. + */ +- (NSStringEncoding)stringEncoding +{ + return stringEncoding; +} + +/** + * Returns whether the connection is set to use Latin1 transport for queries and + * results. + * Latin1 transport is a compatibility mode in place for compatibility with older + * incorrect setups, where databases and clients might both be set to use UTF8 (or + * other encodings) for storing and retrieving data, but the MySQL link was never + * set to UTF8 mode; as a result, multibyte characters where split by the connection + * into pairs of characters, resulting in malformed storage. The data works + * correctly if written and read in the same way, so this mode allows correct display + * of that data. + */ +- (BOOL)encodingUsesLatin1Transport +{ + return encodingUsesLatin1Transport; +} + +#pragma mark - +#pragma mark Setting connection encoding + +/** + * Set the name of the encoding - the MySQL character set - that the connection + * should use. If an encoding not recognised by the server is supplied, NO is + * returned. + * Calling this resets whether the connection should use Latin1 transport to NO. + */ +- (BOOL)setEncoding:(NSString *)theEncoding +{ + + // MySQL versions prior to 4.1 don't support encoding changes; return NO on those + // versions. + if (![self serverVersionIsGreaterThanOrEqualTo:4 minorVersion:1 releaseVersion:0]) { + return NO; + } + + // If the supplied encoding is already set, return success + if ([encoding isEqualToString:theEncoding] && !encodingUsesLatin1Transport) { + return YES; + } + + // Run a query to set the connection encoding + [self queryString:[NSString stringWithFormat:@"SET NAMES %@", [theEncoding mySQLTickQuotedString]]]; + + // If the query errored, no encoding change occurred - return failure. + if ([self queryErrored]) return NO; + + // Connection encoding was successfully set, update the instance settings, + // and return success. + [encoding release]; + encoding = [[NSString alloc] initWithString:theEncoding]; + stringEncoding = [SPMySQLConnection stringEncodingForMySQLCharset:[theEncoding UTF8String]]; + encodingUsesLatin1Transport = NO; + + return YES; +} + +/** + * Sets the connection to use Latin1 transport for queries and results or not. All + * encodings will default to not use Latin1 transport.. + * Latin1 transport is a compatibility mode in place for compatibility with older + * incorrect setups, where databases and clients might both be set to use UTF8 (or + * other encodings) for storing and retrieving data, but the MySQL link was never + * set to UTF8 mode; as a result, multibyte characters where split by the connection + * into pairs of characters, resulting in malformed storage. The data works + * correctly if written and read in the same way, so this mode allows correct display + * of that data. + */ +- (BOOL)setEncodingUsesLatin1Transport:(BOOL)useLatin1 +{ + + // MySQL versions prior to 4.1 don't support encoding changes; return NO on those + // versions. + if (![self serverVersionIsGreaterThanOrEqualTo:4 minorVersion:1 releaseVersion:0]) { + return NO; + } + + // If the Latin1 mode is already set, return success + if (encodingUsesLatin1Transport == useLatin1) { + return YES; + } + + // If disabling Latin1 transport, just restore the connection encoding + if (!useLatin1) { + return [self setEncoding:encoding]; + } + + // Otherwise attempt to set Latin1 transport. First, the result set encoding. + [self queryString:@"SET CHARACTER_SET_RESULTS=latin1"]; + + // If that failed, no encoding change occurred - return failure. + if ([self queryErrored]) return NO; + + // Next, change the client character set, to also amend queries sent. + [self queryString:@"SET CHARACTER_SET_CLIENT=latin1"]; + + // If that failed, encoding details are in a partial state - attempt to restore + // the original details before returning failure. + if ([self queryErrored]) { + [self setEncoding:encoding]; + return NO; + } + + // Connecting encoding transport was successfully set, update the instance settings, + // and return success. + encodingUsesLatin1Transport = YES; + return YES; +} + +#pragma mark - +#pragma mark Encoding storage and restoration + + +/** + * Store a previous encoding setting, to allow it to be easily restored + * later - used when the encoding needs to be temporarily changed. + */ +- (void)storeEncodingForRestoration +{ + if (previousEncoding) [previousEncoding release]; + previousEncoding = [[NSString alloc] initWithString:encoding]; + previousEncodingUsesLatin1Transport = encodingUsesLatin1Transport; +} + +/** + * Restore a previously stored encoding setting, if available. Used in + * conjunection with -storeEncodingForRestoration for when the encoding needs + * to be temporarily changed. + */ +- (void)restoreStoredEncoding +{ + if (!previousEncoding || state == SPMySQLDisconnected || state == SPMySQLDisconnecting) { + return; + } + + [self setEncoding:previousEncoding]; + [self setEncodingUsesLatin1Transport:previousEncodingUsesLatin1Transport]; +} + +#pragma mark - +#pragma mark Encoding conversion + +/** + * Map MySQL encodings to NSStringEncodings, using the list of encodings sourced + * from http://dev.mysql.com/doc/refman/5.6/en/charset-charsets.html and the same + * list on previous MySQL versions. Older versions also had less-standard lists, + * such as the charset options listed on + * http://dev.mysql.com/doc/refman/4.1/en/charset-map.html . + * For each, the equivalent NSStringEncoding, or conversion from CfStringEncoding, + * was found. + * If a supplied character set can not be matched, logs an error and falls back + * to UTF8 encoding. + */ ++ (NSStringEncoding)stringEncodingForMySQLCharset:(const char *)mysqlCharset +{ + + // Handle the most common cases first + if (!strcmp(mysqlCharset, "utf8")) { + return NSUTF8StringEncoding; + } else if (!strcmp(mysqlCharset, "latin1")) { + return NSISOLatin1StringEncoding; + } else if (!strcmp(mysqlCharset, "ascii")) { + return NSASCIIStringEncoding; + + // Work down the rest of the 4.1+ charsets + } else if (!strcmp(mysqlCharset, "big5")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingBig5); + } else if (!strcmp(mysqlCharset, "dec8")) { + return NSISOLatin1StringEncoding; // Not exact, but very close + } else if (!strcmp(mysqlCharset, "cp850")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingDOSLatin1); + } else if (!strcmp(mysqlCharset, "koi8r")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingKOI8_R); + } else if (!strcmp(mysqlCharset, "latin2")) { + return NSISOLatin2StringEncoding; + } else if (!strcmp(mysqlCharset, "ujis")) { + return NSJapaneseEUCStringEncoding; + } else if (!strcmp(mysqlCharset, "sjis")) { + return NSShiftJISStringEncoding; + } else if (!strcmp(mysqlCharset, "hebrew")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingISOLatinHebrew); + } else if (!strcmp(mysqlCharset, "tis620")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingISOLatinThai); + } else if (!strcmp(mysqlCharset, "euckr")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingEUC_KR); + } else if (!strcmp(mysqlCharset, "koi8u")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingKOI8_U); + } else if (!strcmp(mysqlCharset, "gb2312")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_2312_80); + } else if (!strcmp(mysqlCharset, "greek")) { + return NSWindowsCP1253StringEncoding; + } else if (!strcmp(mysqlCharset, "cp1250")) { + return NSWindowsCP1250StringEncoding; + } else if (!strcmp(mysqlCharset, "gbk")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGBK_95); + } else if (!strcmp(mysqlCharset, "latin5")) { + return NSWindowsCP1254StringEncoding; + } else if (!strcmp(mysqlCharset, "ucs2")) { + return NSUnicodeStringEncoding; + } else if (!strcmp(mysqlCharset, "cp866")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingDOSRussian); + } else if (!strcmp(mysqlCharset, "macce")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingMacCentralEurRoman); + } else if (!strcmp(mysqlCharset, "macroman")) { + return NSMacOSRomanStringEncoding; + } else if (!strcmp(mysqlCharset, "cp852")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingDOSLatin2); + } else if (!strcmp(mysqlCharset, "latin7")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingISOLatin7); + } else if (!strcmp(mysqlCharset, "utf8mb4")) { + return NSUnicodeStringEncoding; // Is this correct? + } else if (!strcmp(mysqlCharset, "cp1251")) { + return NSWindowsCP1251StringEncoding; + } else if (!strcmp(mysqlCharset, "utf16")) { + return NSUnicodeStringEncoding; + } else if (!strcmp(mysqlCharset, "utf16le")) { + return NSUTF16LittleEndianStringEncoding; + } else if (!strcmp(mysqlCharset, "cp1256")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingWindowsArabic); + } else if (!strcmp(mysqlCharset, "cp1257")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingWindowsBalticRim); + } else if (!strcmp(mysqlCharset, "utf32")) { + return NSUTF32StringEncoding; + } else if (!strcmp(mysqlCharset, "binary")) { + return NSUTF8StringEncoding; + } else if (!strcmp(mysqlCharset, "cp932")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingDOSJapanese); + } else if (!strcmp(mysqlCharset, "eucjpms")) { + return NSJapaneseEUCStringEncoding; + + // Continue with old < 4.1 mappings + } else if (!strcmp(mysqlCharset, "czech")) { + return NSISOLatin2StringEncoding; + } else if (!strcmp(mysqlCharset, "dos")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingDOSLatin1); + } else if (!strcmp(mysqlCharset, "german1")) { + return NSISOLatin1StringEncoding; + } else if (!strcmp(mysqlCharset, "usa7")) { + return NSASCIIStringEncoding; + } else if (!strcmp(mysqlCharset, "danish")) { + return NSISOLatin1StringEncoding; + } else if (!strcmp(mysqlCharset, "win1251")) { + return NSWindowsCP1251StringEncoding; + } else if (!strcmp(mysqlCharset, "euc_kr")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingEUC_KR); + } else if (!strcmp(mysqlCharset, "estonia")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingISOLatin7); + } else if (!strcmp(mysqlCharset, "hungarian")) { + return NSISOLatin2StringEncoding; + } else if (!strcmp(mysqlCharset, "koi8_ru")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingKOI8_R); + } else if (!strcmp(mysqlCharset, "koi8_ukr")) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingKOI8_U); + } else if (!strcmp(mysqlCharset, "win1251ukr")) { + return NSWindowsCP1251StringEncoding; + } else if (!strcmp(mysqlCharset, "win1250")) { + return NSWindowsCP1250StringEncoding; + } else if (!strcmp(mysqlCharset, "croat")) { + return NSISOLatin2StringEncoding; + } else if (!strcmp(mysqlCharset, "latin1_de")) { + return NSISOLatin1StringEncoding; + } + + /** + * Finally, certain other encodings, including the following: + * hp8 + * swe7 + * armscii8 + * keybcs2 + * geostd8 + * ...don't appear to have OS X equivalents; for these and unhandled, log and + * fall back to UTF8 handling. + */ + NSLog(@"SPMySQL Framework has encountered the MySQL encoding '%s' which it is unable to process correctly; falling back to UTF8 mapping.", mysqlCharset); + return NSUTF8StringEncoding; +} + +/** + * Match a supplied NSStringEncoding to a MySQL character set, returning the MySQL + * name of that character set as an NSString. + * If the supplied NSStringEncoding could not be matched, logs an error and returns nil. + */ ++ (NSString *)mySQLCharsetForStringEncoding:(NSStringEncoding)aStringEncoding +{ + + // Switch through the list of NSStringEncodings from NSString, returning the most + // appropriate encoding for each + switch (aStringEncoding) { + + case NSASCIIStringEncoding: + return @"ascii"; + + case NSJapaneseEUCStringEncoding: + return @"ujis"; + + case NSUTF8StringEncoding: + return @"utf8"; + + case NSISOLatin1StringEncoding: + return @"latin1"; + + case NSNonLossyASCIIStringEncoding: + return @"utf8"; + + case NSShiftJISStringEncoding: + return @"sjis"; + + case NSISOLatin2StringEncoding: + return @"latin2"; + + case NSUnicodeStringEncoding: + return @"ucs2"; + + case NSWindowsCP1251StringEncoding: + return @"cp1251"; + + case NSWindowsCP1252StringEncoding: + return @"latin1"; + + case NSWindowsCP1253StringEncoding: + return @"greek"; + + case NSWindowsCP1254StringEncoding: + return @"latin5"; + + case NSWindowsCP1250StringEncoding: + return @"cp1250"; + + case NSMacOSRomanStringEncoding: + return @"macroman"; + + case NSUTF16BigEndianStringEncoding: + return @"utf16"; + + case NSUTF16LittleEndianStringEncoding: + return @"utf16le"; + + case NSUTF32StringEncoding: + return @"utf32"; + + case NSUTF32BigEndianStringEncoding: + return @"utf32"; + } + + /** + * Certain string encodings, including the following: + * NSNEXTSTEPStringEncoding + * NSSymbolStringEncoding + * NSISO2022JPStringEncoding + * NSUTF32LittleEndianStringEncoding + * + * ...don't have equivalents; similarly, many CFStringEncodings aren't yet + * matched. For those, log and return nil. + */ + NSLog(@"SPMySQL Framework was asked for the MySQL charset for the string encoding '%llu', which is currently unhandled.", (unsigned long long)aStringEncoding); + return nil; +} +@end
\ No newline at end of file diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Locking.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Locking.h new file mode 100644 index 00000000..90e11179 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Locking.h @@ -0,0 +1,41 @@ +// +// $Id$ +// +// Locking.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 22, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +// This class is private to the framework. + +@interface SPMySQLConnection (Locking) + +- (void)_lockConnection; +- (BOOL)_tryLockConnection; +- (void)_unlockConnection; + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Locking.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Locking.m new file mode 100644 index 00000000..d654066b --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Locking.m @@ -0,0 +1,104 @@ +// +// $Id$ +// +// Locking.m +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 22, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +// This class is private to the framework. + +#import "Locking.h" +#import "SPMySQL Private APIs.h" + +@implementation SPMySQLConnection (Locking) + + +/** + * Lock the connection. This must be done before performing any operation + * that is not thread safe, eg. performing queries or pinging. + */ +- (void)_lockConnection +{ + + // We can only start a query when the condition is SPMySQLConnectionIdle + [connectionLock lockWhenCondition:SPMySQLConnectionIdle]; + + // Set the condition to SPMySQLConnectionBusy + [connectionLock unlockWithCondition:SPMySQLConnectionBusy]; +} + +/** + * Attempt to lock the connection. If the connection is idle (unlocked), this method + * locks the connection and returns YES for success. The connection must afterward + * be unlocked using unlockConnection. If the connection is currently busy (locked), + * this method immediately returns NO and doesn't lock the connection. + */ +- (BOOL)_tryLockConnection +{ + + // If the connection is already is use, return failure + if (![connectionLock tryLockWhenCondition:SPMySQLConnectionIdle]) { + return NO; + } + + // We're allowed to use the connection; set it to busy, and return success + [connectionLock unlockWithCondition:SPMySQLConnectionBusy]; + return YES; +} + + +/** + * Unlock the connection. + */ +- (void)_unlockConnection +{ + + // Always lock the conditional lock before proceeding + [connectionLock lock]; + + // Check if the connection actually was busy. If it wasn't busy, + // it means the connection may have been unlocked twice. This is + // potentially dangerous, so we log this to the console + if ([connectionLock condition] != SPMySQLConnectionBusy) { + NSLog(@"SPMySQLConnection: Tried to unlock the connection, but it wasn't locked."); + } + + // Since we connected with CLIENT_MULTI_RESULT, we must make sure there are not more results! + // This is still a bit of a dirty hack + if (state == SPMySQLConnected + && mySQLConnection && mySQLConnection->net.vio && mySQLConnection->net.buff && mysql_more_results(mySQLConnection)) + { + NSLog(@"SPMySQLConnection: Discarding unretrieved results. This is currently normal when using CALL."); + [self _flushMultipleResultSets]; + } + + // Tell everyone that the connection is available again + [connectionLock unlockWithCondition:SPMySQLConnectionIdle]; +} + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Max Packet Size.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Max Packet Size.h new file mode 100644 index 00000000..faa667d8 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Max Packet Size.h @@ -0,0 +1,40 @@ +// +// $Id$ +// +// Max Packet Size.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 9, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +@interface SPMySQLConnection (Max_Packet_Size) + +- (NSUInteger)maxQuerySize; +- (BOOL)isMaxQuerySizeEditable; +- (NSUInteger)setGlobalMaxQuerySize:(NSUInteger)newMaxSize; + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Max Packet Size.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Max Packet Size.m new file mode 100644 index 00000000..e0bfef52 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Max Packet Size.m @@ -0,0 +1,196 @@ +// +// $Id$ +// +// Max Packet Size.m +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 9, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +#import "Max Packet Size.h" +#import "SPMySQL Private APIs.h" + +@implementation SPMySQLConnection (Max_Packet_Size) + +/** + * Retrieve the current maximum query size (MySQL's max_allowed_packet), as cached + * by the class. If the connection has been unable to retrieve this value, the + * default of 1MB will be returned. + */ +- (NSUInteger)maxQuerySize +{ + return maxQuerySize; +} + +/** + * Retrieve whether the server's maximum query size (MySQL's max_allowed_packet) is + * editable by the current user. + */ +- (BOOL)isMaxQuerySizeEditable +{ + if (!maxQuerySizeEditabilityChecked) { + [self _updateMaxQuerySizeEditability]; + } + + return maxQuerySizeIsEditable; +} + +/** + * Set the servers's global maximum query size - MySQL's max_allowed_packed - to the + * supplied size. Note that this *does not* affect the current connection; a reconnection + * is required to pick up the new size setting. As a result it may be important to restore + * the connection size after use. + * Validates the supplied size (eg 1GB limit) and applies it if appropriate, returning + * the set query size or NSNotFound on error. + */ +- (NSUInteger)setGlobalMaxQuerySize:(NSUInteger)newMaxSize +{ + + // Perform basic validation. First, ensure the max query size is editable + if (![self isMaxQuerySizeEditable]) return NSNotFound; + + // Validate sizes + if (newMaxSize < 1024) return NSNotFound; + if (newMaxSize > (1024 * 1024 * 1024)) newMaxSize = 1024 * 1024 * 1024; + + // Perform a standard query to set the new size + [self queryString:[NSString stringWithFormat:@"SET GLOBAL max_allowed_packet = %lu", newMaxSize]]; + + // On failure, return NSNotFound - error state will have automatically been set + if ([self queryErrored]) return NSNotFound; + + // Otherwise, set the local instance variable and return success + maxQuerySize = newMaxSize; + return maxQuerySize; +} + +@end + +#pragma mark - + +@implementation SPMySQLConnection (Max_Packet_Size_Private_API) + +/** + * Update the max_allowed_packet size - the largest supported query size - from the server. + */ +- (void)_updateMaxQuerySize +{ + + // Determine which query to run based on server version + NSString *packetQueryString; + if ([self serverMajorVersion] == 3) { + packetQueryString = @"SHOW VARIABLES LIKE 'max_allowed_packet'"; + } else { + packetQueryString = @"SELECT @@global.max_allowed_packet"; + } + + // Make a standard query to the server to retrieve the information + SPMySQLResult *result = [self queryString:packetQueryString]; + [result setReturnDataAsStrings:YES]; + + // Get the maximum size string + NSString *maxQuerySizeString = nil; + if ([self serverMajorVersion] == 3) { + maxQuerySizeString = [[result getRowAsArray] objectAtIndex:1]; + } else { + maxQuerySizeString = [[result getRowAsArray] objectAtIndex:0]; + } + + // If a valid size was returned, update the instance variable + if (maxQuerySizeString) { + maxQuerySize = (NSUInteger)[maxQuerySizeString integerValue]; + } +} + +/** + * Perform a query to determine whether the current user has permission to edit the + * max_allowed_packet setting for their connection. + */ +- (void)_updateMaxQuerySizeEditability +{ + [self queryString:@"SET GLOBAL max_allowed_packet = @@global.max_allowed_packet"]; + maxQuerySizeIsEditable = ![self queryErrored]; + maxQuerySizeEditabilityChecked = YES; +} + +/** + * Attempts to change the maximum query size in order to allow a query to be performed. + * Returns whether the change was successfully made. + */ +- (BOOL)_attemptMaxQuerySizeIncreaseTo:(NSUInteger)targetSize +{ + + // If the query size is editable, attempt to increase the size + if ([self isMaxQuerySizeEditable]) { + NSUInteger newSize = [self setGlobalMaxQuerySize:targetSize]; + if (newSize != NSNotFound) { + + // Successfully increased the global size - reconnect to use it, and return success + [self reconnect]; + return YES; + } + } + + // Can not, or failed to, increase the max query size. Record an error message. + NSString *errorMessage = [NSString stringWithFormat:NSLocalizedString(@"The query length of %lu bytes is larger than max_allowed_packet size (%lu).", @"error message if max_allowed_packet < query size"), targetSize, maxQuerySize]; + [self _updateLastErrorMessage:errorMessage]; + + // Update delegate error if it supports the protocol + if ([delegate respondsToSelector:@selector(queryGaveError:connection:)]) { + [delegate queryGaveError:errorMessage connection:self]; + } + + // Display an alert as this is a special failure + if ([delegate respondsToSelector:@selector(showErrorWithTitle:message:)]) { + [delegate showErrorWithTitle:NSLocalizedString(@"Error", @"error") message:errorMessage]; + } else { + NSRunAlertPanel(NSLocalizedString(@"Error", @"error"), errorMessage, @"OK", nil, nil); + } + + return NO; +} + +/** + * Restore a maximum query size after temporarily increasing it for a query. This action + * may be called directly after a query, or may be before the next query if a streaming result + * had to be used. + */ +- (void)_restoreMaximumQuerySizeAfterQuery +{ + + // Return if no action needs to be performed + if (queryActionShouldRestoreMaxQuerySize == NSNotFound) return; + + // Move the target size to a local variable to prevent looping + NSUInteger targetMaxQuerySize = queryActionShouldRestoreMaxQuerySize; + queryActionShouldRestoreMaxQuerySize = NSNotFound; + + // Enact the change + [self setGlobalMaxQuerySize:targetMaxQuerySize]; +} + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h new file mode 100644 index 00000000..e84c4ca6 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h @@ -0,0 +1,55 @@ +// +// $Id$ +// +// Ping & KeepAlive.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 14, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +// This class is private to the framework. + +typedef struct { + MYSQL *mySQLConnection; + BOOL *keepAlivePingActivePointer; + BOOL *keepAliveLastPingSuccessPointer; +} SPMySQLConnectionPingDetails; + +@interface SPMySQLConnection (Ping_and_KeepAlive) + +// Keepalive ping initialisation +- (void)_keepAlive; +- (void)_threadedKeepAlive; + +// Master ping method +- (BOOL)_pingConnectionUsingLoopDelay:(NSUInteger)loopDelay; + +// Ping thread internals +void _backgroundPingTask(void *ptr); +void _forceThreadExit(int signalNumber); +void _pingThreadCleanup(void *pingDetails); + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m new file mode 100644 index 00000000..3ce0c0cd --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m @@ -0,0 +1,214 @@ +// +// $Id$ +// +// Ping & KeepAlive.m +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 14, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +#import "Ping & KeepAlive.h" +#import "Locking.h" +#import <pthread.h> + +@implementation SPMySQLConnection (Ping_and_KeepAlive) + +#pragma mark - +#pragma mark Keepalive ping initialisation + +/** + * Keeps the connection alive by running a ping. + * This method is called every ten seconds and spawns a thread which determines + * whether or not it should perform a ping. + */ +- (void)_keepAlive +{ + + // Do nothing if not connected or if keepalive is disabled + if (state != SPMySQLConnected || !useKeepAlive) return; + + // Check to see whether a ping is required. First, compare the last query + // and keepalive times against the keepalive interval. + // Compare against interval-1 to allow default keepalive intervals to repeat + // at the correct intervals (eg no timer interval delay). + uint64_t currentTime = mach_absolute_time(); + if (_elapsedSecondsSinceAbsoluteTime(lastConnectionUsedTime) < keepAliveInterval - 1 + || _elapsedSecondsSinceAbsoluteTime(lastKeepAliveTime) < keepAliveInterval - 1) + { + return; + } + + // Attempt to lock the connection. If the connection is currently busy, + // we don't need a ping. + if (![self _tryLockConnection]) return; + [self _unlockConnection]; + + // Store the ping time + lastKeepAliveTime = currentTime; + + [NSThread detachNewThreadSelector:@selector(_threadedKeepAlive) toTarget:self withObject:nil]; +} + +/** + * A threaded keepalive to avoid blocking the interface. Performs safety + * checks, and then creates a child pthread to actually ping the connection, + * forcing the thread to close after the timeout if it hasn't closed already. + */ +- (void)_threadedKeepAlive +{ + + // If the maximum number of ping failures has been reached, trigger a reconnect + if (keepAliveLastPingBlocked || keepAlivePingFailures >= 3) { + [self reconnect]; + return; + } + + // Otherwise, perform a background ping. + BOOL pingResult = [self _pingConnectionUsingLoopDelay:10000]; + if (pingResult) { + keepAlivePingFailures = 0; + } else { + keepAlivePingFailures++; + } +} + +#pragma mark - +#pragma mark Master ping method + +/** + * This function provides a method of pinging the remote server while also enforcing + * the specified connection time. This is required because low-level net reads can + * block indefinitely if the remote server disappears or on network issues - setting + * the MYSQL_OPT_READ_TIMEOUT (and the WRITE equivalent) would "fix" ping, but cause + * long queries to be terminated. + * The supplied loop delay number controls how tight the thread checking loop is, in + * microseconds, to allow differentiating foreground and background pings. + * Unlike mysql_ping, this function returns FALSE on failure and TRUE on success. + */ +- (BOOL)_pingConnectionUsingLoopDelay:(NSUInteger)loopDelay +{ + if (state != SPMySQLConnected) return NO; + + uint64_t pingStartTime_t; + double pingElapsedTime; + BOOL threadCancelled = NO; + + // Set up a query lock + [self _lockConnection]; + + keepAliveLastPingSuccess = NO; + keepAliveLastPingBlocked = NO; + keepAlivePingThreadActive = YES; + + // Use a ping timeout defaulting to thirty seconds, but using the connection timeout if set + NSUInteger pingTimeout = 30; + if (timeout > 0) pingTimeout = timeout; + + // Set up a struct containing details the ping task will need + SPMySQLConnectionPingDetails pingDetails; + pingDetails.mySQLConnection = mySQLConnection; + pingDetails.keepAliveLastPingSuccessPointer = &keepAliveLastPingSuccess; + pingDetails.keepAlivePingActivePointer = &keepAlivePingThreadActive; + + // Create a pthread for the ping + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&keepAlivePingThread, &attr, (void *)&_backgroundPingTask, &pingDetails); + + // Record the ping start time + pingStartTime_t = mach_absolute_time(); + + // Loop until the ping completes + do { + usleep((useconds_t)loopDelay); + pingElapsedTime = _elapsedSecondsSinceAbsoluteTime(pingStartTime_t); + + // If the ping timeout has been exceeded, force a timeout; double-check that the + // thread is still active. + if (pingElapsedTime > pingTimeout && keepAlivePingThreadActive && !threadCancelled) { + pthread_cancel(keepAlivePingThread); + threadCancelled = YES; + + // If the timeout has been exceeded by an additional two seconds, and the thread is + // still active, kill the thread. This can occur in certain network conditions causing + // a blocking read. + } else if (pingElapsedTime > (pingTimeout + 2) && keepAlivePingThreadActive) { + pthread_kill(keepAlivePingThread, SIGUSR1); + keepAlivePingThreadActive = NO; + keepAliveLastPingBlocked = YES; + } + } while (keepAlivePingThreadActive); + + // Clean up + keepAlivePingThread = NULL; + pthread_attr_destroy(&attr); + + // Unlock the connection + [self _unlockConnection]; + + return keepAliveLastPingSuccess; +} + +#pragma mark - +#pragma mark Ping thread internals + +/** + * Actually perform a keepalive ping - intended for use within a pthread. + */ +void _backgroundPingTask(void *ptr) +{ + SPMySQLConnectionPingDetails *pingDetails = (SPMySQLConnectionPingDetails *)ptr; + + // Set up a cleanup routine + pthread_cleanup_push(_pingThreadCleanup, pingDetails); + + // Set up a signal handler for SIGUSR1, to handle forced timeouts. + signal(SIGUSR1, _forceThreadExit); + + // Perform a ping + *(pingDetails->keepAliveLastPingSuccessPointer) = (BOOL)(!mysql_ping(pingDetails->mySQLConnection)); + + // Call the cleanup routine + pthread_cleanup_pop(1); +} + +/** + * Support forcing a thread to exit as a result of a signal. + */ +void _forceThreadExit(int signalNumber) +{ + pthread_exit(NULL); +} + +void _pingThreadCleanup(void *pingDetails) +{ + SPMySQLConnectionPingDetails *pingDetailsStruct = pingDetails; + *(pingDetailsStruct->keepAlivePingActivePointer) = NO; +} + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.h new file mode 100644 index 00000000..0f086a89 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.h @@ -0,0 +1,106 @@ +// +// $Id$ +// +// Querying & Preparation.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 14, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +@interface SPMySQLConnection (Querying_and_Preparation) + +// Data preparation +- (NSString *)escapeAndQuoteString:(NSString *)theString; +- (NSString *)escapeString:(NSString *)theString includingQuotes:(BOOL)includeQuotes; +- (NSString *)escapeAndQuoteData:(NSData *)theData; +- (NSString *)escapeData:(NSData *)theData includingQuotes:(BOOL)includeQuotes; + +// Queries +- (SPMySQLResult *)queryString:(NSString *)theQueryString; +- (SPMySQLFastStreamingResult *)streamingQueryString:(NSString *)theQueryString; +- (id)streamingQueryString:(NSString *)theQueryString useLowMemoryBlockingStreaming:(BOOL)fullStreaming; +- (id)queryString:(NSString *)theQueryString usingEncoding:(NSStringEncoding)theEncoding withResultType:(SPMySQLResultType)theReturnType; + +// Query convenience functions +- (NSArray *)getAllRowsFromQuery:(NSString *)theQueryString; +- (id)getFirstFieldFromQuery:(NSString *)theQueryString; + +// Query information +- (unsigned long long)rowsAffectedByLastQuery; +- (unsigned long long)lastInsertID; + +// Connection and query error state +- (BOOL)queryErrored; +- (NSString *)lastErrorMessage; +- (NSUInteger)lastErrorID; ++ (BOOL)isErrorIDConnectionError:(NSUInteger)theErrorID; + +// Query cancellation +- (void)cancelCurrentQuery; +- (BOOL)lastQueryWasCancelledUsingReconnect; + +@end + +/** + * Set up static functions to allow fast calling with cached selectors + */ + +static inline id SPMySQLConnectionEscapeString(SPMySQLConnection* self, NSString *theString, BOOL encloseInQuotes) +{ + typedef id (*SPMySQLConnectionEscapeStringMethodPtr)(SPMySQLConnection*, SEL, NSString *, BOOL); + static SPMySQLConnectionEscapeStringMethodPtr cachedMethodPointer; + static SEL cachedSelector; + + if (!cachedSelector) cachedSelector = @selector(escapeString:includingQuotes:); + if (!cachedMethodPointer) cachedMethodPointer = (SPMySQLConnectionEscapeStringMethodPtr)[self methodForSelector:cachedSelector]; + + return cachedMethodPointer(self, cachedSelector, theString, encloseInQuotes); +} + +static inline id SPMySQLConnectionEscapeData(SPMySQLConnection* self, NSData *theData, BOOL encloseInQuotes) +{ + typedef id (*SPMySQLConnectionEscapeDataMethodPtr)(SPMySQLConnection*, SEL, NSData *, BOOL); + static SPMySQLConnectionEscapeDataMethodPtr cachedMethodPointer; + static SEL cachedSelector; + + if (!cachedSelector) cachedSelector = @selector(escapeData:includingQuotes:); + if (!cachedMethodPointer) cachedMethodPointer = (SPMySQLConnectionEscapeDataMethodPtr)[self methodForSelector:cachedSelector]; + + return cachedMethodPointer(self, cachedSelector, theData, encloseInQuotes); +} + +static inline id SPMySQLConnectionQueryString(SPMySQLConnection* self, NSString *theQueryString, NSStringEncoding theEncoding, SPMySQLResultType theReturnType) +{ + typedef id (*SPMySQLConnectionQueryStringMethodPtr)(SPMySQLConnection*, SEL, NSString *, NSStringEncoding, SPMySQLResultType); + static SPMySQLConnectionQueryStringMethodPtr cachedMethodPointer; + static SEL cachedSelector; + + if (!cachedSelector) cachedSelector = @selector(queryString:usingEncoding:withResultType:); + if (!cachedMethodPointer) cachedMethodPointer = (SPMySQLConnectionQueryStringMethodPtr)[self methodForSelector:cachedSelector]; + + return cachedMethodPointer(self, cachedSelector, theQueryString, theEncoding, theReturnType); +} diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m new file mode 100644 index 00000000..9b54029c --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m @@ -0,0 +1,636 @@ +// +// $Id$ +// +// Querying & Preparation.m +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 14, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +#import "SPMySQLConnection.h" +#import "SPMySQL Private APIs.h" + +@implementation SPMySQLConnection (Querying_and_Preparation) + +#pragma mark - +#pragma mark Data preparation + +/** + * See also the NSString methods mySQLTickQuotedString and mySQLBacktickQuotedString, + * added via an NSString category; however these methods are safer and more complete + * as they use the current connection encoding to quote characters. + */ + + +/** + * Take a string, escapes any special character, and surrounds it with single quotes + * for safe use within a query; correctly escapes any characters within the string + * using the current connection encoding. + */ +- (NSString *)escapeAndQuoteString:(NSString *)theString +{ + return SPMySQLConnectionEscapeString(self, theString, YES); +} + +/** + * Take a string and escapes any special character for safe use within a query; correctly + * escapes any characters within the string using the current connection encoding. + * Allows control over whether to also wrap the string in single quotes. + */ +- (NSString *)escapeString:(NSString *)theString includingQuotes:(BOOL)includeQuotes +{ + + // Return nil strings untouched + if (!theString) return theString; + + // To correctly escape the string, an active connection is required, so verify. + if (state == SPMySQLDisconnected || state == SPMySQLConnecting) { + if ([delegate respondsToSelector:@selector(noConnectionAvailable:)]) { + [delegate noConnectionAvailable:self]; + } + return nil; + } + if (![self _checkConnectionIfNecessary]) return nil; + + // Perform a lossy conversion to bytes, using NSData to do the hard work. Preserves + // nul characters correctly. + NSData *cData = [theString dataUsingEncoding:stringEncoding allowLossyConversion:YES]; + NSUInteger cDataLength = [cData length]; + + // Create a buffer for mysql_real_escape_string to place the converted string into; + // the max length is 2*length (if every character was quoted) + 2 (quotes/terminator). + // Adding quotes in this way makes the logic below *slightly* harder to follow but + // makes the addition of the quotes almost free, which is much nicer when building + // lots of strings. + char *escBuffer = (char *)malloc((cDataLength * 2) + 2); + + // Use mysql_real_escape_string to perform the escape, starting one character in + NSUInteger escapedLength = mysql_real_escape_string(mySQLConnection, escBuffer+1, [cData bytes], cDataLength); + + // Set up an NSData object to allow conversion back to NSString while preserving + // any nul characters contained in the string. + NSData *escapedData; + if (includeQuotes) { + + // Add quotes if requested + escBuffer[0] = '\''; + escBuffer[escapedLength+1] = '\''; + + escapedData = [NSData dataWithBytesNoCopy:escBuffer length:escapedLength+2 freeWhenDone:NO]; + } else { + escapedData = [NSData dataWithBytesNoCopy:escBuffer+1 length:escapedLength freeWhenDone:NO]; + } + + // Convert to the string to return + NSString *escapedString = [[NSString alloc] initWithData:escapedData encoding:stringEncoding]; + + // Free up any memory and return + free(escBuffer); + return [escapedString autorelease]; +} + +/** + * Take NSData and hex-encodes the contents for safe transmission to a server, + * preserving all bytes whatever the encoding. Surrounds the hex-encoded resulting + * string with single quotes and precedes it with the hex-marker X for safe inclusion + * in a query. + */ +- (NSString *)escapeAndQuoteData:(NSData *)theData +{ + return SPMySQLConnectionEscapeData(self, theData, YES); +} + +/** + * Takes NSData and hex-encodes the contents for safe transmission to a server, + * preserving all bytes whatever the encoding. + * Allows control over whether to also wrap the string in single quotes and a + * preceding X (X'...') for safe use in queries. + */ +- (NSString *)escapeData:(NSData *)theData includingQuotes:(BOOL)includeQuotes +{ + + // Return nil datas as nil strings + if (!theData) return nil; + + NSUInteger dataLength = [theData length]; + + // Create a buffer for mysql_real_escape_string to place the converted string into; + // the max length is 2*length (if every character was quoted) + 3 (quotes/terminator). + // Adding quotes in this way makes the logic below *slightly* harder to follow but + // makes the addition of the quotes almost free, which is much nicer when building + // lots of strings. + char *hexBuffer = (char *)malloc((dataLength * 2) + 3); + + // Use mysql_hex_string to perform the escape, starting two characters in + NSUInteger hexLength = mysql_hex_string(hexBuffer+2, [theData bytes], dataLength); + + // Set up the return NSString + NSString *hexString; + if (includeQuotes) { + + // Add quotes if requested + hexBuffer[0] = 'X'; + hexBuffer[1] = '\''; + hexBuffer[hexLength+2] = '\''; + + hexString = [[NSString alloc] initWithBytes:hexBuffer length:hexLength+3 encoding:NSASCIIStringEncoding]; + } else { + hexString = [[NSString alloc] initWithBytes:hexBuffer+2 length:hexLength encoding:NSASCIIStringEncoding]; + } + + // Free up any memory and return + free(hexBuffer); + return [hexString autorelease]; +} + +#pragma mark - +#pragma mark Queries + +/** + * Run a query, provided as a string, on the active connection in the current connection + * encoding. Stores all the results before returning the complete result set. + */ +- (SPMySQLResult *)queryString:(NSString *)theQueryString +{ + return SPMySQLConnectionQueryString(self, theQueryString, stringEncoding, SPMySQLResultAsResult); +} + +/** + * Run a query, provided as a string, on the active connection in the current connection + * encoding. Returns the result as a fast streaming query set, where not all the results + * may be available at time of return. + */ +- (SPMySQLFastStreamingResult *)streamingQueryString:(NSString *)theQueryString +{ + return SPMySQLConnectionQueryString(self, theQueryString, stringEncoding, SPMySQLResultAsFastStreamingResult); +} + +/** + * Run a query, provided as a string, on the active connection in the current connection + * encoding. Returns the result as a streaming query set, where not all the results may + * be available at time of return. + * Supports a flag specifying whether streaming should be low-memory blocking (results are + * read from the server as the code retrives them, possibly blocking other queries on the + * server) or fast streaming (results are cached in the result object as fast as possible, + * freeing up the server even in the local rows are still being read from the result object). + * Will return a SPMySQLStreamingResult or SPMySQLFastStreamingResult as appropriate. + */ +- (id)streamingQueryString:(NSString *)theQueryString useLowMemoryBlockingStreaming:(BOOL)fullStreaming +{ + return SPMySQLConnectionQueryString(self, theQueryString, stringEncoding, fullStreaming?SPMySQLResultAsLowMemStreamingResult:SPMySQLResultAsFastStreamingResult); +} + +/** + * Run a query, provided as a string, on the active connection. The query and its result + * set are interpreted according to the supplied encoding, which should usually match + * the connection encoding. + * The result type desired can be specified, supporting either standard or streaming + * result sets. + */ +- (id)queryString:(NSString *)theQueryString usingEncoding:(NSStringEncoding)theEncoding withResultType:(SPMySQLResultType)theReturnType +{ + double queryExecutionTime; + lastQueryWasCancelled = NO; + lastQueryWasCancelledUsingReconnect = NO; + + // Check the connection state - if no connection is available, log an + // error and return. + if (state == SPMySQLDisconnected || state == SPMySQLConnecting) { + if ([delegate respondsToSelector:@selector(queryGaveError:connection:)]) { + [delegate queryGaveError:@"No connection available!" connection:self]; + } + if ([delegate respondsToSelector:@selector(noConnectionAvailable:)]) { + [delegate noConnectionAvailable:self]; + } + return nil; + } + + // Check the connection if necessary, returning nil if the query couldn't be validated + if (![self _checkConnectionIfNecessary]) return nil; + + // Determine whether a maximum query size needs to be restored from a previous query + if (queryActionShouldRestoreMaxQuerySize != NSNotFound) { + [self _restoreMaximumQuerySizeAfterQuery]; + } + + // If delegate logging is enabled, and the protocol is implemented, inform the delegate + if (delegateQueryLogging && delegateSupportsWillQueryString) { + [delegate willQueryString:theQueryString connection:self]; + } + + // Retrieve a C-style query string from the supplied NSString + NSUInteger cQueryStringLength; + const char *cQueryString = _cStringForStringWithEncoding(theQueryString, theEncoding, &cQueryStringLength); + + // Check the query length against the current maximum query length. If it is + // larger, the query would error (and probably cause a disconnect), so if + // the maximum size is editable, increase it and reconnect. + if (cQueryStringLength > maxQuerySize) { + queryActionShouldRestoreMaxQuerySize = maxQuerySize; + if (![self _attemptMaxQuerySizeIncreaseTo:(cQueryStringLength + 1024)]) { + queryActionShouldRestoreMaxQuerySize = NSNotFound; + return nil; + } + } + + // Prepare to enter a loop to run the query, allowing reattempts if appropriate + NSUInteger queryAttemptsAllowed = 1; + if (retryQueriesOnConnectionFailure) queryAttemptsAllowed++; + int queryStatus; + + // Lock the connection while it's actively in use + [self _lockConnection]; + + while (queryAttemptsAllowed > 0) { + + // While recording the overall execution time (including network lag!), run + // the raw query + uint64_t queryStartTime = mach_absolute_time(); + queryStatus = mysql_real_query(mySQLConnection, cQueryString, cQueryStringLength); + queryExecutionTime = _elapsedSecondsSinceAbsoluteTime(queryStartTime); + lastConnectionUsedTime = mach_absolute_time(); + + // If the query succeeded, no need to re-attempt. + if (!queryStatus) { + break; + + // If the query failed, determine whether to reattempt the query + } else { + + // Prevent retries if the query was cancelled or not a connection error + if (lastQueryWasCancelled || ![SPMySQLConnection isErrorIDConnectionError:mysql_errno(mySQLConnection)]) { + break; + } + } + + // Query has failed - check the connection + if (![self checkConnection]) { + [self _unlockConnection]; + return nil; + } + + queryAttemptsAllowed--; + } + + unsigned long long theAffectedRowCount = mysql_affected_rows(mySQLConnection); + id theResult = nil; + + // On success, if there is a query result, retrieve the result data type + if (!queryStatus && mysql_field_count(mySQLConnection)) { + MYSQL_RES *mysqlResult; + + switch (theReturnType) { + + // For standard result sets, retrieve all the results now, and afterwards + // update the affected row count. + case SPMySQLResultAsResult: + mysqlResult = mysql_store_result(mySQLConnection); + theResult = [[SPMySQLResult alloc] initWithMySQLResult:mysqlResult stringEncoding:theEncoding]; + theAffectedRowCount = mysql_affected_rows(mySQLConnection); + break; + + // For fast streaming and low memory streaming result sets, set up the result + case SPMySQLResultAsLowMemStreamingResult: + mysqlResult = mysql_use_result(mySQLConnection); + theResult = [[SPMySQLStreamingResult alloc] initWithMySQLResult:mysqlResult stringEncoding:theEncoding connection:self]; + break; + + case SPMySQLResultAsFastStreamingResult: + mysqlResult = mysql_use_result(mySQLConnection); + theResult = [[SPMySQLFastStreamingResult alloc] initWithMySQLResult:mysqlResult stringEncoding:theEncoding connection:self]; + break; + } + } + + // Record the error state now, as it may be affected by subsequent clean-up queries + NSString *theErrorMessage = [self _stringForCString:mysql_error(mySQLConnection)]; + NSUInteger theErrorID = mysql_errno(mySQLConnection); + + // If the query was cancelled, override the error state + if (lastQueryWasCancelled) { + theErrorMessage = NSLocalizedString(@"Query cancelled.", @"Query cancelled error"); + theErrorID = 1317; + } + + // Unlock the connection if appropriate - if not a streaming result type. + if (![theResult isKindOfClass:[SPMySQLStreamingResult class]]) { + [self _unlockConnection]; + + // Also perform restore if appropriate + if (queryActionShouldRestoreMaxQuerySize != NSNotFound) { + [self _restoreMaximumQuerySizeAfterQuery]; + } + } + + // Update error string and ID, and the rows affected + [self _updateLastErrorMessage:theErrorMessage]; + [self _updateLastErrorID:theErrorID]; + lastQueryAffectedRowCount = theAffectedRowCount; + + // Store the result time on the response object + [theResult _setQueryExecutionTime:queryExecutionTime]; + + return [theResult autorelease]; +} + +#pragma mark - +#pragma mark Query convenience functions + +/** + * Run a query and retrieve the entire result set as an array of dictionaries. + * Returns nil if there was a problem running the query or retrieving any results. + */ +- (NSArray *)getAllRowsFromQuery:(NSString *)theQueryString +{ + return [[self queryString:theQueryString] getAllRows]; +} + +/** + * Run a query and retrieve the first field of any response. Returns nil if there + * was a problem running the query or retrieving any results. + */ +- (id)getFirstFieldFromQuery:(NSString *)theQueryString +{ + return [[[self queryString:theQueryString] getRowAsArray] objectAtIndex:0]; +} + +#pragma mark - +#pragma mark Query information + +/** + * Returns the number of rows changed, deleted, inserted, or selected by + * the last query. + */ +- (unsigned long long)rowsAffectedByLastQuery +{ + return lastQueryAffectedRowCount; +} + +/** + * Returns the insert ID for the previous query which inserted a row. Note that + * this value persists through other SELECT/UPDATE etc queries. + */ +- (unsigned long long)lastInsertID +{ + return lastQueryInsertID; +} + +#pragma mark - +#pragma mark Retrieving connection and query error state + +/** + * Return whether the last query errored or not. + */ +- (BOOL)queryErrored +{ + return (queryErrorMessage)?YES:NO; +} + +/** + * If the last query (or connection) triggered an error, returns the error + * message as a string; if the last query did not error, nil is returned. + */ +- (NSString *)lastErrorMessage +{ + return queryErrorMessage; +} + +/** + * If the last query (or connection) triggered an error, returns the error + * ID; if the last query did not error, 0 is returned. + */ +- (NSUInteger)lastErrorID +{ + return queryErrorID; +} + +/** + * Determines whether a supplied error ID can be classed as a connection error. + */ ++ (BOOL)isErrorIDConnectionError:(NSUInteger)theErrorID +{ + switch (theErrorID) { + case 2001: // CR_SOCKET_CREATE_ERROR + case 2002: // CR_CONNECTION_ERROR + case 2003: // CR_CONN_HOST_ERROR + case 2004: // CR_IPSOCK_ERROR + case 2005: // CR_UNKNOWN_HOST + case 2006: // CR_SERVER_GONE_ERROR + case 2007: // CR_VERSION_ERROR + case 2009: // CR_WRONG_HOST_INFO + case 2012: // CR_SERVER_HANDSHAKE_ERR + case 2013: // CR_SERVER_LOST + case 2027: // CR_MALFORMED_PACKET + case 2032: // CR_DATA_TRUNCATED + case 2047: // CR_CONN_UNKNOW_PROTOCOL + case 2048: // CR_INVALID_CONN_HANDLE + case 2050: // CR_FETCH_CANCELED + case 2055: // CR_SERVER_LOST_EXTENDED + return YES; + } + + return NO; +} + +#pragma mark - +#pragma mark Query cancellation + +/** + * Cancel the currently running query. This tries to kill the current query, + * and if that isn't possible - for example, on MySQL < 5 or if the current user + * does not have the relevant permissions - resets the connection. + */ +- (void)cancelCurrentQuery +{ + + // If not connected, no action is required + if (state != SPMySQLConnected && state != SPMySQLDisconnecting) return; + + // Check whether a query is actually being performed - if not, return + if ([self _tryLockConnection]) { + [self _unlockConnection]; + return; + } + + // Mark that the last query was cancelled to prevent query retries from occurring + lastQueryWasCancelled = YES; + + // The query cancellation cannot occur on the connection actively running a query + // so set up a new connection to run the KILL command. + MYSQL *killerConnection = [self _makeRawMySQLConnectionWithEncoding:@"utf8" isMasterConnection:NO]; + + + // If the new connection was successfully set up, use it to run a KILL command. + if (killerConnection) { + NSStringEncoding aStringEncoding = [SPMySQLConnection stringEncodingForMySQLCharset:mysql_character_set_name(killerConnection)]; + BOOL killQuerySupported = [self serverVersionIsGreaterThanOrEqualTo:5 minorVersion:0 releaseVersion:0]; + + // Build the kill query + NSMutableString *killQuery = [NSMutableString stringWithString:@"KILL"]; + if (killQuerySupported) [killQuery appendString:@" QUERY"]; + [killQuery appendFormat:@" %lu", mySQLConnection->thread_id]; + + // Convert to a C string + NSUInteger killQueryCStringLength; + const char *killQueryCString = [SPMySQLConnection _cStringForString:killQuery usingEncoding:aStringEncoding returningLengthAs:&killQueryCStringLength]; + + // Run the query + int killQueryStatus = mysql_real_query(killerConnection, killQueryCString, killQueryCStringLength); + + // Close the temporary connection + mysql_close(killerConnection); + + // If the kill query succeeded, the active query was cancelled. + if (killQueryStatus == 0) { + + // On MySQL < 5, the entire connection will have been reset. Ensure it's + // restored. + if (!killQuerySupported) { + [self checkConnection]; + lastQueryWasCancelledUsingReconnect = YES; + } else { + lastQueryWasCancelledUsingReconnect = NO; + } + + // Ensure the tracking bool is re-set to cover encompassed queries and return + lastQueryWasCancelled = YES; + return; + } else { + NSLog(@"SPMySQL Framework: query cancellation failed due to cancellation query error (status %d)", killQueryStatus); + } + } else { + NSLog(@"SPMySQL Framework: query cancellation failed because connection failed"); + } + + // A full reconnect is required at this point to force a cancellation. As the + // connection may have finished processing the query at this point (depending how + // long the connection attempt took), check whether we can skip the reconnect. + if ([self _tryLockConnection]) { + [self _unlockConnection]; + return; + } + + if (state == SPMySQLDisconnecting) return; + + // Reset the connection with a reconnect. Unlock the connection beforehand, + // to allow the reconnect, but lock it again afterwards to restore the expected + // state (query execution process should unlock as appropriate). + [self _unlockConnection]; + [self reconnect]; + [self _lockConnection]; + + // Reset tracking bools to cover encompassed queries + lastQueryWasCancelled = YES; + lastQueryWasCancelledUsingReconnect = YES; +} + +/** + * If the last query was cancelled, returns whether that query cancellation + * required the connection to be reset or whether the query was successfully + * cancelled leaving the connection intact. + * If the last query was not cancelled, this will return NO. + */ +- (BOOL)lastQueryWasCancelledUsingReconnect +{ + return lastQueryWasCancelledUsingReconnect; +} + +@end + +#pragma mark - +#pragma mark Private API + +@implementation SPMySQLConnection (Querying_and_Preparation_Private_API) + +/** + * Retrieves all remaining results and discards them. + * This is necessary to correctly process multiple result sets on the connection - as + * we currently don't fully support multiple result, this at least allows the connection + * to function after running statements with multiple result sets. + */ +- (void)_flushMultipleResultSets +{ + + // Repeat as long as there are results + while (!mysql_next_result(mySQLConnection)) { + MYSQL_RES *eachResult = mysql_use_result(mySQLConnection); + + // Ensure the result is really a result + if (eachResult) { + + // Retrieve and discard all rows + while (mysql_fetch_row(eachResult)); + + // Free the result set + mysql_free_result(eachResult); + } + } +} + +/** + * Update the MySQL error message for this connection. If an error is supplied + * it will be stored and returned to anything asking the instance for the last + * error; if no error is supplied, the connection will be used to derive (or clear) + * the error string. + */ +- (void)_updateLastErrorMessage:(NSString *)theErrorMessage +{ + + // If an error message wasn't supplied, select one from the connection + if (!theErrorMessage) { + theErrorMessage = [self _stringForCString:mysql_error(mySQLConnection)]; + } + + // Clear the last error message stored on the instance + if (queryErrorMessage) [queryErrorMessage release], queryErrorMessage = nil; + + // If we have an error message *with a length*, update the instance error message + if (theErrorMessage && [theErrorMessage length]) { + queryErrorMessage = [[NSString alloc] initWithString:theErrorMessage]; + } +} + +/** + * Update the MySQL error ID for this connection. If an error ID is supplied, + * it will be stored and returned to anything asking the instance for the last + * error; if an NSNotFound error ID is supplied, the connection will be used to + * set the error ID. Note that an error ID of 0 corresponds to no error. + */ +- (void)_updateLastErrorID:(NSUInteger)theErrorID +{ + + // If NSNotFound was supplied as the ID, ask the connection for the last error + if (theErrorID == NSNotFound) { + queryErrorID = mysql_errno(mySQLConnection); + + // Otherwise, update the error ID with the supplied ID + } else { + queryErrorID = theErrorID; + } +} + +@end
\ No newline at end of file diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h new file mode 100644 index 00000000..d8f5f183 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h @@ -0,0 +1,50 @@ +// +// $Id$ +// +// Server Info.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 14, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +@class SPMySQLResult; + +@interface SPMySQLConnection (Server_Info) + +// Server version information +- (NSString *)serverVersionString; +- (NSUInteger)serverMajorVersion; +- (NSUInteger)serverMinorVersion; +- (NSUInteger)serverReleaseVersion; + +// Server version comparisons +- (BOOL)serverVersionIsGreaterThanOrEqualTo:(NSUInteger)aMajorVersion minorVersion:(NSUInteger)aMinorVersion releaseVersion:(NSUInteger)aReleaseVersion; + +// Server tasks & processes +- (SPMySQLResult *)listProcesses; +- (BOOL)killQueryOnThreadID:(unsigned long)theThreadID; + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m new file mode 100644 index 00000000..f695d977 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m @@ -0,0 +1,175 @@ +// +// $Id$ +// +// Server Info.m +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 14, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +#import "Server Info.h" +#import "SPMySQL Private APIs.h" + +@implementation SPMySQLConnection (Server_Info) + +#pragma mark - +#pragma mark Server version information + +/** + * Return the server version string, or nil on failure. + */ +- (NSString *)serverVersionString +{ + if (serverVersionString) { + return [NSString stringWithString:serverVersionString]; + } + + return nil; +} + +/** + * Return the server major version or NSNotFound on failure + */ +- (NSUInteger)serverMajorVersion +{ + + if (serverVersionString != nil) { + NSString *s = [[serverVersionString componentsSeparatedByString:@"."] objectAtIndex:0]; + return (NSUInteger)[s integerValue]; + } + + return NSNotFound; +} + +/** + * Return the server minor version or NSNotFound on failure + */ +- (NSUInteger)serverMinorVersion +{ + if (serverVersionString != nil) { + NSString *s = [[serverVersionString componentsSeparatedByString:@"."] objectAtIndex:1]; + return (NSUInteger)[s integerValue]; + } + + return NSNotFound; +} + +/** + * Return the server release version or NSNotFound on failure + */ +- (NSUInteger)serverReleaseVersion +{ + if (serverVersionString != nil) { + NSString *s = [[serverVersionString componentsSeparatedByString:@"."] objectAtIndex:2]; + return (NSUInteger)[[[s componentsSeparatedByString:@"-"] objectAtIndex:0] integerValue]; + } + + return NSNotFound; +} + +#pragma mark - +#pragma mark Server version comparisons + +/** + * Returns whether the connected server version is greater than or equal to the + * supplied version number. Returns NO if no connection is active. + */ +- (BOOL)serverVersionIsGreaterThanOrEqualTo:(NSUInteger)aMajorVersion minorVersion:(NSUInteger)aMinorVersion releaseVersion:(NSUInteger)aReleaseVersion +{ + if (!serverVersionString) return NO; + + NSArray *serverVersionParts = [serverVersionString componentsSeparatedByString:@"."]; + + NSUInteger serverMajorVersion = (NSUInteger)[[serverVersionParts objectAtIndex:0] integerValue]; + if (serverMajorVersion < aMajorVersion) return NO; + if (serverMajorVersion > aMajorVersion) return YES; + + NSUInteger serverMinorVersion = (NSUInteger)[[serverVersionParts objectAtIndex:1] integerValue]; + if (serverMinorVersion < aMinorVersion) return NO; + if (serverMinorVersion > aMinorVersion) return YES; + + NSString *serverReleasePart = [serverVersionParts objectAtIndex:2]; + NSUInteger serverReleaseVersion = (NSUInteger)[[[serverReleasePart componentsSeparatedByString:@"-"] objectAtIndex:0] integerValue]; + if (serverReleaseVersion < aReleaseVersion) return NO; + return YES; +} + +#pragma mark - +#pragma mark Server tasks & processes + +/** + * Returns a result set describing the current server threads and their tasks. Note that + * the resulting process list defaults to the short form; run a manual SHOW FULL PROCESSLIST + * to retrieve tasks in non-truncated form. + * Returns nil on error. + */ +- (SPMySQLResult *)listProcesses +{ + if (state != SPMySQLConnected) return nil; + + // Check the connection if appropriate + if (![self _checkConnectionIfNecessary]) return nil; + + // Lock the connection before using it + [self _lockConnection]; + + // Get the process list + MYSQL_RES *mysqlResult = mysql_list_processes(mySQLConnection); + + // Convert to SPMySQLResult + SPMySQLResult *theResult = [[SPMySQLResult alloc] initWithMySQLResult:mysqlResult stringEncoding:stringEncoding]; + + // Unlock and return + [self _unlockConnection]; + return [theResult autorelease]; +} + +/** + * Kill the process with the supplied thread ID. On MySQL version 5 or later, this kills + * the query; on older servers this kills the entire connection. Note that the SUPER + * privilege is required to kill queries and processes not belonging to the currently + * connected user, while only PROCESS is required to see other user's processes. + * Returns a boolean indicating success or failure. + */ +- (BOOL)killQueryOnThreadID:(unsigned long)theThreadID +{ + + // Note that mysql_kill has been deprecated, so use a query to perform this task. + NSMutableString *killQuery = [NSMutableString stringWithString:@"KILL"]; + if ([self serverVersionIsGreaterThanOrEqualTo:5 minorVersion:0 releaseVersion:0]) { + [killQuery appendString:@" QUERY"]; + } + [killQuery appendFormat:@" %lu", theThreadID]; + + // Run the query + [self queryString:killQuery]; + + // Return a value based on whether the query errored or not + return ![self queryErrored]; +} + +@end
\ No newline at end of file diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h new file mode 100644 index 00000000..8f3b7f9f --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h @@ -0,0 +1,181 @@ +// +// $Id$ +// +// SPMySQLConnection.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 8, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +@class SPMySQLKeepAliveTimer; + +@interface SPMySQLConnection : NSObject { + + // Delegate + NSObject <SPMySQLConnectionDelegate> *delegate; + BOOL delegateSupportsWillQueryString; + BOOL delegateSupportsConnectionLost; + BOOL delegateQueryLogging; // Defaults to YES if protocol implemented + + // Basic connection details + NSString *host; + NSString *username; + NSString *password; + NSUInteger port; + BOOL useSocket; + NSString *socketPath; + + // SSL connection details + BOOL useSSL; + NSString *sslKeyFilePath; + NSString *sslCertificatePath; + NSString *sslCACertificatePath; + + // MySQL connection details and state + struct st_mysql *mySQLConnection; + SPMySQLConnectionState state; + BOOL connectedWithSSL; + BOOL userTriggeredDisconnect; + BOOL isReconnecting; + uint64_t initialConnectTime; + unsigned long mysqlConnectionThreadId; + + // Connection proxy + NSObject <SPMySQLConnectionProxy> *proxy; + SPMySQLConnectionProxyState previousProxyState; + BOOL proxyStateChangeNotificationsIgnored; + + // Connection lock to prevent non-thread-safe query misuse + NSConditionLock *connectionLock; + + // Currently selected database + NSString *database; + + // Delegate connection lost decisions + NSUInteger reconnectionRetryAttempts; + SPMySQLConnectionLostDecision lastDelegateDecisionForLostConnection; + NSLock *delegateDecisionLock; + + // Timeout and keep-alive + NSUInteger timeout; + BOOL useKeepAlive; + SPMySQLKeepAliveTimer *keepAliveTimer; + CGFloat keepAliveInterval; + uint64_t lastKeepAliveTime; + NSUInteger keepAlivePingFailures; + pthread_t keepAlivePingThread; + BOOL keepAlivePingThreadActive; + BOOL keepAliveLastPingSuccess; + BOOL keepAliveLastPingBlocked; + + // Encoding details - and also a record of any previous encoding to allow + // switching back and forth + NSString *encoding; + NSStringEncoding stringEncoding; + BOOL encodingUsesLatin1Transport; + NSString *previousEncoding; + BOOL previousEncodingUsesLatin1Transport; + + // Server details + NSString *serverVersionString; + + // Error state for the last query or connection state + NSUInteger queryErrorID; + NSString *queryErrorMessage; + + // Query details + unsigned long long lastQueryAffectedRowCount; + unsigned long long lastQueryInsertID; + + // Query cancellation details + BOOL lastQueryWasCancelled; + BOOL lastQueryWasCancelledUsingReconnect; + + // Timing details + uint64_t lastConnectionUsedTime; + double lastQueryExecutionTime; + + // Maximum query size + NSUInteger maxQuerySize; + BOOL maxQuerySizeIsEditable; + BOOL maxQuerySizeEditabilityChecked; + NSUInteger queryActionShouldRestoreMaxQuerySize; + + // Queries + BOOL retryQueriesOnConnectionFailure; +} + +#pragma mark - +#pragma mark Synthesized properties + +@property (readwrite, assign, nonatomic) NSObject <SPMySQLConnectionDelegate> *delegate; +@property (readwrite, assign, nonatomic) NSObject <SPMySQLConnectionProxy> *proxy; + +@property (readwrite, retain) NSString *host; +@property (readwrite, retain) NSString *username; +@property (readwrite, retain) NSString *password; +@property (readwrite, assign) NSUInteger port; +@property (readwrite, assign) BOOL useSocket; +@property (readwrite, retain) NSString *socketPath; + +@property (readwrite, assign) BOOL useSSL; +@property (readwrite, retain) NSString *sslKeyFilePath; +@property (readwrite, retain) NSString *sslCertificatePath; +@property (readwrite, retain) NSString *sslCACertificatePath; + +@property (readwrite, assign) NSUInteger timeout; +@property (readwrite, assign) BOOL useKeepAlive; +@property (readwrite, assign) CGFloat keepAliveInterval; + +@property (readonly) unsigned long mysqlConnectionThreadId; +@property (readwrite, assign) BOOL retryQueriesOnConnectionFailure; + +@property (readwrite, assign) BOOL delegateQueryLogging; + +@property (readwrite, assign) BOOL lastQueryWasCancelled; + +#pragma mark - +#pragma mark Connection and disconnection + +- (BOOL)connect; +- (BOOL)reconnect; +- (void)disconnect; + +#pragma mark - +#pragma mark Connection state + +- (BOOL)isConnected; +- (BOOL)isConnectedViaSSL; +- (BOOL)checkConnection; +- (double)timeConnected; +- (BOOL)userTriggeredDisconnect; + +#pragma mark - +#pragma mark Connection utility + ++ (NSString *)findSocketPath; + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m new file mode 100644 index 00000000..6308a3f5 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m @@ -0,0 +1,834 @@ +// +// $Id$ +// +// SPMySQLConnection.m +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 8, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "SPMySQL Private APIs.h" +#import "SPMySQLKeepAliveTimer.h" +#include <mach/mach_time.h> +#include <pthread.h> +#include <SystemConfiguration/SCNetworkReachability.h> + + +#pragma mark Class constants + +// The default connection options for MySQL connections +const NSUInteger SPMySQLConnectionOptions = + CLIENT_COMPRESS | // Enable protocol compression - almost always a win + CLIENT_INTERACTIVE | // Mark ourselves as an interactive client + CLIENT_MULTI_RESULTS; // Multiple result support (very basic, but present) + +// List of permissible ciphers to use for SSL connections +const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RSA-AES128-SHA:AES128-SHA:AES256-RMD:AES128-RMD:DES-CBC3-RMD:DHE-RSA-AES256-RMD:DHE-RSA-AES128-RMD:DHE-RSA-DES-CBC3-RMD:RC4-SHA:RC4-MD5:DES-CBC3-SHA:DES-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC-SHA"; + + +@implementation SPMySQLConnection + +#pragma mark - +#pragma mark Synthesized properties + +@synthesize delegate; +@synthesize proxy; +@synthesize host; +@synthesize username; +@synthesize password; +@synthesize port; +@synthesize useSocket; +@synthesize socketPath; +@synthesize useSSL; +@synthesize sslKeyFilePath; +@synthesize sslCertificatePath; +@synthesize sslCACertificatePath; +@synthesize timeout; +@synthesize useKeepAlive; +@synthesize keepAliveInterval; +@synthesize mysqlConnectionThreadId; +@synthesize retryQueriesOnConnectionFailure; +@synthesize delegateQueryLogging; +@synthesize lastQueryWasCancelled; + +#pragma mark - +#pragma mark Initialisation and teardown + +/** + * Initialise the SPMySQLConnection object, setting up class defaults. + * + * Typically initialisation would be followed by setting the connection details + * and then calling -connect. + */ +- (id)init +{ + if ((self = [super init])) { + mySQLConnection = NULL; + state = SPMySQLDisconnected; + userTriggeredDisconnect = NO; + isReconnecting = NO; + mysqlConnectionThreadId = 0; + initialConnectTime = 0; + + port = 3306; + + // Default to socket connections if no other details have been provided + useSocket = YES; + + // Start with no proxy + proxy = nil; + proxyStateChangeNotificationsIgnored = NO; + + // Start with no selected database + database = nil; + + // Set a timeout of 30 seconds, with keepalive on and acting every sixty seconds + timeout = 30; + useKeepAlive = YES; + keepAliveInterval = 60; + keepAlivePingFailures = 0; + lastKeepAliveTime = 0; + keepAlivePingThread = NULL; + keepAlivePingThreadActive = NO; + keepAliveLastPingSuccess = NO; + keepAliveLastPingBlocked = NO; + + // Set up default encoding variables + encoding = [[NSString alloc] initWithString:@"utf8"]; + stringEncoding = NSUTF8StringEncoding; + encodingUsesLatin1Transport = NO; + previousEncoding = nil; + previousEncodingUsesLatin1Transport = NO; + + // Initialise default delegate settings + delegateSupportsWillQueryString = NO; + delegateSupportsConnectionLost = NO; + delegateQueryLogging = YES; + + // Delegate disconnection decisions + reconnectionRetryAttempts = 0; + lastDelegateDecisionForLostConnection = SPMySQLConnectionLostDisconnect; + delegateDecisionLock = [[NSLock alloc] init]; + + // Set up the connection lock + connectionLock = [[NSConditionLock alloc] initWithCondition:SPMySQLConnectionIdle]; + [connectionLock setName:@"SPMySQLConnection query lock"]; + + // Ensure the server detail records are initialised + serverVersionString = nil; + + // Start with a blank error state + queryErrorID = 0; + queryErrorMessage = nil; + + // Start with empty cancellation details + lastQueryWasCancelled = NO; + lastQueryWasCancelledUsingReconnect = NO; + + // Empty or reset the timing variables + lastConnectionUsedTime = 0; + lastQueryExecutionTime = 0; + + // Default to editable query size of 1MB + maxQuerySize = 1048576; + maxQuerySizeIsEditable = YES; + maxQuerySizeEditabilityChecked = NO; + queryActionShouldRestoreMaxQuerySize = NSNotFound; + + // Default to allowing queries to be automatically retried if the connection drops + // while running them + retryQueriesOnConnectionFailure = YES; + + // Start the ping keepalive timer + keepAliveTimer = [[SPMySQLKeepAliveTimer alloc] initWithInterval:10 target:self selector:@selector(_keepAlive)]; + } + + return self; +} + +/** + * Object deallocation. + */ +- (void) dealloc +{ + userTriggeredDisconnect = YES; + + // Unset the delegate + [self setDelegate:nil]; + + // Clear the keepalive timer + [keepAliveTimer invalidate]; + [keepAliveTimer release]; + + // Disconnect if appropriate (which should also disconnect any proxy) + [self disconnect]; + + // Clean up the connection proxy, if any + if (proxy) { + [proxy setConnectionStateChangeSelector:NULL delegate:nil]; + [proxy release]; + } + + // Ensure the query lock is unlocked, thereafter setting to nil in case of pending calls + if ([connectionLock condition] != SPMySQLConnectionIdle) { + [self _unlockConnection]; + } + [connectionLock release], connectionLock = nil; + + [encoding release]; + if (previousEncoding) [previousEncoding release], previousEncoding = nil; + + if (database) [database release], database = nil; + if (serverVersionString) [serverVersionString release], serverVersionString = nil; + if (queryErrorMessage) [queryErrorMessage release], queryErrorMessage = nil; + [delegateDecisionLock release]; + + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + + [super dealloc]; +} + +#pragma mark - +#pragma mark Connection and disconnection + +/** + * Trigger a connection to the specified host, if any, using any connection details + * that have been set. + * Returns whether the connection was successful. + */ +- (BOOL)connect +{ + + // If a connection is already active in some form, throw an exception + if (state != SPMySQLDisconnected) { + [NSException raise:NSInternalInconsistencyException format:@"Attempted to connect a connection that is not disconnected."]; + return NO; + } + state = SPMySQLConnecting; + + // Lock the connection for safety + [self _lockConnection]; + + // Attempt the connection + mySQLConnection = [self _makeRawMySQLConnectionWithEncoding:encoding isMasterConnection:YES]; + + // If the connection failed, reset state and return + if (!mySQLConnection) { + [self _unlockConnection]; + state = SPMySQLDisconnected; + return NO; + } + + // Successfully connected - record connected state and reset tracking variables + state = SPMySQLConnected; + userTriggeredDisconnect = NO; + reconnectionRetryAttempts = 0; + initialConnectTime = mach_absolute_time(); + mysqlConnectionThreadId = mySQLConnection->thread_id; + lastConnectionUsedTime = 0; + + // Update SSL state + connectedWithSSL = NO; + if (useSSL) connectedWithSSL = (mysql_get_ssl_cipher(mySQLConnection))?YES:NO; + if (useSSL && !connectedWithSSL) { + if ([delegate respondsToSelector:@selector(connectionFellBackToNonSSL:)]) { + [delegate connectionFellBackToNonSSL:self]; + } + } + + // Reset keepalive variables + lastKeepAliveTime = 0; + keepAlivePingFailures = 0; + + // Clear the connection error record + [self _updateLastErrorID:NSNotFound]; + [self _updateLastErrorMessage:nil]; + + // Unlock the connection + [self _unlockConnection]; + + // Update connection variables to be in sync with the server state. As this performs + // a query, ensure the connection is still up afterwards (!) + [self _updateConnectionVariables]; + if (state != SPMySQLConnected) return NO; + + // Update the maximum query size + [self _updateMaxQuerySize]; + + return YES; +} + +/** + * Reconnect to the currently "active" - but possibly disconnected - connection, using the + * stored details. + * Error checks extensively - if this method fails, it will ask how to proceed and loop depending + * on the status, not returning control until either a connection has been established or + * the connection and document have been closed. + * Runs its own autorelease pool as sometimes called in a thread following proxy changes + * (where the return code doesn't matter). + */ +- (BOOL)reconnect +{ + if (userTriggeredDisconnect) return NO; + + NSAutoreleasePool *reconnectionPool = [[NSAutoreleasePool alloc] init]; + + // Check whether a reconnection attempt is already being made - if so, wait + // and return the status of that reconnection attempt. This improves threaded + // use of the connection by preventing reconnect races. + if (isReconnecting) { + + // Loop in a panel runloop mode until the reconnection has processed; if an iteration + // takes less than the requested 0.1s, sleep instead. + while (isReconnecting) { + uint64_t loopIterationStart_t = mach_absolute_time(); + + [[NSRunLoop currentRunLoop] runMode:NSModalPanelRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + if (_elapsedSecondsSinceAbsoluteTime(loopIterationStart_t) < 0.1) { + usleep(100000 - (useconds_t)(1000000 * _elapsedSecondsSinceAbsoluteTime(loopIterationStart_t))); + } + } + + [reconnectionPool drain]; + return (state == SPMySQLConnected); + } + + isReconnecting = YES; + + // Store certain details about the connection, so that if the reconnection is successful + // they can be restored. This has to be treated separately from _restoreConnectionDetails + // as a full connection reinitialises certain values from the server. + NSString *preReconnectEncoding = [NSString stringWithString:encoding]; + BOOL preReconnectEncodingUsesLatin1 = encodingUsesLatin1Transport; + NSString *preReconnectDatabase = nil; + if (database) preReconnectDatabase = [NSString stringWithString:database]; + + // If there is a connection proxy, temporarily disassociate the state change action + if (proxy) proxyStateChangeNotificationsIgnored = YES; + + // Close the connection if it's active + [self disconnect]; + + // Lock the connection while waiting for network and proxy + [self _lockConnection]; + + // If no network is present, wait for a short time for one to become available + [self _waitForNetworkConnectionWithTimeout:10]; + + // If there is a proxy, attempt to reconnect it in blocking fashion + if (proxy) { + uint64_t loopIterationStart_t, proxyWaitStart_t; + + // If the proxy is not yet idle after requesting a disconnect, wait for a short time + // to allow it to disconnect. + if ([proxy state] != SPMySQLProxyIdle) { + + proxyWaitStart_t = mach_absolute_time(); + while ([proxy state] != SPMySQLProxyIdle) { + loopIterationStart_t = mach_absolute_time(); + + // If the connection timeout has passed, break out of the loop + if (_elapsedSecondsSinceAbsoluteTime(proxyWaitStart_t) > timeout) break; + + // Allow events to process for 0.25s, sleeping to completion on early return + [[NSRunLoop currentRunLoop] runMode:NSModalPanelRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.25]]; + if (_elapsedSecondsSinceAbsoluteTime(loopIterationStart_t) < 0.25) { + usleep(250000 - (useconds_t)(1000000 * _elapsedSecondsSinceAbsoluteTime(loopIterationStart_t))); + } + } + } + + // Request that the proxy re-establishes its connection + [proxy connect]; + + // Wait while the proxy connects + proxyWaitStart_t = mach_absolute_time(); + while (1) { + loopIterationStart_t = mach_absolute_time(); + + // If the proxy has connected, record the new local port and break out of the loop + if ([proxy state] == SPMySQLProxyConnected) { + port = [proxy localPort]; + break; + } + + // If the proxy connection attempt time has exceeded the timeout, break of of the loop. + if (_elapsedSecondsSinceAbsoluteTime(proxyWaitStart_t) > (timeout + 1)) { + [proxy disconnect]; + break; + } + + // Process events for a short time, allowing dialogs to be shown but waiting for + // the proxy. Capture how long this interface action took, standardising the + // overall time. + [[NSRunLoop mainRunLoop] runMode:NSModalPanelRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.25]]; + if (_elapsedSecondsSinceAbsoluteTime(loopIterationStart_t) < 0.25) { + usleep((useconds_t)(250000 - (1000000 * _elapsedSecondsSinceAbsoluteTime(loopIterationStart_t)))); + } + + // Extend the connection timeout by any interface time + if ([proxy state] == SPMySQLProxyWaitingForAuth) { + proxyWaitStart_t += mach_absolute_time() - loopIterationStart_t; + } + } + + // Having in theory performed the proxy connect, update state + previousProxyState = [proxy state]; + proxyStateChangeNotificationsIgnored = NO; + } + + // Unlock the connection + [self _unlockConnection]; + + // If not using a proxy, or if the proxy successfully connected, trigger a connection + if (!proxy || [proxy state] == SPMySQLProxyConnected) { + [self connect]; + } + + // If the connection failed, retry the reconnection or cancel as appropriate. + if (state != SPMySQLConnected) { + + // Default to attempting another reconnect + SPMySQLConnectionLostDecision connectionLostDecision = SPMySQLConnectionLostReconnect; + + // If the delegate supports the decision process, ask it how to proceed + if (delegateSupportsConnectionLost) { + connectionLostDecision = [self _delegateDecisionForLostConnection]; + + // Otherwise default to reconnect, but only a set number of times to prevent a runaway loop + } else { + if (reconnectionRetryAttempts < 5) { + connectionLostDecision = SPMySQLConnectionLostReconnect; + } else { + connectionLostDecision = SPMySQLConnectionLostDisconnect; + } + reconnectionRetryAttempts++; + } + + switch (connectionLostDecision) { + case SPMySQLConnectionLostDisconnect: + [self _updateLastErrorMessage:NSLocalizedString(@"User triggered disconnection", @"User triggered disconnection")]; + userTriggeredDisconnect = YES; + isReconnecting = NO; + [reconnectionPool release]; + return NO; + default: + isReconnecting = NO; + [reconnectionPool release]; + return [self reconnect]; + } + } + + // If the connection was successfully established, restore the connection + // state if appropriate. + if (preReconnectDatabase) { + [self selectDatabase:preReconnectDatabase]; + } + [self setEncoding:preReconnectEncoding]; + [self setEncodingUsesLatin1Transport:preReconnectEncodingUsesLatin1]; + + isReconnecting = NO; + [reconnectionPool release]; + return YES; +} + +/** + * Trigger a disconnection if the connection is currently active. + */ +- (void)disconnect +{ + + // Only continue if a connection is active + if (state != SPMySQLConnected && state != SPMySQLConnecting) return; + state = SPMySQLDisconnecting; + + // If a query is active, cancel it + [self cancelCurrentQuery]; + + // Allow any pings or cancelled queries to complete, inside a time limit of ten seconds + uint64_t disconnectStartTime_t = mach_absolute_time(); + do { + usleep(100000); + if (_elapsedSecondsSinceAbsoluteTime(disconnectStartTime_t) > 10) break; + } while (![self _tryLockConnection]); + [self _unlockConnection]; + if (keepAlivePingThread != NULL) pthread_cancel(keepAlivePingThread), keepAlivePingThread = NULL; + + // Close the underlying MySQL connection if it still appears to be active, and not reading + // or writing. While this may result in a leak of the MySQL object, it prevents crashes + // due to attempts to close a blocked/stuck connection. + if (!mySQLConnection->net.reading_or_writing && mySQLConnection->net.vio && mySQLConnection->net.buff) { + mysql_close(mySQLConnection); + } + mySQLConnection = NULL; + + // If using a connection proxy, disconnect that too + if (proxy) { + [proxy performSelectorOnMainThread:@selector(disconnect) withObject:nil waitUntilDone:YES]; + } + + // Clear host-specific information + if (serverVersionString) [serverVersionString release], serverVersionString = nil; + if (database) [database release], database = nil; + + state = SPMySQLDisconnected; +} + +#pragma mark - +#pragma mark Connection state + +/** + * Retrieve whether the connection instance is connected to the remote host. + * Returns NO if the connection is still in process, YES if a disconnection is + * being actively performed. + */ +- (BOOL)isConnected +{ + return (state == SPMySQLConnected || state == SPMySQLDisconnecting); +} + +/** + * Returns YES if the MCPConnection is connected to a server via SSL, NO otherwise. + */ +- (BOOL)isConnectedViaSSL +{ + if (![self isConnected]) return NO; + return connectedWithSSL; +} + +/** + * Checks whether the connection to the server is still active. This verifies + * the connection using a ping, and if the connection is found to be down attempts + * to quickly restore it, including the previous state. + */ +- (BOOL)checkConnection +{ + + // If the connection is not seen as active, don't proceed + if (state != SPMySQLConnected) return NO; + + // Similarly, if the connection is currently locked, that indicates it's in use. This + // could be because queries are actively being run, or that a ping is running. + if ([connectionLock condition] == SPMySQLConnectionBusy) { + + // If a ping thread is not active queries are being performed - return success. + if (!keepAlivePingThreadActive) return YES; + + // If a ping thread is active, wait for it to complete before checking the connection + while (keepAlivePingThreadActive) { + usleep(10000); + } + } + + // Confirm whether the connection is still responding by using a ping + BOOL connectionVerified = [self _pingConnectionUsingLoopDelay:400]; + + // If the connection didn't respond, trigger a reconnect. This will automatically + // attempt to reconnect once, and if that fails will ask the user how to proceed - whether + // to keep reconnecting, or whether to disconnect. + if (!connectionVerified) { + connectionVerified = [self reconnect]; + } + + return connectionVerified; +} + +/** + * Retrieve the time elapsed since the connection was established, in seconds. + * This time is retrieved in a monotonically increasing fashion and is high + * precision; it is used internally for query timing, and is reset on reconnections. + * If no connection is currently active, returns -1. + */ +- (double)timeConnected +{ + if (initialConnectTime == 0) return -1; + + return _elapsedSecondsSinceAbsoluteTime(initialConnectTime); +} + +/** + * Returns YES if the user chose to disconnect at the last "connection failure" + * prompt, NO otherwise. This can be used to alter behaviour in response to state + * changes. + */ +- (BOOL)userTriggeredDisconnect +{ + return userTriggeredDisconnect; +} + +#pragma mark - +#pragma mark General connection utilities + ++ (NSString *)findSocketPath +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + + NSArray *possibleSocketLocations = [NSArray arrayWithObjects: + @"/tmp/mysql.sock", // Default + @"/Applications/MAMP/tmp/mysql/mysql.sock", // MAMP default location + @"/Applications/xampp/xamppfiles/var/mysql/mysql.sock", // XAMPP default location + @"/var/mysql/mysql.sock", // Mac OS X Server default + @"/opt/local/var/run/mysqld/mysqld.sock", // Darwinports MySQL + @"/opt/local/var/run/mysql4/mysqld.sock", // Darwinports MySQL 4 + @"/opt/local/var/run/mysql5/mysqld.sock", // Darwinports MySQL 5 + @"/usr/local/zend/mysql/tmp/mysql.sock", // Zend Server CE (see Issue #1251) + @"/var/run/mysqld/mysqld.sock", // As used on Debian/Gentoo + @"/var/tmp/mysql.sock", // As used on FreeBSD + @"/var/lib/mysql/mysql.sock", // As used by Fedora + @"/opt/local/lib/mysql/mysql.sock", // Alternate fedora + nil]; + + for (NSUInteger i = 0; i < [possibleSocketLocations count]; i++) { + if ([fileManager fileExistsAtPath:[possibleSocketLocations objectAtIndex:i]]) + return [possibleSocketLocations objectAtIndex:i]; + } + + return nil; +} + +@end + +#pragma mark - +#pragma mark Private API + +@implementation SPMySQLConnection (PrivateAPI) + +/** + * Make a connection using the class connection settings, returning a MySQL + * connection object on success. + */ +- (MYSQL *)_makeRawMySQLConnectionWithEncoding:(NSString *)encodingName isMasterConnection:(BOOL)isMaster +{ + + // Set up the MySQL connection object + MYSQL *theConnection = mysql_init(NULL); + if (!theConnection) return NULL; + + // Disable automatic reconnection, as it's handled in-framework to preserve + // options, encodings and connection state. + my_bool falseMyBool = FALSE; + mysql_options(theConnection, MYSQL_OPT_RECONNECT, &falseMyBool); + + // Set the connection timeout + mysql_options(theConnection, MYSQL_OPT_CONNECT_TIMEOUT, (const void *)&timeout); + + // Set the connection encoding + mysql_options(theConnection, MYSQL_SET_CHARSET_NAME, [encodingName UTF8String]); + + // Set up the connection variables in the format MySQL needs, from the class-wide variables + const char *theHost = NULL; + const char *theUsername = ""; + const char *thePassword = NULL; + const char *theSocket = NULL; + + if (host) theHost = [self _cStringForString:host]; + if (username) theUsername = [self _cStringForString:username]; + + // If a password was supplied, use it; otherwise ask the delegate if appropriate + if (password) { + thePassword = [self _cStringForString:password]; + } else if ([delegate respondsToSelector:@selector(keychainPasswordForConnection:)]) { + thePassword = [self _cStringForString:[delegate keychainPasswordForConnection:self]]; + } + + // If set to use a socket and a socket was supplied, use it; otherwise, search for a socket to use + if (useSocket) { + if ([socketPath length]) { + theSocket = [self _cStringForString:socketPath]; + } else { + theSocket = [self _cStringForString:[SPMySQLConnection findSocketPath]]; + } + } + + // Apply SSL if appropriate + if (useSSL) { + const char *theSSLKeyFilePath = NULL; + const char *theSSLCertificatePath = NULL; + const char *theCACertificatePath = NULL; + + if (sslKeyFilePath) { + theSSLKeyFilePath = [[sslKeyFilePath stringByExpandingTildeInPath] UTF8String]; + } + if (sslCertificatePath) { + theSSLCertificatePath = [[sslCertificatePath stringByExpandingTildeInPath] UTF8String]; + } + if (sslCACertificatePath) { + theCACertificatePath = [[sslCACertificatePath stringByExpandingTildeInPath] UTF8String]; + } + + mysql_ssl_set(theConnection, theSSLKeyFilePath, theSSLCertificatePath, theCACertificatePath, NULL, SPMySQLSSLPermissibleCiphers); + } + + MYSQL *connectionStatus = mysql_real_connect(theConnection, theHost, theUsername, thePassword, NULL, (unsigned int)port, theSocket, SPMySQLConnectionOptions); + + // If the connection failed, return NULL + if (theConnection != connectionStatus) { + + // If the connection is the master connection, record the error state + if (isMaster) { + [self _updateLastErrorMessage:[self _stringForCString:mysql_error(theConnection)]]; + [self _updateLastErrorID:mysql_errno(theConnection)]; + } + + return NULL; + } + + // Ensure automatic reconnection is disabled for older versions + theConnection->reconnect = 0; + + // Successful connection - return the handle + return theConnection; +} + +/** + * Loop while a connection isn't available; allows blocking while the network is disconnected + * or still connecting (eg Airport still coming up after sleep). + */ +- (BOOL)_waitForNetworkConnectionWithTimeout:(double)timeoutSeconds +{ + BOOL hostReachable; + Boolean flagsValid; + SCNetworkReachabilityRef reachabilityTarget; + SCNetworkConnectionFlags reachabilityStatus; + + // Set up the reachability target - the host is not important, and is not connected to. + reachabilityTarget = SCNetworkReachabilityCreateWithName(NULL, "dev.mysql.com"); + + // In a loop until success or the timeout, test reachability + uint64_t loopStart_t = mach_absolute_time(); + while (1) { + + // Check reachability + flagsValid = SCNetworkReachabilityGetFlags(reachabilityTarget, &reachabilityStatus); + + hostReachable = flagsValid ? YES : NO; + + // Ensure that the network is reachable + if (hostReachable && !(reachabilityStatus & kSCNetworkFlagsReachable)) hostReachable = NO; + + // Ensure that Airport is up/connected if present + if (hostReachable && (reachabilityStatus & kSCNetworkFlagsConnectionRequired)) hostReachable = NO; + + // If the host *is* reachable, return success + if (hostReachable) return YES; + + // If the timeout has been exceeded, break out of the loop + if (_elapsedSecondsSinceAbsoluteTime(loopStart_t) >= timeoutSeconds) break; + + // Sleep before the next loop iteration + usleep(250000); + } + + // All checks failed - return failure + return NO; +} + +/** + * Update connection variables from the server, collecting state and ensuring + * settings like encoding are in sync. + */ +- (void)_updateConnectionVariables +{ + if (state != SPMySQLConnected && state != SPMySQLConnecting) return; + + // Retrieve all variables from the server in a single query + SPMySQLResult *theResult = [self queryString:@"SHOW VARIABLES"]; + if (![theResult numberOfRows]) return; + + // SHOW VARIABLES can return binary results on certain MySQL 4 versions; ensure string output + [theResult setReturnDataAsStrings:YES]; + + // Convert the result set into a variables dictionary + [theResult setDefaultRowReturnType:SPMySQLResultRowAsArray]; + NSMutableDictionary *variables = [NSMutableDictionary new]; + for (NSArray *variableRow in theResult) { + [variables setObject:[variableRow objectAtIndex:1] forKey:[variableRow objectAtIndex:0]]; + } + + // Copy the server version string to the instance variable + if (serverVersionString) [serverVersionString release], serverVersionString = nil; + serverVersionString = [[variables objectForKey:@"version"] retain]; + + // Get the connection encoding. Although a specific encoding may have been requested on + // connection, it may be overridden by init_connect commands or connection state changes. + // Default to latin1 for older server versions. + NSString *retrievedEncoding = @"latin1"; + if ([variables objectForKey:@"character_set_results"]) { + retrievedEncoding = [variables objectForKey:@"character_set_results"]; + } else if ([variables objectForKey:@"character_set"]) { + retrievedEncoding = [variables objectForKey:@"character_set"]; + } + + // Update instance variables + if (encoding) [encoding release]; + encoding = [[NSString alloc] initWithString:retrievedEncoding]; + stringEncoding = [SPMySQLConnection stringEncodingForMySQLCharset:[self _cStringForString:encoding]]; + encodingUsesLatin1Transport = NO; + + // Check the interactive timeout - if it's below five minutes, increase it to ten + // to imprive timeout/keepalive behaviour + if ([variables objectForKey:@"interactive_timeout"]) { + if ([[variables objectForKey:@"interactive_timeout"] integerValue] < 300) { + [self queryString:@"SET interactive_timeout=600"]; + } + } + + [variables release]; +} + +/** + * Restore the connection encoding details as necessary based on previously set + * details. + */ +- (void)_restoreConnectionVariables +{ + mysqlConnectionThreadId = mySQLConnection->thread_id; + initialConnectTime = mach_absolute_time(); + + [self selectDatabase:database]; + + [self setEncoding:encoding]; + [self setEncodingUsesLatin1Transport:encodingUsesLatin1Transport]; +} + +/** + * If thirty seconds have passed since the last time the connection was + * used, check the connection. + * This minimises the impact of continuous additional connection checks - + * each of which requires a round trip to the server - but handles most + * network issues. + * Returns whether the connection is considered still valid. + */ +- (BOOL)_checkConnectionIfNecessary +{ + + // If the connection was recently used, return success + if (_elapsedSecondsSinceAbsoluteTime(lastConnectionUsedTime) < 30) return YES; + + // Otherwise check the connection + return [self checkConnection]; +} +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnectionDelegate.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnectionDelegate.h new file mode 100644 index 00000000..97e94170 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnectionDelegate.h @@ -0,0 +1,108 @@ +// +// $Id$ +// +// SPMySQLConnectionDelegate.h +// SPMySQLFramework +// +// Created by Stuart Connolly (stuconnolly.com) on October 20, 2010. +// Copyright (c) 2010 Stuart Connolly. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "SPMySQLConstants.h" + +@protocol SPMySQLConnectionDelegate <NSObject> +@optional + +/** + * Notifies the delegate that a query will be performed. + * + * @param query The query string that will be sent to the MySQL server + * @param connection The connection instance performing the query + */ +- (void)willQueryString:(NSString *)query connection:(id)connection; + +/** + * Notifies the delegate that a query that was just performed gave + * an error. + * + * @param error The query error, as a string + * @param connection The connection instance which received the error + */ +- (void)queryGaveError:(NSString *)error connection:(id)connection; + +/** + * Notifies the delegate that it should display the supplied error. + * The connection may sometimes want to notify the user directly + * about certain issues, and will use this method to allow the + * delegate to do so. + * + * @param title The title of the message to display to the user + * @param message The main text of the message to display to the user + */ +- (void)showErrorWithTitle:(NSString *)title message:(NSString *)message; + +/** + * Requests the keychain password for the connection. + * When a connection is being made to a server, it is best not to + * set the password on the class; instead, it should be kept within + * the secure store, and the other connection details (user, host) + * can be used to look it up and supplied on demand. + * + * @param connection The connection instance to supply the password for + */ +- (NSString *)keychainPasswordForConnection:(id)connection; + +/** + * Notifies the delegate that no underlying connection is available, + * typically when the connection has been asked to perform a query + * or some other action for which a connection must be present. + * Those actions will still return false or error states as appropriate, + * but the delegate may wish to perform actions as a result of a total + * loss of connection. + * + * @param connection The connection instance which has lost the connection to the host + */ +- (void)noConnectionAvailable:(id)connection; + +/** + * Notifies the delegate that although a SSL connection was requested, + * MySQL made the connection without using SSL. This can happen because + * the server connected to doesn't support SSL or had it disabled, or + * that insufficient details were provided to make the connection over + * SSL. + */ +- (void)connectionFellBackToNonSSL:(id)connection; + +/** + * Notifies the delegate that the connection has been temporarily lost, + * and asks the delegate for guidance on how to proceed. If the delegate + * does not implement this method, reconnections will automatically be + * attempted - up to a small limit of attempts. + * + * @param connection The connection instance that requires a decision on how to proceed + */ +- (SPMySQLConnectionLostDecision)connectionLost:(id)connection; + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnectionProxy.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnectionProxy.h new file mode 100644 index 00000000..2da553c6 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnectionProxy.h @@ -0,0 +1,77 @@ +// +// $Id$ +// +// SPMySQLConnectionProxy.h +// SPMySQLFramework +// +// Created by Stuart Connolly (stuconnolly.com) on July 2, 2009. +// Copyright (c) 2009 Stuart Connolly. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +/** + * Connection proxy state constants. + */ +typedef enum { + SPMySQLProxyIdle = 0, + SPMySQLProxyConnecting = 1, + SPMySQLProxyWaitingForAuth = 2, + SPMySQLProxyConnected = 3, + SPMySQLProxyForwardingFailed = 4 +} SPMySQLConnectionProxyState; + + +@protocol SPMySQLConnectionProxy <NSObject> + +/** + * All the methods for this protocol are required. + */ + +/** + * Connect the proxy. + */ +- (void)connect; + +/** + * Disconnect the proxy. + */ +- (void)disconnect; + +/** + * Get the current state of the proxy. + */ +- (SPMySQLConnectionProxyState)state; + +/** + * Get the local port being provided by the proxy. + */ +- (NSUInteger)localPort; + +/** + * Sets the method the proxy should call whenever the state of the connection changes. + */ +- (BOOL)setConnectionStateChangeSelector:(SEL)theStateChangeSelector delegate:(id)theDelegate; + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConstants.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConstants.h new file mode 100644 index 00000000..b1689569 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConstants.h @@ -0,0 +1,76 @@ +// +// $Id$ +// +// SPMySQLConstants.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 14, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +// Connection state +typedef enum { + SPMySQLDisconnected = 0, + SPMySQLConnecting = 1, + SPMySQLConnected = 2, + SPMySQLDisconnecting = 3 +} SPMySQLConnectionState; + +// Connection lock state +typedef enum { + SPMySQLConnectionIdle = 0, + SPMySQLConnectionBusy = 1 +} SPMySQLConnectionLockState; + +// Decision on how to handle lost connections +// Connection check constants +typedef enum { + SPMySQLConnectionLostDisconnect = 0, + SPMySQLConnectionLostReconnect = 1 +} SPMySQLConnectionLostDecision; + +// Result set row types +typedef enum { + SPMySQLResultRowAsDefault = 0, + SPMySQLResultRowAsArray = 1, + SPMySQLResultRowAsDictionary = 2 +} SPMySQLResultRowType; + +// Result charset list +typedef struct { + NSUInteger nr; + const char *name; + const char *collation; + NSUInteger char_minlen; + NSUInteger char_maxlen; +} SPMySQLResultCharset; + +// Query result types +typedef enum { + SPMySQLResultAsResult = 0, + SPMySQLResultAsFastStreamingResult = 1, + SPMySQLResultAsLowMemStreamingResult = 2 +} SPMySQLResultType; diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.h b/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.h new file mode 100644 index 00000000..a4f07cdd --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.h @@ -0,0 +1,47 @@ +// +// $Id$ +// +// SPMySQLFastStreamingResult.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 2, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +@interface SPMySQLFastStreamingResult : SPMySQLStreamingResult { + + // Linked list setup + struct st_spmysqlstreamingrowdata *currentDataStoreEntry; + struct st_spmysqlstreamingrowdata *lastDataStoreEntry; + + // Additional counts and memory length tracking + NSUInteger processedRowCount; + + // Thread safety + pthread_mutex_t dataLock; +} + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m b/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m new file mode 100644 index 00000000..8ba55134 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m @@ -0,0 +1,405 @@ +// +// $Id$ +// +// SPMySQLFastStreamingResult.m +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 2, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "SPMySQLFastStreamingResult.h" +#import "SPMySQL Private APIs.h" +#import "SPMySQLArrayAdditions.h" +#include <pthread.h> + +static id NSNullPointer; + +/** + * This type of streaming result operates in a multithreaded fashion - a worker + * thread is set up to download the results as fast as possible in the background, + * while the results are made available via blocking (and so single-thread-compatible) + * calls. This provides the benefit of allowing a progress bar to be shown during + * downloads, and threaded processing, but still has reasonable memory usage for the + * downloaded result - and won't block the server. + */ + +typedef struct st_spmysqlstreamingrowdata { + char *data; + unsigned long *dataLengths; + struct st_spmysqlstreamingrowdata *nextRow; +} SPMySQLStreamingRowData; + +@interface SPMySQLFastStreamingResult (Private_API) + +- (void) _downloadAllData; + +@end + +#pragma mark - + +@implementation SPMySQLFastStreamingResult + +#pragma mark - + +/** + * In the one-off class initialisation, cache static variables + */ ++ (void)initialize +{ + + // Cached NSNull singleton reference + if (!NSNullPointer) NSNullPointer = [NSNull null]; +} + +/** + * Standard init method, constructing the SPMySQLStreamingResult around a MySQL + * result pointer and the encoding to use when working with the data. + * As opposed to SPMySQLResult, defaults to returning rows as arrays, as the result + * sets are likely to be larger and processed in loops. + */ +- (id)initWithMySQLResult:(void *)theResult stringEncoding:(NSStringEncoding)theStringEncoding connection:(SPMySQLConnection *)theConnection +{ + + // If no result set was passed in, return nil. + if (!theResult) return nil; + + if ((self = [super initWithMySQLResult:theResult stringEncoding:theStringEncoding connection:theConnection])) { + + // Initialise the extra streaming result counts and tracking + processedRowCount = 0; + + // Initialise the linked list pointers + currentDataStoreEntry = NULL; + lastDataStoreEntry = NULL; + + // Set up the linked list lock + pthread_mutex_init(&dataLock, NULL); + + // Start the data download thread + [NSThread detachNewThreadSelector:@selector(_downloadAllData) toTarget:self withObject:nil]; + } + + return self; +} + +/** + * Deallocate the result and ensure the parent connection is unlocked for further use. + */ +- (void)dealloc +{ + + // Ensure all data is processed and the parent connection is unlocked + [self cancelResultLoad]; + + // Destroy the linked list lock + pthread_mutex_destroy(&dataLock); + + // Call dealloc on super to clean up everything else, and to throw an exception if + // the parent connection hasn't been cleaned up correctly. + [super dealloc]; +} + +#pragma mark - +#pragma mark Data retrieval + +/** + * Override the convenience selectors so that forwarding works correctly. + */ +- (id)getRow +{ + return SPMySQLResultGetRow(self, SPMySQLResultRowAsDefault); +} +- (NSArray *)getRowAsArray +{ + return SPMySQLResultGetRow(self, SPMySQLResultRowAsArray); +} +- (NSDictionary *)getRowAsDictionary +{ + return SPMySQLResultGetRow(self, SPMySQLResultRowAsDictionary); +} + +/** + * Retrieve the next row in the result set, using the internal pointer, in the specified + * return format. + * If there are no rows remaining in the current iteration, returns nil. + */ +- (id)getRowAsType:(SPMySQLResultRowType)theType +{ + NSUInteger copiedDataLength = 0; + char *theRowData; + unsigned long *fieldLengths; + id theReturnData; + + // If the target type was unspecified, use the instance default + if (theType == SPMySQLResultRowAsDefault) theType = defaultRowReturnType; + + // Set up the return data as appropriate + if (theType == SPMySQLResultRowAsArray) { + theReturnData = [NSMutableArray arrayWithCapacity:numberOfFields]; + } else { + theReturnData = [NSMutableDictionary dictionaryWithCapacity:numberOfFields]; + } + + // Lock the data mutex for safe access of variables and counters + pthread_mutex_lock(&dataLock); + + // Determine whether any data is available; if not, wait 1ms before trying again + while (!dataDownloaded && processedRowCount == downloadedRowCount) { + pthread_mutex_unlock(&dataLock); + usleep(1000); + pthread_mutex_lock(&dataLock); + } + + // If all rows have been processed, the end of the result set has been reached; return nil. + if (processedRowCount == downloadedRowCount) { + pthread_mutex_unlock(&dataLock); + return nil; + } + + // Unlock the data mutex now checks are complete + pthread_mutex_unlock(&dataLock); + + // Get a reference to the data for the current row; this is safe to do outside the lock + // as the pointer won't change until markers are changed at the end of this process + theRowData = currentDataStoreEntry->data; + fieldLengths = currentDataStoreEntry->dataLengths; + + // Convert each of the cells in the row in turn + unsigned long fieldLength; + id cellData; + char *rawCellData; + for (NSUInteger i = 0; i < numberOfFields; i++) { + fieldLength = fieldLengths[i]; + + // If the length of this cell is NSNotFound, it's a null reference + if (fieldLength == NSNotFound) { + cellData = nil; + + // Otherwise grab a reference to that data using pointer arithmetic + } else { + rawCellData = theRowData + copiedDataLength; + copiedDataLength += fieldLength; + + // Convert to the correct object type + cellData = SPMySQLResultGetObject(self, rawCellData, fieldLength, fieldTypes[i], i); + } + + // If object creation failed, display a null + if (!cellData) cellData = NSNullPointer; + + // Add to the result array/dictionary + if (theType == SPMySQLResultRowAsArray) { + SPMySQLMutableArrayInsertObject(theReturnData, cellData, i); + } else { + [(NSMutableDictionary *)theReturnData setObject:cellData forKey:fieldNames[i]]; + } + } + + // Get a reference to the current item + SPMySQLStreamingRowData *previousDataStoreEntry = currentDataStoreEntry; + + // Lock the mutex before updating counters and linked lists + pthread_mutex_lock(&dataLock); + + // Update the active-data pointer to the next item in the list (which may be NULL) + currentDataStoreEntry = currentDataStoreEntry->nextRow; + if (!currentDataStoreEntry) lastDataStoreEntry = NULL; + + // Increment the processed counter and row index + processedRowCount++; + currentRowIndex++; + if (dataDownloaded && processedRowCount == downloadedRowCount) currentRowIndex = NSNotFound; + + // Unlock the mutex + pthread_mutex_unlock(&dataLock); + + // Free the memory for the processed row + free(previousDataStoreEntry->dataLengths); + if (previousDataStoreEntry->data != NULL) free(previousDataStoreEntry->data); + free(previousDataStoreEntry); + + return theReturnData; +} + +/* + * Ensure the result set is fully processed and freed without any processing + * This method ensures that the connection is unlocked. + */ +- (void)cancelResultLoad +{ + + // If data has already been downloaded successfully, no further action is required + if (dataDownloaded && processedRowCount == downloadedRowCount) return; + + // Loop until all data is fetched and freed + while (1) { + + // Check to see whether we need to wait for the data to be available + // - if so, wait 1ms before checking again + while (!dataDownloaded && processedRowCount == downloadedRowCount) usleep(1000); + + // If all rows have been processed, we're at the end of the result set - return + if (processedRowCount == downloadedRowCount) { + + // We don't need to unlock the connection because the data loading thread + // has already taken care of that + return; + } + + // Mark the row entry as processed without performing any actions + pthread_mutex_lock(&dataLock); + SPMySQLStreamingRowData *previousDataStoreEntry = currentDataStoreEntry; + + // Update the active-data pointer to the next item in the list (which may be NULL) + currentDataStoreEntry = currentDataStoreEntry->nextRow; + if (!currentDataStoreEntry) lastDataStoreEntry = NULL; + + processedRowCount++; + currentRowIndex++; + if (dataDownloaded && processedRowCount == downloadedRowCount) currentRowIndex = NSNotFound; + + // Unlock the mutex + pthread_mutex_unlock(&dataLock); + + // Free the memory for the processed row + free(previousDataStoreEntry->dataLengths); + if (previousDataStoreEntry->data != NULL) free(previousDataStoreEntry->data); + free(previousDataStoreEntry); + } +} + +#pragma mark - +#pragma mark Data retrieval for fast enumeration + +/** + * Implement the fast enumeration endpoint. Rows for fast enumeration are retrieved in + * the instance default, as specified in setDefaultRowReturnType: or defaulting to + * NSDictionary. + */ +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len +{ + + // To avoid lock issues, return one row at a time. + id nextRow = SPMySQLResultGetRow(self, SPMySQLResultRowAsDefault); + + // If no row was available, return 0 to stop iteration. + if (!nextRow) return 0; + + // Otherwise, add the item to the buffer and return the appropriate state. + stackbuf[0] = nextRow; + + state->state += 1; + state->itemsPtr = stackbuf; + state->mutationsPtr = (unsigned long *)self; + + return 1; +} + +@end + +#pragma mark - +#pragma mark Result set internals + +@implementation SPMySQLFastStreamingResult (Private_API) + +/** + * Used internally to download results in a background thread + */ +- (void)_downloadAllData +{ + NSAutoreleasePool *downloadPool = [[NSAutoreleasePool alloc] init]; + MYSQL_ROW theRow; + unsigned long *fieldLengths; + NSUInteger i, dataCopiedLength, rowDataLength; + SPMySQLStreamingRowData *newRowStore; + + size_t sizeOfStreamingRowData = sizeof(SPMySQLStreamingRowData); + size_t sizeOfDataLengths = (size_t)(sizeof(unsigned long) * numberOfFields); + size_t sizeOfChar = sizeof(char); + + // Loop through the rows until the end of the data is reached - indicated via a NULL + while ( + (*isConnectedPtr)(parentConnection, isConnectedSelector) + && (theRow = mysql_fetch_row(resultSet)) + ) + { + + // Retrieve the lengths of the returned data + fieldLengths = mysql_fetch_lengths(resultSet); + rowDataLength = 0; + dataCopiedLength = 0; + for (i = 0; i < numberOfFields; i++) { + rowDataLength += fieldLengths[i]; + } + + // Initialise memory for the row and set a NULL pointer for the next item + newRowStore = malloc(sizeOfStreamingRowData); + newRowStore->nextRow = NULL; + + // Set up the row data store - a char* - and copy in the data if there is any. + newRowStore->data = malloc(sizeOfChar * rowDataLength); + for (i = 0; i < numberOfFields; i++) { + if (theRow[i] != NULL) { + memcpy(newRowStore->data+dataCopiedLength, theRow[i], fieldLengths[i]); + dataCopiedLength += fieldLengths[i]; + } else { + fieldLengths[i] = NSNotFound; + } + } + + // Set up the memory for, and copy in, the field lengths + newRowStore->dataLengths = memcpy(malloc(sizeOfDataLengths), fieldLengths, sizeOfDataLengths); + + // Lock the data mutex + pthread_mutex_lock(&dataLock); + + // Add the newly allocated row to end of the storage linked list + if (lastDataStoreEntry) { + lastDataStoreEntry->nextRow = newRowStore; + } + lastDataStoreEntry = newRowStore; + if (!currentDataStoreEntry) currentDataStoreEntry = newRowStore; + + // Update the downloaded row count + downloadedRowCount++; + + // Unlock the mutex + pthread_mutex_unlock(&dataLock); + } + + // Update the connection's error statuses to reflect any errors during the content download + [parentConnection _updateLastErrorID:NSNotFound]; + [parentConnection _updateLastErrorMessage:nil]; + + // Unlock the parent connection now all data has been retrieved + [parentConnection _unlockConnection]; + connectionUnlocked = YES; + + dataDownloaded = YES; + [downloadPool drain]; +} + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLFramework_Prefix.pch b/Frameworks/SPMySQLFramework/Source/SPMySQLFramework_Prefix.pch new file mode 100644 index 00000000..8528c29c --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLFramework_Prefix.pch @@ -0,0 +1,11 @@ +// +// Prefix header for all source files of the 'SPMySQLFramework' target in the 'SPMySQLFramework' project. +// + +#ifdef __OBJC__ + #import <Cocoa/Cocoa.h> +#endif + +#import "mysql.h" +#import "SPMySQL.h" +#import "SPMySQLUtilities.h"
\ No newline at end of file diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLGeometryData.h b/Frameworks/SPMySQLFramework/Source/SPMySQLGeometryData.h new file mode 100644 index 00000000..e1313032 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLGeometryData.h @@ -0,0 +1,53 @@ +// +// $Id$ +// +// SPMySQLGeometryData.h +// sequel-pro +// +// Created by Hans-Jörg Bibiko on October 07, 2010 +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +@interface SPMySQLGeometryData : NSObject +{ + // Holds the WKB bytes coming from SQL server + Byte *geoBuffer; + + // Holds the buffer length + NSUInteger bufferLength; + +} + +- (id)initWithBytes:(const void *)geoData length:(NSUInteger)length; ++ (id)dataWithBytes:(const void *)geoData length:(NSUInteger)length; +- (NSString *)description; +- (NSUInteger)length; +- (NSData *)data; +- (NSString *)wktString; +- (NSDictionary *)coordinates; +- (NSInteger)wkbType; +- (NSString *)wktType; + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLGeometryData.m b/Frameworks/SPMySQLFramework/Source/SPMySQLGeometryData.m new file mode 100644 index 00000000..3c37e403 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLGeometryData.m @@ -0,0 +1,810 @@ +// +// $Id$ +// +// SPMySQLGeometryData.m +// sequel-pro +// +// Created by Hans-Jörg Bibiko on October 07, 2010 +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "SPMySQLGeometryData.h" + +enum wkbType +{ + wkb_point = 1, + wkb_linestring = 2, + wkb_polygon = 3, + wkb_multipoint = 4, + wkb_multilinestring = 5, + wkb_multipolygon = 6, + wkb_geometrycollection = 7 +}; + +typedef struct st_point_2d_ +{ + double x; + double y; +} st_point_2d; + +#define SIZEOF_STORED_UINT32 4 +#define SIZEOF_STORED_DOUBLE 8 +#define POINT_DATA_SIZE (SIZEOF_STORED_DOUBLE*2) +#define WKB_HEADER_SIZE (1+SIZEOF_STORED_UINT32) +#define BUFFER_START 0 + +@implementation SPMySQLGeometryData + +/** + * Initialize the SPMySQLGeometryData object + */ +- (id)init +{ + if ((self = [super init])) { + geoBuffer = nil; + bufferLength = 0; + } + return self; +} + +/** + * Initialize the SPMySQLGeometryData object with the WKB data + */ +- (id)initWithBytes:(const void *)geoData length:(NSUInteger)length +{ + if ((self = [self init])) { + bufferLength = length; + geoBuffer = malloc(bufferLength); + memcpy(geoBuffer, geoData, bufferLength); + } + return self; +} + +/** + * Return an autorelease SPMySQLGeometryData object + */ ++ (id)dataWithBytes:(const void *)geoData length:(NSUInteger)length +{ + return [[[SPMySQLGeometryData alloc] initWithBytes:geoData length:length] autorelease]; +} + +/** + * copyWithZone + */ +- (id)copyWithZone:(NSZone *)zone +{ + return [self retain]; +} + +/** + * Return the hex representation of the WKB buffer (only for convenience) + */ +- (NSString*)description +{ + return [[NSData dataWithBytes:geoBuffer length:bufferLength] description]; +} + +/** + * Return the length of the WKB buffer + */ +- (NSUInteger)length +{ + return bufferLength; +} + +/** + * Return NSData pointer of the WKB buffer + */ +- (NSData *)data +{ + return [NSData dataWithBytes:geoBuffer length:bufferLength]; +} + +/** + * Return a human readable WKT string of the internal format (imitating the SQL function AsText()). + */ +- (NSString *)wktString +{ + char byteOrder; + uint32_t geoType, numberOfItems, numberOfSubItems, numberOfSubSubItems, numberOfCollectionItems; + int32_t srid; + st_point_2d aPoint; + + uint32_t i, j, k, n; // Loop counter for numberOf...Items + uint32_t ptr = BUFFER_START; // pointer to geoBuffer while parsing + + NSMutableString *wkt = [NSMutableString string]; + + if (bufferLength < WKB_HEADER_SIZE) + return @""; + + memcpy(&srid, &geoBuffer[0], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + + byteOrder = (char)geoBuffer[ptr]; + + if (byteOrder != 0x1) + return @"Byte order not yet supported"; + + ptr++; + geoType = geoBuffer[ptr]; + ptr += SIZEOF_STORED_UINT32; + + switch (geoType) { + + case wkb_point: + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + return [NSString stringWithFormat:@"POINT(%.16g %.16g)%@", aPoint.x, aPoint.y, (srid) ? [NSString stringWithFormat:@",%d",srid]: @""]; + break; + + case wkb_linestring: + [wkt setString:@"LINESTRING("]; + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + for (i=0; i < numberOfItems; i++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + [wkt appendFormat:@"%.16g %.16g%@", aPoint.x, aPoint.y, (i < numberOfItems-1) ? @"," : @""]; + ptr += POINT_DATA_SIZE; + } + [wkt appendFormat:@")%@", (srid) ? [NSString stringWithFormat:@",%d",srid]: @""]; + return wkt; + break; + + case wkb_polygon: + [wkt setString:@"POLYGON("]; + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + for (i=0; i < numberOfItems; i++) { + memcpy(&numberOfSubItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + [wkt appendString:@"("]; + for (j=0; j < numberOfSubItems; j++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + [wkt appendFormat:@"%.16g %.16g%@", aPoint.x, aPoint.y, (j < numberOfSubItems-1) ? @"," : @""]; + ptr += POINT_DATA_SIZE; + } + [wkt appendFormat:@")%@", (i < numberOfItems-1) ? @"," : @""]; + } + [wkt appendFormat:@")%@", (srid) ? [NSString stringWithFormat:@",%d",srid]: @""]; + return wkt; + break; + + case wkb_multipoint: + [wkt setString:@"MULTIPOINT("]; + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32+WKB_HEADER_SIZE; + for (i=0; i < numberOfItems; i++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + [wkt appendFormat:@"%.16g %.16g%@", aPoint.x, aPoint.y, (i < numberOfItems-1) ? @"," : @""]; + ptr += POINT_DATA_SIZE+WKB_HEADER_SIZE; + } + [wkt appendFormat:@")%@", (srid) ? [NSString stringWithFormat:@",%d",srid]: @""]; + return wkt; + break; + + case wkb_multilinestring: + [wkt setString:@"MULTILINESTRING("]; + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32+WKB_HEADER_SIZE; + for (i=0; i < numberOfItems; i++) { + memcpy(&numberOfSubItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + [wkt appendString:@"("]; + for (j=0; j < numberOfSubItems; j++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + [wkt appendFormat:@"%.16g %.16g%@", aPoint.x, aPoint.y, (j < numberOfSubItems-1) ? @"," : @""]; + ptr += POINT_DATA_SIZE; + } + ptr += WKB_HEADER_SIZE; + [wkt appendFormat:@")%@", (i < numberOfItems-1) ? @"," : @""]; + } + [wkt appendFormat:@")%@", (srid) ? [NSString stringWithFormat:@",%d",srid]: @""]; + return wkt; + break; + + case wkb_multipolygon: + [wkt setString:@"MULTIPOLYGON("]; + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32+WKB_HEADER_SIZE; + for (i=0; i < numberOfItems; i++) { + memcpy(&numberOfSubItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + [wkt appendString:@"("]; + for (j=0; j < numberOfSubItems; j++) { + memcpy(&numberOfSubSubItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + [wkt appendString:@"("]; + for (k=0; k < numberOfSubSubItems; k++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + [wkt appendFormat:@"%.16g %.16g%@", aPoint.x, aPoint.y, (k < numberOfSubSubItems-1) ? @"," : @""]; + ptr += POINT_DATA_SIZE; + } + [wkt appendFormat:@")%@", (j < numberOfSubItems-1) ? @"," : @""]; + } + ptr += WKB_HEADER_SIZE; + [wkt appendFormat:@")%@", (i < numberOfItems-1) ? @"," : @""]; + } + [wkt appendFormat:@")%@", (srid) ? [NSString stringWithFormat:@",%d",srid]: @""]; + return wkt; + break; + + case wkb_geometrycollection: + [wkt setString:@"GEOMETRYCOLLECTION("]; + numberOfCollectionItems = geoBuffer[ptr]; + ptr += SIZEOF_STORED_UINT32; + + for (n=0; n < numberOfCollectionItems; n++) { + + byteOrder = (char)geoBuffer[ptr]; + + if(byteOrder != 0x1) + return @"Byte order not yet supported"; + + ptr++; + geoType = geoBuffer[ptr]; + ptr += SIZEOF_STORED_UINT32; + + switch(geoType) { + + case wkb_point: + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + [wkt appendFormat:@"POINT(%.16g %.16g)", aPoint.x, aPoint.y]; + ptr += POINT_DATA_SIZE; + break; + + case wkb_linestring: + [wkt appendString:@"LINESTRING("]; + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + for (i=0; i < numberOfItems; i++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + [wkt appendFormat:@"%.16g %.16g%@", aPoint.x, aPoint.y, (i < numberOfItems-1) ? @"," : @""]; + ptr += POINT_DATA_SIZE; + } + [wkt appendString:@")"]; + break; + + case wkb_polygon: + [wkt appendString:@"POLYGON("]; + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + for (i=0; i < numberOfItems; i++) { + memcpy(&numberOfSubItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + [wkt appendString:@"("]; + for (j=0; j < numberOfSubItems; j++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + [wkt appendFormat:@"%.16g %.16g%@", aPoint.x, aPoint.y, (j < numberOfSubItems-1) ? @"," : @""]; + ptr += POINT_DATA_SIZE; + } + [wkt appendFormat:@")%@", (i < numberOfItems-1) ? @"," : @""]; + } + [wkt appendString:@")"]; + break; + + case wkb_multipoint: + [wkt appendString:@"MULTIPOINT("]; + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32+WKB_HEADER_SIZE; + for (i=0; i < numberOfItems; i++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + [wkt appendFormat:@"%.16g %.16g%@", aPoint.x, aPoint.y, (i < numberOfItems-1) ? @"," : @""]; + ptr += POINT_DATA_SIZE+WKB_HEADER_SIZE; + } + ptr -= WKB_HEADER_SIZE; + [wkt appendString:@")"]; + break; + + case wkb_multilinestring: + [wkt appendString:@"MULTILINESTRING("]; + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32+WKB_HEADER_SIZE; + for (i=0; i < numberOfItems; i++) { + memcpy(&numberOfSubItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + [wkt appendString:@"("]; + for (j=0; j < numberOfSubItems; j++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + [wkt appendFormat:@"%.16g %.16g%@", aPoint.x, aPoint.y, (j < numberOfSubItems-1) ? @"," : @""]; + ptr += POINT_DATA_SIZE; + } + ptr += WKB_HEADER_SIZE; + [wkt appendFormat:@")%@", (i < numberOfItems-1) ? @"," : @""]; + } + ptr -= WKB_HEADER_SIZE; + [wkt appendString:@")"]; + break; + + case wkb_multipolygon: + [wkt appendString:@"MULTIPOLYGON("]; + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32+WKB_HEADER_SIZE; + for (i=0; i < numberOfItems; i++) { + memcpy(&numberOfSubItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + [wkt appendString:@"("]; + for (j=0; j < numberOfSubItems; j++) { + memcpy(&numberOfSubSubItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + [wkt appendString:@"("]; + for (k=0; k < numberOfSubSubItems; k++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + [wkt appendFormat:@"%.16g %.16g%@", aPoint.x, aPoint.y, (k < numberOfSubSubItems-1) ? @"," : @""]; + ptr += POINT_DATA_SIZE; + } + [wkt appendFormat:@")%@", (j < numberOfSubItems-1) ? @"," : @""]; + } + ptr += WKB_HEADER_SIZE; + [wkt appendFormat:@")%@", (i < numberOfItems-1) ? @"," : @""]; + } + ptr -= WKB_HEADER_SIZE; + [wkt appendString:@")"]; + break; + + default: + return @"Error geometrycollection type parsing"; + } + [wkt appendString:(n < numberOfCollectionItems-1) ? @"," : @""]; + } + [wkt appendFormat:@")%@", (srid) ? [NSString stringWithFormat:@",%d",srid]: @""]; + return wkt; + break; + + default: + return @"Error geometry type parsing"; + } + + return @"Error while parsing"; +} + +/** + * Return a dictionary of coordinates, bbox, etc. to be able to draw the given geometry. + * + * @return A dictionary having the following keys: "bbox" as NSArray of NSNumbers of x_min x_max y_min y_max, "coordinates" as NSArray containing the + * the to be drawn points as NSPoint strings, "type" as NSString + */ +- (NSDictionary *)coordinates +{ + char byteOrder; + uint32_t geoType, numberOfItems, numberOfSubItems, numberOfSubSubItems, numberOfCollectionItems; + int32_t srid; + st_point_2d aPoint; + + uint32_t i, j, k, n; // Loop counter for numberOf...Items + uint32_t ptr = BUFFER_START; // pointer to geoBuffer while parsing + + double x_min = DBL_MAX; + double x_max = -DBL_MAX; + double y_min = DBL_MAX; + double y_max = -DBL_MAX; + + NSMutableArray *coordinates = [NSMutableArray array]; + NSMutableArray *subcoordinates = [NSMutableArray array]; + NSMutableArray *pointcoordinates = [NSMutableArray array]; + NSMutableArray *linecoordinates = [NSMutableArray array]; + NSMutableArray *linesubcoordinates = [NSMutableArray array]; + NSMutableArray *polygoncoordinates = [NSMutableArray array]; + NSMutableArray *polygonsubcoordinates = [NSMutableArray array]; + + if (bufferLength < WKB_HEADER_SIZE) + return nil; + + memcpy(&srid, &geoBuffer[0], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + + byteOrder = (char)geoBuffer[ptr]; + + if (byteOrder != 0x1) + return nil; + + ptr++; + geoType = geoBuffer[ptr]; + ptr += SIZEOF_STORED_UINT32; + + switch(geoType) { + + case wkb_point: + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + x_min = aPoint.x; + x_max = aPoint.x; + y_min = aPoint.y; + y_max = aPoint.y; + [coordinates addObject:NSStringFromPoint(NSMakePoint((CGFloat)aPoint.x, (CGFloat)aPoint.y))]; + return [NSDictionary dictionaryWithObjectsAndKeys: + [NSArray arrayWithObjects: + [NSNumber numberWithDouble:x_min], + [NSNumber numberWithDouble:x_max], + [NSNumber numberWithDouble:y_min], + [NSNumber numberWithDouble:y_max], + nil], @"bbox", + coordinates, @"coordinates", + [NSNumber numberWithInt:srid], @"srid", + @"POINT", @"type", + nil]; + break; + + case wkb_linestring: + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + for (i=0; i < numberOfItems; i++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + x_min = (aPoint.x < x_min) ? aPoint.x : x_min; + x_max = (aPoint.x > x_max) ? aPoint.x : x_max; + y_min = (aPoint.y < y_min) ? aPoint.y : y_min; + y_max = (aPoint.y > y_max) ? aPoint.y : y_max; + [coordinates addObject:NSStringFromPoint(NSMakePoint((CGFloat)aPoint.x, (CGFloat)aPoint.y))]; + ptr += POINT_DATA_SIZE; + } + return [NSDictionary dictionaryWithObjectsAndKeys: + [NSArray arrayWithObjects: + [NSNumber numberWithDouble:x_min], + [NSNumber numberWithDouble:x_max], + [NSNumber numberWithDouble:y_min], + [NSNumber numberWithDouble:y_max], + nil], @"bbox", + [NSArray arrayWithObjects:coordinates,nil], @"coordinates", + @"LINESTRING", @"type", + nil]; + break; + + case wkb_polygon: + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + for (i=0; i < numberOfItems; i++) { + memcpy(&numberOfSubItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + for (j=0; j < numberOfSubItems; j++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + x_min = (aPoint.x < x_min) ? aPoint.x : x_min; + x_max = (aPoint.x > x_max) ? aPoint.x : x_max; + y_min = (aPoint.y < y_min) ? aPoint.y : y_min; + y_max = (aPoint.y > y_max) ? aPoint.y : y_max; + [subcoordinates addObject:NSStringFromPoint(NSMakePoint((CGFloat)aPoint.x, (CGFloat)aPoint.y))]; + ptr += POINT_DATA_SIZE; + } + [coordinates addObject:[[subcoordinates copy] autorelease]]; + [subcoordinates removeAllObjects]; + } + return [NSDictionary dictionaryWithObjectsAndKeys: + [NSArray arrayWithObjects: + [NSNumber numberWithDouble:x_min], + [NSNumber numberWithDouble:x_max], + [NSNumber numberWithDouble:y_min], + [NSNumber numberWithDouble:y_max], + nil], @"bbox", + coordinates, @"coordinates", + [NSNumber numberWithInt:srid], @"srid", + @"POLYGON", @"type", + nil]; + break; + + case wkb_multipoint: + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32+WKB_HEADER_SIZE; + for (i=0; i < numberOfItems; i++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + x_min = (aPoint.x < x_min) ? aPoint.x : x_min; + x_max = (aPoint.x > x_max) ? aPoint.x : x_max; + y_min = (aPoint.y < y_min) ? aPoint.y : y_min; + y_max = (aPoint.y > y_max) ? aPoint.y : y_max; + [coordinates addObject:NSStringFromPoint(NSMakePoint((CGFloat)aPoint.x, (CGFloat)aPoint.y))]; + ptr += POINT_DATA_SIZE+WKB_HEADER_SIZE; + } + return [NSDictionary dictionaryWithObjectsAndKeys: + [NSArray arrayWithObjects: + [NSNumber numberWithDouble:x_min], + [NSNumber numberWithDouble:x_max], + [NSNumber numberWithDouble:y_min], + [NSNumber numberWithDouble:y_max], + nil], @"bbox", + coordinates, @"coordinates", + [NSNumber numberWithInt:srid], @"srid", + @"MULTIPOINT", @"type", + nil]; + break; + + case wkb_multilinestring: + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32+WKB_HEADER_SIZE; + for (i=0; i < numberOfItems; i++) { + memcpy(&numberOfSubItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + for (j=0; j < numberOfSubItems; j++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + x_min = (aPoint.x < x_min) ? aPoint.x : x_min; + x_max = (aPoint.x > x_max) ? aPoint.x : x_max; + y_min = (aPoint.y < y_min) ? aPoint.y : y_min; + y_max = (aPoint.y > y_max) ? aPoint.y : y_max; + [subcoordinates addObject:NSStringFromPoint(NSMakePoint((CGFloat)aPoint.x, (CGFloat)aPoint.y))]; + ptr += POINT_DATA_SIZE; + } + ptr += WKB_HEADER_SIZE; + [coordinates addObject:[[subcoordinates copy] autorelease]]; + [subcoordinates removeAllObjects]; + } + return [NSDictionary dictionaryWithObjectsAndKeys: + [NSArray arrayWithObjects: + [NSNumber numberWithDouble:x_min], + [NSNumber numberWithDouble:x_max], + [NSNumber numberWithDouble:y_min], + [NSNumber numberWithDouble:y_max], + nil], @"bbox", + coordinates, @"coordinates", + [NSNumber numberWithInt:srid], @"srid", + @"MULTILINESTRING", @"type", + nil]; + break; + + case wkb_multipolygon: + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32+WKB_HEADER_SIZE; + for (i=0; i < numberOfItems; i++) { + memcpy(&numberOfSubItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + for (j=0; j < numberOfSubItems; j++) { + memcpy(&numberOfSubSubItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + for (k=0; k < numberOfSubSubItems; k++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + x_min = (aPoint.x < x_min) ? aPoint.x : x_min; + x_max = (aPoint.x > x_max) ? aPoint.x : x_max; + y_min = (aPoint.y < y_min) ? aPoint.y : y_min; + y_max = (aPoint.y > y_max) ? aPoint.y : y_max; + [subcoordinates addObject:NSStringFromPoint(NSMakePoint((CGFloat)aPoint.x, (CGFloat)aPoint.y))]; + ptr += POINT_DATA_SIZE; + } + [coordinates addObject:[[subcoordinates copy] autorelease]]; + [subcoordinates removeAllObjects]; + } + ptr += WKB_HEADER_SIZE; + } + return [NSDictionary dictionaryWithObjectsAndKeys: + [NSArray arrayWithObjects: + [NSNumber numberWithDouble:x_min], + [NSNumber numberWithDouble:x_max], + [NSNumber numberWithDouble:y_min], + [NSNumber numberWithDouble:y_max], + nil], @"bbox", + coordinates, @"coordinates", + [NSNumber numberWithInt:srid], @"srid", + @"MULTIPOLYGON", @"type", + nil]; + break; + + case wkb_geometrycollection: + numberOfCollectionItems = geoBuffer[ptr]; + ptr += SIZEOF_STORED_UINT32; + + for (n=0; n < numberOfCollectionItems; n++) { + + byteOrder = (char)geoBuffer[ptr]; + + if (byteOrder != 0x1) + return nil; + + ptr++; + geoType = geoBuffer[ptr]; + ptr += SIZEOF_STORED_UINT32; + + switch(geoType) { + + case wkb_point: + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + x_min = (aPoint.x < x_min) ? aPoint.x : x_min; + x_max = (aPoint.x > x_max) ? aPoint.x : x_max; + y_min = (aPoint.y < y_min) ? aPoint.y : y_min; + y_max = (aPoint.y > y_max) ? aPoint.y : y_max; + [pointcoordinates addObject:NSStringFromPoint(NSMakePoint((CGFloat)aPoint.x, (CGFloat)aPoint.y))]; + ptr += POINT_DATA_SIZE; + break; + + case wkb_linestring: + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + for (i=0; i < numberOfItems; i++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + x_min = (aPoint.x < x_min) ? aPoint.x : x_min; + x_max = (aPoint.x > x_max) ? aPoint.x : x_max; + y_min = (aPoint.y < y_min) ? aPoint.y : y_min; + y_max = (aPoint.y > y_max) ? aPoint.y : y_max; + [linesubcoordinates addObject:NSStringFromPoint(NSMakePoint((CGFloat)aPoint.x, (CGFloat)aPoint.y))]; + ptr += POINT_DATA_SIZE; + } + [linecoordinates addObject:[[linesubcoordinates copy] autorelease]]; + [linesubcoordinates removeAllObjects]; + break; + + case wkb_polygon: + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + for (i=0; i < numberOfItems; i++) { + memcpy(&numberOfSubItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + for (j=0; j < numberOfSubItems; j++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + x_min = (aPoint.x < x_min) ? aPoint.x : x_min; + x_max = (aPoint.x > x_max) ? aPoint.x : x_max; + y_min = (aPoint.y < y_min) ? aPoint.y : y_min; + y_max = (aPoint.y > y_max) ? aPoint.y : y_max; + [polygonsubcoordinates addObject:NSStringFromPoint(NSMakePoint((CGFloat)aPoint.x, (CGFloat)aPoint.y))]; + ptr += POINT_DATA_SIZE; + } + [polygoncoordinates addObject:[[polygonsubcoordinates copy] autorelease]]; + [polygonsubcoordinates removeAllObjects]; + } + break; + + case wkb_multipoint: + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32+WKB_HEADER_SIZE; + for (i=0; i < numberOfItems; i++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + x_min = (aPoint.x < x_min) ? aPoint.x : x_min; + x_max = (aPoint.x > x_max) ? aPoint.x : x_max; + y_min = (aPoint.y < y_min) ? aPoint.y : y_min; + y_max = (aPoint.y > y_max) ? aPoint.y : y_max; + [pointcoordinates addObject:NSStringFromPoint(NSMakePoint((CGFloat)aPoint.x, (CGFloat)aPoint.y))]; + ptr += POINT_DATA_SIZE+WKB_HEADER_SIZE; + } + ptr -= WKB_HEADER_SIZE; + break; + + case wkb_multilinestring: + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32+WKB_HEADER_SIZE; + for (i=0; i < numberOfItems; i++) { + memcpy(&numberOfSubItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + for (j=0; j < numberOfSubItems; j++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + x_min = (aPoint.x < x_min) ? aPoint.x : x_min; + x_max = (aPoint.x > x_max) ? aPoint.x : x_max; + y_min = (aPoint.y < y_min) ? aPoint.y : y_min; + y_max = (aPoint.y > y_max) ? aPoint.y : y_max; + [linesubcoordinates addObject:NSStringFromPoint(NSMakePoint((CGFloat)aPoint.x, (CGFloat)aPoint.y))]; + ptr += POINT_DATA_SIZE; + } + [linecoordinates addObject:[[linesubcoordinates copy] autorelease]]; + [linesubcoordinates removeAllObjects]; + ptr += WKB_HEADER_SIZE; + } + ptr -= WKB_HEADER_SIZE; + break; + + case wkb_multipolygon: + memcpy(&numberOfItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32+WKB_HEADER_SIZE; + for (i=0; i < numberOfItems; i++) { + memcpy(&numberOfSubItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + for (j=0; j < numberOfSubItems; j++) { + memcpy(&numberOfSubSubItems, &geoBuffer[ptr], SIZEOF_STORED_UINT32); + ptr += SIZEOF_STORED_UINT32; + for (k=0; k < numberOfSubSubItems; k++) { + memcpy(&aPoint, &geoBuffer[ptr], POINT_DATA_SIZE); + x_min = (aPoint.x < x_min) ? aPoint.x : x_min; + x_max = (aPoint.x > x_max) ? aPoint.x : x_max; + y_min = (aPoint.y < y_min) ? aPoint.y : y_min; + y_max = (aPoint.y > y_max) ? aPoint.y : y_max; + [polygonsubcoordinates addObject:NSStringFromPoint(NSMakePoint((CGFloat)aPoint.x, (CGFloat)aPoint.y))]; + ptr += POINT_DATA_SIZE; + } + [polygoncoordinates addObject:[[polygonsubcoordinates copy] autorelease]]; + [polygonsubcoordinates removeAllObjects]; + } + ptr += WKB_HEADER_SIZE; + } + ptr -= WKB_HEADER_SIZE; + break; + + default: + return nil; + } + } + return [NSDictionary dictionaryWithObjectsAndKeys: + [NSArray arrayWithObjects: + [NSNumber numberWithDouble:x_min], + [NSNumber numberWithDouble:x_max], + [NSNumber numberWithDouble:y_min], + [NSNumber numberWithDouble:y_max], + nil], @"bbox", + [NSArray arrayWithObjects:pointcoordinates, linecoordinates, polygoncoordinates, nil], @"coordinates", + @"GEOMETRYCOLLECTION", @"type", + nil]; + break; + + default: + return nil; + } + + return nil; +} + +/** + * Return the WKB type of the geoBuffer ie if buffer represents a POINT, LINESTRING, etc. + * according to stored wkbType in header file. It returns -1 if an error occurred. + */ +- (NSInteger)wkbType +{ + char byteOrder; + SInt32 geoType; + + NSUInteger ptr = BUFFER_START; // pointer to geoBuffer while parsing + + if (bufferLength < WKB_HEADER_SIZE) + return -1; + + byteOrder = (char)geoBuffer[ptr]; + + if (byteOrder != 0x1) + return -1; + + ptr++; + geoType = geoBuffer[ptr]; + + if (geoType > 0 && geoType < 8) + return geoType; + else + return -1; + +} + +/** + * Return the WKT type of the geoBuffer ie if buffer represents a POINT, LINESTRING, etc. + * according to stored wkbType in header file. It returns nil if an error occurred. + */ +- (NSString *)wktType +{ + switch ([self wkbType]) + { + case wkb_point: + return @"POINT"; + case wkb_linestring: + return @"LINESTRING"; + case wkb_polygon: + return @"POLYGON"; + case wkb_multipoint: + return @"MULTIPOINT"; + case wkb_multilinestring: + return @"MULTILINESTRING"; + case wkb_multipolygon: + return @"MULTIPOLYGON"; + case wkb_geometrycollection: + return @"GEOMETRYCOLLECTION"; + default: + return nil; + } + return nil; +} + +/** + * dealloc + */ +- (void)dealloc +{ + if (geoBuffer && bufferLength) free(geoBuffer); + [super dealloc]; +} + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLKeepAliveTimer.h b/Frameworks/SPMySQLFramework/Source/SPMySQLKeepAliveTimer.h new file mode 100644 index 00000000..ebe46bed --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLKeepAliveTimer.h @@ -0,0 +1,45 @@ +// +// $Id$ +// +// SPMySQLKeepAliveTimer.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on March 5, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +@interface SPMySQLKeepAliveTimer : NSObject { + id timerTarget; + SEL timerSelector; + NSTimeInterval timerRepeatInterval; + + NSTimer *wrappedTimer; +} + +- (id)initWithInterval:(NSTimeInterval)anInterval target:(id)aTarget selector:(SEL)aSelector; +- (void)invalidate; + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLKeepAliveTimer.m b/Frameworks/SPMySQLFramework/Source/SPMySQLKeepAliveTimer.m new file mode 100644 index 00000000..f9164aff --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLKeepAliveTimer.m @@ -0,0 +1,127 @@ +// +// $Id$ +// +// SPMySQLKeepAliveTimer.m +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on March 5, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +#import "SPMySQLKeepAliveTimer.h" +#import "SPMySQL Private APIs.h" + +@interface SPMySQLKeepAliveTimer (Private_API) + +- (void)_initKeepAliveTimer; +- (void)_forwardPing; + +@end + +#pragma mark - + +@implementation SPMySQLKeepAliveTimer + +/** + * Prevent SPMySQLKeepAliveTimer from being init'd normally. + */ +- (id)init +{ + [NSException raise:NSInternalInconsistencyException format:@"SPMySQLKeepAliveTimers should not be init'd directly; use initWithInterval:target:selector: instead."]; + return nil; +} + +/** + * Initialise the SPMySQLKeepAliveTimer. This also sets up the contained timer, + * which has to be wrapped in this class to prevent retain cycles preventing the + * parent connection from being released. + * + * After initialisation, the delegate should be set to ensure that the timer events + * are received. + */ +- (id)initWithInterval:(NSTimeInterval)anInterval target:(id)aTarget selector:(SEL)aSelector +{ + if ((self = [super init])) { + wrappedTimer = nil; + + // Keep a weak reference to the target + timerTarget = aTarget; + timerSelector = aSelector; + timerRepeatInterval = anInterval; + + // Ensure the timer is set up on the main thread + if ([NSThread isMainThread]) { + [self _initKeepAliveTimer]; + } else { + [self performSelectorOnMainThread:@selector(_initKeepAliveTimer) withObject:nil waitUntilDone:YES]; + } + } + + return self; +} + +/** + * Invalidate the wrapped timer, which also releases the reference to the timer + * target (this object), breaking retain loops. + */ +- (void)invalidate +{ + if ([NSThread isMainThread]) { + [wrappedTimer invalidate]; + } else { + [wrappedTimer performSelectorOnMainThread:@selector(invalidate) withObject:nil waitUntilDone:YES]; + } +} + +- (void)dealloc +{ + [wrappedTimer dealloc]; + [super dealloc]; +} + +@end + +@implementation SPMySQLKeepAliveTimer (Private_API) + +/** + * Set up the timer to tickle the target. This must be set up on the main thread + * to ensure the timer events keep firing. + */ +- (void)_initKeepAliveTimer +{ + wrappedTimer = [[NSTimer scheduledTimerWithTimeInterval:timerRepeatInterval target:self selector:@selector(_forwardPing) userInfo:nil repeats:YES] retain]; +} + +/** + * Forward the NSTimer-fired ping to the target object. Performing this forwarding + * breaks the retain cycle. + */ +- (void)_forwardPing +{ + [timerTarget performSelector:timerSelector]; +} + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Convenience Methods.h b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Convenience Methods.h new file mode 100644 index 00000000..7d870a93 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Convenience Methods.h @@ -0,0 +1,38 @@ +// +// $Id$ +// +// Convenience Methods.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 20, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +@interface SPMySQLResult (Convenience_Methods) + +- (NSArray *)getAllRows; + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Convenience Methods.m b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Convenience Methods.m new file mode 100644 index 00000000..2b049264 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Convenience Methods.m @@ -0,0 +1,71 @@ +// +// $Id$ +// +// Convenience Methods.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 20, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "Convenience Methods.h" + +@implementation SPMySQLResult (Convenience_Methods) + +/** + * Iterates over the result set, retrieving all the rows, and returns them + * as an array. + * The rows are in the default format for this instance, as controlled via + * -setDefaultRowReturnType:. + * Returns nil if there are no rows to return. + */ +- (NSArray *)getAllRows +{ + unsigned long long previousSeekPosition = currentRowIndex; + + NSMutableArray *rowsToReturn; + + // If the number of rows is known, pre-set the size; otherwise just create an array + if (numberOfRows != NSNotFound) { + rowsToReturn = [[NSMutableArray alloc] initWithCapacity:(NSUInteger)numberOfRows]; + } else { + rowsToReturn = [[NSMutableArray alloc] init]; + } + + // Loop through the rows in the instance-specified return format + for (id eachRow in self) { + [rowsToReturn addObject:eachRow]; + } + + // Seek to the previous position if appropriate + if (previousSeekPosition) [self seekToRow:previousSeekPosition]; + + // Instead of empty arrays, return nil if there are no rows. + if (![rowsToReturn count]) return nil; + + return rowsToReturn; +} + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.h b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.h new file mode 100644 index 00000000..20e1ddc9 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.h @@ -0,0 +1,38 @@ +// +// $Id$ +// +// Field Definitions.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 2, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +@interface SPMySQLResult (Field_Definitions) + +- (NSArray *)fieldDefinitions; + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m new file mode 100644 index 00000000..59e75d2f --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m @@ -0,0 +1,557 @@ +// +// $Id$ +// +// Field Definitions.m +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 2, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "Field Definitions.h" + +@interface SPMySQLResult (Field_Definitions_Private_API) + +- (NSUInteger)_findCharsetMaxByteLengthPerCharForMySQLNumber:(NSUInteger)charsetnr; +- (NSString *)_charsetNameForMySQLNumber:(NSUInteger)charsetnr; +- (NSString *)_charsetCollationForMySQLNumber:(NSUInteger)charsetnr; +- (NSString *)_mysqlTypeToStringForType:(NSUInteger)type withCharsetNr:(NSUInteger)charsetnr withFlags:(NSUInteger)flags withLength:(unsigned long long)length; +- (NSString *)_mysqlTypeToGroupForType:(NSUInteger)type withCharsetNr:(NSUInteger)charsetnr withFlags:(NSUInteger)flags; + +@end + +// Import a private declaration from the SPMySQLResult file for use +@interface SPMySQLResult (Private_API) + +- (NSString *)_stringWithBytes:(const void *)bytes length:(NSUInteger)length; + +@end + +#define MAGIC_BINARY_CHARSET_NR 63 + +const SPMySQLResultCharset SPMySQLCharsetMap[] = +{ + {1, "big5","big5_chinese_ci", 1, 2}, + {3, "dec8", "dec8_swedisch_ci", 1, 1}, + {4, "cp850", "cp850_general_ci", 1, 1}, + {6, "hp8", "hp8_english_ci", 1, 1}, + {7, "koi8r", "koi8r_general_ci", 1, 1}, + {8, "latin1", "latin1_swedish_ci", 1, 1}, + {9, "latin2", "latin2_general_ci", 1, 1}, + {10, "swe7", "swe7_swedish_ci", 1, 1}, + {11, "ascii", "ascii_general_ci", 1, 1}, + {12, "ujis", "ujis_japanese_ci", 1, 3}, + {13, "sjis", "sjis_japanese_ci", 1, 2}, + {16, "hebrew", "hebrew_general_ci", 1, 1}, + {18, "tis620", "tis620_thai_ci", 1, 1}, + {19, "euckr", "euckr_korean_ci", 1, 2}, + {22, "koi8u", "koi8u_general_ci", 1, 1}, + {24, "gb2312", "gb2312_chinese_ci", 1, 2}, + {25, "greek", "greek_general_ci", 1, 1}, + {26, "cp1250", "cp1250_general_ci", 1, 1}, + {28, "gbk", "gbk_chinese_ci", 1, 2}, + {30, "latin5", "latin5_turkish_ci", 1, 1}, + {32, "armscii8", "armscii8_general_ci", 1, 1}, + {33, "utf8", "utf8_general_ci", 1, 3}, + {35, "ucs2", "ucs2_general_ci", 2, 2}, + {36, "cp866", "cp866_general_ci", 1, 1}, + {37, "keybcs2", "keybcs2_general_ci", 1, 1}, + {38, "macce", "macce_general_ci", 1, 1}, + {39, "macroman", "macroman_general_ci", 1, 1}, + {40, "cp852", "cp852_general_ci", 1, 1}, + {41, "latin7", "latin7_general_ci", 1, 1}, + {51, "cp1251", "cp1251_general_ci", 1, 1}, + {57, "cp1256", "cp1256_general_ci", 1, 1}, + {59, "cp1257", "cp1257_general_ci", 1, 1}, + {63, "binary", "binary", 1, 1}, + {92, "geostd8", "geostd8_general_ci", 1, 1}, + {95, "cp932", "cp932_japanese_ci", 1, 2}, + {97, "eucjpms", "eucjpms_japanese_ci", 1, 3}, + {2, "latin2", "latin2_czech_cs", 1, 1}, + {5, "latin1", "latin1_german_ci", 1, 1}, + {14, "cp1251", "cp1251_bulgarian_ci", 1, 1}, + {15, "latin1", "latin1_danish_ci", 1, 1}, + {17, "filename", "filename", 1, 5}, + {20, "latin7", "latin7_estonian_cs", 1, 1}, + {21, "latin2", "latin2_hungarian_ci", 1, 1}, + {23, "cp1251", "cp1251_ukrainian_ci", 1, 1}, + {27, "latin2", "latin2_croatian_ci", 1, 1}, + {29, "cp1257", "cp1257_lithunian_ci", 1, 1}, + {31, "latin1", "latin1_german2_ci", 1, 1}, + {34, "cp1250", "cp1250_czech_cs", 1, 1}, + {42, "latin7", "latin7_general_cs", 1, 1}, + {43, "macce", "macce_bin", 1, 1}, + {44, "cp1250", "cp1250_croatian_ci", 1, 1}, + {45, "utf8", "utf8_general_ci", 1, 1}, + {46, "utf8", "utf8_bin", 1, 1}, + {47, "latin1", "latin1_bin", 1, 1}, + {48, "latin1", "latin1_general_ci", 1, 1}, + {49, "latin1", "latin1_general_cs", 1, 1}, + {50, "cp1251", "cp1251_bin", 1, 1}, + {52, "cp1251", "cp1251_general_cs", 1, 1}, + {53, "macroman", "macroman_bin", 1, 1}, + {58, "cp1257", "cp1257_bin", 1, 1}, + {60, "armascii8", "armascii8_bin", 1, 1}, + {65, "ascii", "ascii_bin", 1, 1}, + {66, "cp1250", "cp1250_bin", 1, 1}, + {67, "cp1256", "cp1256_bin", 1, 1}, + {68, "cp866", "cp866_bin", 1, 1}, + {69, "dec8", "dec8_bin", 1, 1}, + {70, "greek", "greek_bin", 1, 1}, + {71, "hebew", "hebrew_bin", 1, 1}, + {72, "hp8", "hp8_bin", 1, 1}, + {73, "keybcs2", "keybcs2_bin", 1, 1}, + {74, "koi8r", "koi8r_bin", 1, 1}, + {75, "koi8u", "koi8u_bin", 1, 1}, + {77, "latin2", "latin2_bin", 1, 1}, + {78, "latin5", "latin5_bin", 1, 1}, + {79, "latin7", "latin7_bin", 1, 1}, + {80, "cp850", "cp850_bin", 1, 1}, + {81, "cp852", "cp852_bin", 1, 1}, + {82, "swe7", "swe7_bin", 1, 1}, + {93, "geostd8", "geostd8_bin", 1, 1}, + {83, "utf8", "utf8_bin", 1, 3}, + {84, "big5", "big5_bin", 1, 2}, + {85, "euckr", "euckr_bin", 1, 2}, + {86, "gb2312", "gb2312_bin", 1, 2}, + {87, "gbk", "gbk_bin", 1, 2}, + {88, "sjis", "sjis_bin", 1, 2}, + {89, "tis620", "tis620_bin", 1, 1}, + {90, "ucs2", "ucs2_bin", 2, 2}, + {91, "ujis", "ujis_bin", 1, 3}, + {94, "latin1", "latin1_spanish_ci", 1, 1}, + {96, "cp932", "cp932_bin", 1, 2}, + {99, "cp1250", "cp1250_polish_ci", 1, 1}, + {98, "eucjpms", "eucjpms_bin", 1, 3}, + {128, "ucs2", "ucs2_unicode_ci", 2, 2}, + {129, "ucs2", "ucs2_icelandic_ci", 2, 2}, + {130, "ucs2", "ucs2_latvian_ci", 2, 2}, + {131, "ucs2", "ucs2_romanian_ci", 2, 2}, + {132, "ucs2", "ucs2_slovenian_ci", 2, 2}, + {133, "ucs2", "ucs2_polish_ci", 2, 2}, + {134, "ucs2", "ucs2_estonian_ci", 2, 2}, + {135, "ucs2", "ucs2_spanish_ci", 2, 2}, + {136, "ucs2", "ucs2_swedish_ci", 2, 2}, + {137, "ucs2", "ucs2_turkish_ci", 2, 2}, + {138, "ucs2", "ucs2_czech_ci", 2, 2}, + {139, "ucs2", "ucs2_danish_ci", 2, 2}, + {140, "ucs2", "ucs2_lithunian_ci", 2, 2}, + {141, "ucs2", "ucs2_slovak_ci", 2, 2}, + {142, "ucs2", "ucs2_spanish2_ci", 2, 2}, + {143, "ucs2", "ucs2_roman_ci", 2, 2}, + {144, "ucs2", "ucs2_persian_ci", 2, 2}, + {145, "ucs2", "ucs2_esperanto_ci", 2, 2}, + {146, "ucs2", "ucs2_hungarian_ci", 2, 2}, + {147, "ucs2", "ucs2_sinhala_ci", 2, 2}, + {192, "utf8mb3", "utf8mb3_general_ci", 1, 3}, + {193, "utf8mb3", "utf8mb3_icelandic_ci", 1, 3}, + {194, "utf8mb3", "utf8mb3_latvian_ci", 1, 3}, + {195, "utf8mb3", "utf8mb3_romanian_ci", 1, 3}, + {196, "utf8mb3", "utf8mb3_slovenian_ci", 1, 3}, + {197, "utf8mb3", "utf8mb3_polish_ci", 1, 3}, + {198, "utf8mb3", "utf8mb3_estonian_ci", 1, 3}, + {119, "utf8mb3", "utf8mb3_spanish_ci", 1, 3}, + {200, "utf8mb3", "utf8mb3_swedish_ci", 1, 3}, + {201, "utf8mb3", "utf8mb3_turkish_ci", 1, 3}, + {202, "utf8mb3", "utf8mb3_czech_ci", 1, 3}, + {203, "utf8mb3", "utf8mb3_danish_ci", 1, 3}, + {204, "utf8mb3", "utf8mb3_lithunian_ci", 1, 3}, + {205, "utf8mb3", "utf8mb3_slovak_ci", 1, 3}, + {206, "utf8mb3", "utf8mb3_spanish2_ci", 1, 3}, + {207, "utf8mb3", "utf8mb3_roman_ci", 1, 3}, + {208, "utf8mb3", "utf8mb3_persian_ci", 1, 3}, + {209, "utf8mb3", "utf8mb3_esperanto_ci", 1, 3}, + {210, "utf8mb3", "utf8mb3_hungarian_ci", 1, 3}, + {211, "utf8mb3", "utf8mb3_sinhala_ci", 1, 3}, + {224, "utf8", "utf8_unicode_ci", 1, 3}, + {225, "utf8", "utf8_icelandic_ci", 1, 3}, + {226, "utf8", "utf8_latvian_ci", 1, 3}, + {227, "utf8", "utf8_romanian_ci", 1, 3}, + {228, "utf8", "utf8_slovenian_ci", 1, 3}, + {229, "utf8", "utf8_polish_ci", 1, 3}, + {230, "utf8", "utf8_estonian_ci", 1, 3}, + {231, "utf8", "utf8_spanish_ci", 1, 3}, + {232, "utf8", "utf8_swedish_ci", 1, 3}, + {233, "utf8", "utf8_turkish_ci", 1, 3}, + {234, "utf8", "utf8_czech_ci", 1, 3}, + {235, "utf8", "utf8_danish_ci", 1, 3}, + {236, "utf8", "utf8_lithuanian_ci", 1, 3}, + {237, "utf8", "utf8_slovak_ci", 1, 3}, + {238, "utf8", "utf8_spanish2_ci", 1, 3}, + {239, "utf8", "utf8_roman_ci", 1, 3}, + {240, "utf8", "utf8_persian_ci", 1, 3}, + {241, "utf8", "utf8_esperanto_ci", 1, 3}, + {242, "utf8", "utf8_hungarian_ci", 1, 3}, + {243, "utf8", "utf8_sinhala_ci", 1, 3}, + {254, "utf8mb3", "utf8mb3_general_cs", 1, 3}, + {0, NULL, NULL, 0, 0} +}; + +#pragma mark - + +@implementation SPMySQLResult (Field_Definitions) + +/** + * Return an array of NSDictionaries, each containing information about one of + * the columns in the result set. + * MySQL returns non-valid details as empty strings - these are converted to + * unset entries in the dictionary. + */ +- (NSArray *)fieldDefinitions +{ + NSUInteger i; + NSMutableArray *theFieldDefinitions = [NSMutableArray array]; + NSMutableDictionary *eachField; + MYSQL_FIELD mysqlField; + + for (i = 0; i < numberOfFields; i++) { + eachField = [NSMutableDictionary dictionary]; + mysqlField = fieldDefinitions[i]; + + // Record the original column position within the result set + [eachField setObject:[NSString stringWithFormat:@"%llu", (unsigned long long)i] forKey:@"datacolumnindex"]; + + // Record the column name, or alias if one is being used + [eachField setObject:[self _stringWithBytes:mysqlField.name length:mysqlField.name_length] forKey:@"name"]; + + // Record the original column name if using an alias + [eachField setObject:[self _stringWithBytes:mysqlField.org_name length:mysqlField.org_name_length] forKey:@"org_name"]; + + // If the column had an underlying table, record the table name, respecting aliases + if (mysqlField.table_length) { + [eachField setObject:[self _stringWithBytes:mysqlField.table length:mysqlField.table_length] forKey:@"table"]; + } + + // If the column had an underlying table, record the original table name, ignoring aliases + if (mysqlField.org_table_length) { + [eachField setObject:[self _stringWithBytes:mysqlField.org_table length:mysqlField.org_table_length] forKey:@"org_table"]; + } + + // If the column had an underlying database, record the database name + if (mysqlField.db_length) { + [eachField setObject:[self _stringWithBytes:mysqlField.db length:mysqlField.db_length] forKey:@"db"]; + } + + // Width of column (minimum real length in bytes) + [eachField setObject:[NSNumber numberWithUnsignedLongLong:mysqlField.length] forKey:@"byte_length"]; + + // Width of column (as in create) + // TODO: Discuss the logic of this with Hans-Jörg Bibiko; is this related to max_byte_length? + [eachField setObject:[NSNumber numberWithUnsignedLongLong:(mysqlField.length/[self _findCharsetMaxByteLengthPerCharForMySQLNumber:mysqlField.charsetnr])] forKey:@"char_length"]; + + // Max width (bytes) for selected set. Note that this will be 0 for streaming results. + [eachField setObject:[NSNumber numberWithUnsignedLongLong:mysqlField.max_length] forKey:@"max_byte_length"]; + + // Bit-flags that describe the field, in entirety and split out + [eachField setObject:[NSNumber numberWithUnsignedInt:mysqlField.flags] forKey:@"flags"]; + [eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & NOT_NULL_FLAG) ? YES : NO] forKey:@"null"]; + [eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & PRI_KEY_FLAG) ? YES : NO] forKey:@"PRI_KEY_FLAG"]; + [eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & UNIQUE_KEY_FLAG) ? YES : NO] forKey:@"UNIQUE_KEY_FLAG"]; + [eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & MULTIPLE_KEY_FLAG) ? YES : NO] forKey:@"MULTIPLE_KEY_FLAG"]; + [eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & BLOB_FLAG) ? YES : NO] forKey:@"BLOB_FLAG"]; + [eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & UNSIGNED_FLAG) ? YES : NO] forKey:@"UNSIGNED_FLAG"]; + [eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & ZEROFILL_FLAG) ? YES : NO] forKey:@"ZEROFILL_FLAG"]; + [eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & BINARY_FLAG) ? YES : NO] forKey:@"BINARY_FLAG"]; + [eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & ENUM_FLAG) ? YES : NO] forKey:@"ENUM_FLAG"]; + [eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & AUTO_INCREMENT_FLAG) ? YES : NO] forKey:@"AUTO_INCREMENT_FLAG"]; + [eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & SET_FLAG) ? YES : NO] forKey:@"SET_FLAG"]; + [eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & NUM_FLAG) ? YES : NO] forKey:@"NUM_FLAG"]; + [eachField setObject:[NSNumber numberWithBool:(mysqlField.flags & PART_KEY_FLAG) ? YES : NO] forKey:@"PART_KEY_FLAG"]; + + // For numeric fields, record the number of decimals + [eachField setObject:[NSNumber numberWithUnsignedInteger:mysqlField.decimals] forKey:@"decimals"]; + + // Character set details + [eachField setObject:[NSNumber numberWithUnsignedInteger:mysqlField.charsetnr] forKey:@"charsetnr"]; + [eachField setObject:[self _charsetNameForMySQLNumber:mysqlField.charsetnr] forKey:@"charset_name"]; + [eachField setObject:[self _charsetCollationForMySQLNumber:mysqlField.charsetnr] forKey:@"charset_collation"]; + + /* Table type */ + [eachField setObject:[self _mysqlTypeToStringForType:mysqlField.type + withCharsetNr:mysqlField.charsetnr + withFlags:mysqlField.flags + withLength:mysqlField.length + ] forKey:@"type"]; + + /* Table type group*/ + [eachField setObject:[self _mysqlTypeToGroupForType:mysqlField.type + withCharsetNr:mysqlField.charsetnr + withFlags:mysqlField.flags + ] forKey:@"typegrouping"]; + + [theFieldDefinitions addObject:eachField]; + } + + return theFieldDefinitions; +} + +@end + +#pragma mark - +#pragma mark Field defintion internals + +@implementation SPMySQLResult (Field_Definitions_Private_API) + +/** + * Return the maximum byte length to store a char by using + * a specific mysql_charsetnr + */ +- (NSUInteger)_findCharsetMaxByteLengthPerCharForMySQLNumber:(NSUInteger)charsetnr +{ + const SPMySQLResultCharset *c = SPMySQLCharsetMap; + + do { + if (c->nr == charsetnr) + return c->char_maxlen; + ++c; + } while (c[0].nr != 0); + + return 1; +} + +/** + * Convert a mysql_charsetnr into a charset name as string + */ +- (NSString *)_charsetNameForMySQLNumber:(NSUInteger)charsetnr +{ + const SPMySQLResultCharset *c = SPMySQLCharsetMap; + + do { + if (c->nr == charsetnr) + return [NSString stringWithCString:c->name encoding:stringEncoding]; + ++c; + } while (c[0].nr != 0); + + return @"UNKNOWN"; +} + +/** + * Convert a mysql_charsetnr into a collation name as string + */ +- (NSString *)_charsetCollationForMySQLNumber:(NSUInteger)charsetnr +{ + const SPMySQLResultCharset *c = SPMySQLCharsetMap; + + do { + if (c->nr == charsetnr) + return [NSString stringWithCString:c->collation encoding:stringEncoding]; + ++c; + } while (c[0].nr != 0); + + return @"UNKNOWN"; +} + +/** + * Convert a mysql_type to a string + */ +- (NSString *)_mysqlTypeToStringForType:(NSUInteger)type withCharsetNr:(NSUInteger)charsetnr withFlags:(NSUInteger)flags withLength:(unsigned long long)length +{ + + switch (type) { + + case FIELD_TYPE_BIT: + return @"BIT"; + + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: + return @"DECIMAL"; + + case MYSQL_TYPE_TINY: + return @"TINYINT"; + + case MYSQL_TYPE_SHORT: + return @"SMALLINT"; + + case MYSQL_TYPE_LONG: + return @"INT"; + + case MYSQL_TYPE_FLOAT: + return @"FLOAT"; + + case MYSQL_TYPE_DOUBLE: + return @"DOUBLE"; + + case MYSQL_TYPE_NULL: + return @"NULL"; + + case MYSQL_TYPE_TIMESTAMP: + return @"TIMESTAMP"; + + case MYSQL_TYPE_LONGLONG: + return @"BIGINT"; + + case MYSQL_TYPE_INT24: + return @"MEDIUMINT"; + + case MYSQL_TYPE_DATE: + return @"DATE"; + + case MYSQL_TYPE_TIME: + return @"TIME"; + + case MYSQL_TYPE_DATETIME: + return @"DATETIME"; + + case MYSQL_TYPE_TINY_BLOB:// should no appear over the wire + case MYSQL_TYPE_MEDIUM_BLOB:// should no appear over the wire + case MYSQL_TYPE_LONG_BLOB:// should no appear over the wire + case MYSQL_TYPE_BLOB: + { + BOOL isBlob = (charsetnr == MAGIC_BINARY_CHARSET_NR); + switch (length/[self _findCharsetMaxByteLengthPerCharForMySQLNumber:charsetnr]) { + case 255: return isBlob? @"TINYBLOB":@"TINYTEXT"; + case 65535: return isBlob? @"BLOB":@"TEXT"; + case 16777215: return isBlob? @"MEDIUMBLOB":@"MEDIUMTEXT"; + case 4294967295: return isBlob? @"LONGBLOB":@"LONGTEXT"; + default: + switch (length) { + case 255: return isBlob? @"TINYBLOB":@"TINYTEXT"; + case 65535: return isBlob? @"BLOB":@"TEXT"; + case 16777215: return isBlob? @"MEDIUMBLOB":@"MEDIUMTEXT"; + case 4294967295: return isBlob? @"LONGBLOB":@"LONGTEXT"; + default: + return @"UNKNOWN"; + } + } + } + + case MYSQL_TYPE_VAR_STRING: + if (flags & ENUM_FLAG) { + return @"ENUM"; + } + if (flags & SET_FLAG) { + return @"SET"; + } + if (charsetnr == MAGIC_BINARY_CHARSET_NR) { + return @"VARBINARY"; + } + return @"VARCHAR"; + + case MYSQL_TYPE_STRING: + if (flags & ENUM_FLAG) { + return @"ENUM"; + } + if (flags & SET_FLAG) { + return @"SET"; + } + if ((flags & BINARY_FLAG) && charsetnr == MAGIC_BINARY_CHARSET_NR) { + return @"BINARY"; + } + return @"CHAR"; + + case MYSQL_TYPE_ENUM: + /* This should never happen */ + return @"ENUM"; + + case MYSQL_TYPE_YEAR: + return @"YEAR"; + + case MYSQL_TYPE_SET: + /* This should never happen */ + return @"SET"; + + case MYSQL_TYPE_GEOMETRY: + return @"GEOMETRY"; + + default: + return @"UNKNOWN"; + } +} + +/** + * Merge mysql_types into type groups + */ +- (NSString *)_mysqlTypeToGroupForType:(NSUInteger)type withCharsetNr:(NSUInteger)charsetnr withFlags:(NSUInteger)flags +{ + switch(type){ + + case FIELD_TYPE_BIT: + return @"bit"; + + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_INT24: + return @"integer"; + + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: + return @"float"; + + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TIMESTAMP: + return @"date"; + + case MYSQL_TYPE_VAR_STRING: + if (flags & ENUM_FLAG) { + return @"enum"; + } + if (flags & SET_FLAG) { + return @"enum"; + } + if (charsetnr == MAGIC_BINARY_CHARSET_NR) { + return @"binary"; + } + return @"string"; + + case MYSQL_TYPE_STRING: + if (flags & ENUM_FLAG) { + return @"enum"; + } + if (flags & SET_FLAG) { + return @"enum"; + } + if ((flags & BINARY_FLAG) && charsetnr == MAGIC_BINARY_CHARSET_NR) { + return @"binary"; + } + return @"string"; + + case MYSQL_TYPE_TINY_BLOB: // should no appear over the wire + case MYSQL_TYPE_MEDIUM_BLOB: // should no appear over the wire + case MYSQL_TYPE_LONG_BLOB: // should no appear over the wire + case MYSQL_TYPE_BLOB: + { + if (charsetnr == MAGIC_BINARY_CHARSET_NR) { + return @"blobdata"; + } else { + return @"textdata"; + } + } + + case MYSQL_TYPE_GEOMETRY: + return @"geometry"; + + default: + return @"blobdata"; + } +} + +@end
\ No newline at end of file diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult.h b/Frameworks/SPMySQLFramework/Source/SPMySQLResult.h new file mode 100644 index 00000000..29518b5d --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult.h @@ -0,0 +1,121 @@ +// +// $Id$ +// +// SPMySQLResult.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 26, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +typedef enum { + SPMySQLResultFieldAsUnhandled = 0, + SPMySQLResultFieldAsString = 1, + SPMySQLResultFieldAsStringOrBlob = 2, + SPMySQLResultFieldAsBlob = 3, + SPMySQLResultFieldAsBit = 4, + SPMySQLResultFieldAsGeometry = 5, + SPMySQLResultFieldAsNull = 6 +} SPMySQLResultFieldProcessor; + +@interface SPMySQLResult : NSObject <NSFastEnumeration> { + + // Wrapped MySQL result set and its encoding + struct st_mysql_res *resultSet; + NSStringEncoding stringEncoding; + + // Number of fields in the result set, and the field names and information + NSUInteger numberOfFields; + struct st_mysql_field *fieldDefinitions; + unsigned int *fieldTypes; + NSString **fieldNames; + + // Number of rows in the result set and an internal data position counter + unsigned long long numberOfRows; + unsigned long long currentRowIndex; + + // How long it took to execute the query that produced this result + double queryExecutionTime; + + // The target result set type for fast enumeration and unspecified row retrieval + SPMySQLResultRowType defaultRowReturnType; + + // Whether all data should be returned as strings - useful for working with some older server types + BOOL returnDataAsStrings; +} + +// Master init method +- (id)initWithMySQLResult:(void *)theResult stringEncoding:(NSStringEncoding)theStringEncoding; + +// Result set information +- (NSUInteger)numberOfFields; +- (unsigned long long)numberOfRows; +- (double)queryExecutionTime; + +// Column information +- (NSArray *)fieldNames; + +// Data retrieval (note that fast enumeration is also supported, using instance-default format) +- (void)seekToRow:(unsigned long long)targetRow; +- (id)getRow; +- (NSArray *)getRowAsArray; +- (NSDictionary *)getRowAsDictionary; +- (id)getRowAsType:(SPMySQLResultRowType)theType; + +// Data conversion ++ (NSString *)bitStringWithBytes:(const char *)bytes length:(NSUInteger)length padToLength:(NSUInteger)padLength; + +#pragma mark - +#pragma mark Synthesized properties + +/** + * Set whether the result should return data types as strings. This may be useful + * for queries where the result may be returned in either string or data form, but + * will be converted to string for display and use anyway. + * Note that certain MySQL versions also return data types for strings - eg SHOW + * commands like SHOW CREATE TABLE or SHOW VARIABLES, and this conversion can be + * necessary there. + */ +@property (readwrite, assign) BOOL returnDataAsStrings; + +@property (readwrite, assign) SPMySQLResultRowType defaultRowReturnType; + +@end + +/** + * Set up a static function to allow fast calling with cached selectors + */ +static inline id SPMySQLResultGetRow(SPMySQLResult* self, SPMySQLResultRowType rowType) +{ + typedef id (*SPMySQLResultGetRowMethodPtr)(SPMySQLResult*, SEL, SPMySQLResultRowType); + static SPMySQLResultGetRowMethodPtr cachedMethodPointer; + static SEL cachedSelector; + + if (!cachedSelector) cachedSelector = @selector(getRowAsType:); + if (!cachedMethodPointer) cachedMethodPointer = (SPMySQLResultGetRowMethodPtr)[self methodForSelector:cachedSelector]; + + return cachedMethodPointer(self, cachedSelector, rowType); +} diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m b/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m new file mode 100644 index 00000000..3ccd5727 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m @@ -0,0 +1,464 @@ +// +// $Id$ +// +// SPMySQLResult.m +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on January 26, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "SPMySQLResult.h" +#import "SPMySQL Private APIs.h" +#import "SPMySQLArrayAdditions.h" + +static SPMySQLResultFieldProcessor fieldProcessingMap[256]; +static id NSNullPointer; + +@implementation SPMySQLResult + +#pragma mark - +#pragma mark Synthesized properties + +@synthesize returnDataAsStrings; +@synthesize defaultRowReturnType; + +#pragma mark - +#pragma mark Setup and teardown + +/** + * In the one-off class initialisation, set up the result processing map + */ ++ (void)initialize +{ + + // Cached NSNull singleton reference + if (!NSNullPointer) NSNullPointer = [NSNull null]; + + // Go through the list of enum_field_types in mysql_com.h, mapping each to the method for + // processing that result set. + fieldProcessingMap[MYSQL_TYPE_DECIMAL] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_TINY] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_SHORT] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_LONG] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_FLOAT] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_DOUBLE] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_NULL] = SPMySQLResultFieldAsNull; + fieldProcessingMap[MYSQL_TYPE_TIMESTAMP] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_LONGLONG] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_INT24] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_DATE] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_TIME] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_DATETIME] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_YEAR] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_NEWDATE] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_VARCHAR] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_BIT] = SPMySQLResultFieldAsBit; + fieldProcessingMap[MYSQL_TYPE_NEWDECIMAL] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_ENUM] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_SET] = SPMySQLResultFieldAsString; + fieldProcessingMap[MYSQL_TYPE_TINY_BLOB] = SPMySQLResultFieldAsBlob; + fieldProcessingMap[MYSQL_TYPE_MEDIUM_BLOB] = SPMySQLResultFieldAsBlob; + fieldProcessingMap[MYSQL_TYPE_LONG_BLOB] = SPMySQLResultFieldAsBlob; + fieldProcessingMap[MYSQL_TYPE_BLOB] = SPMySQLResultFieldAsBlob; + fieldProcessingMap[MYSQL_TYPE_VAR_STRING] = SPMySQLResultFieldAsStringOrBlob; + fieldProcessingMap[MYSQL_TYPE_STRING] = SPMySQLResultFieldAsStringOrBlob; + fieldProcessingMap[MYSQL_TYPE_GEOMETRY] = SPMySQLResultFieldAsGeometry; + fieldProcessingMap[MYSQL_TYPE_DECIMAL] = SPMySQLResultFieldAsString; +} + +/** + * Prevent SPMySQLResults from being init'd normally. + */ +- (id)init +{ + [NSException raise:NSInternalInconsistencyException format:@"SPMySQLResults should not be init'd directly; use initWithMySQLResult:stringEncoding: instead."]; + return nil; +} + +/** + * Standard init method, constructing the SPMySQLResult around a MySQL + * result pointer and the encoding to use when working with the data. + */ +- (id)initWithMySQLResult:(void *)theResult stringEncoding:(NSStringEncoding)theStringEncoding +{ + + // If no result set was passed in, return nil. + if (!theResult) return nil; + + if ((self = [super init])) { + stringEncoding = theStringEncoding; + queryExecutionTime = -1; + + // Get the result set and cache the number of fields and number of rows + resultSet = theResult; + numberOfFields = mysql_num_fields(resultSet); + numberOfRows = mysql_num_rows(resultSet); + currentRowIndex = 0; + + // Cache the field definitions and build up an array of cached field names and types + fieldDefinitions = mysql_fetch_fields(resultSet); + fieldNames = malloc(sizeof(NSString *) * numberOfFields); + fieldTypes = malloc(sizeof(unsigned int) * numberOfFields); + for (NSUInteger i = 0; i < numberOfFields; i++) { + MYSQL_FIELD aField = fieldDefinitions[i]; + fieldNames[i] = [[self _stringWithBytes:aField.name length:aField.name_length] retain]; + fieldTypes[i] = aField.type; + } + + defaultRowReturnType = SPMySQLResultRowAsDictionary; + } + + return self; +} + +- (void)dealloc +{ + mysql_free_result(resultSet); + + for (NSUInteger i = 0; i < numberOfFields; i++) { + [fieldNames[i] release]; + } + free(fieldNames); + free(fieldTypes); + + [super dealloc]; +} + +#pragma mark - +#pragma mark Result set information + +/** + * Return the number of fields in the result set. + */ +- (NSUInteger)numberOfFields +{ + return numberOfFields; +} + +/** + * Return the number of data rows in the result set. + */ +- (unsigned long long)numberOfRows +{ + return numberOfRows; +} + +/** + * Return how long the original query took to execute - including connection lag! + */ +- (double)queryExecutionTime +{ + return queryExecutionTime; +} + +#pragma mark - +#pragma mark Column information + +/** + * Retrieve the field names for the result set, as an NSArray of NSStrings. + */ +- (NSArray *)fieldNames +{ + return [NSArray arrayWithObjects:fieldNames count:numberOfFields]; +} + +/** + * For field definitions, see Result Categories/Field Definitions.h/m + */ + +#pragma mark - +#pragma mark Data retrieval + +/** + * Jump to a specified row in the result set; when the result set is initialised, + * the internal pointer automatically starts at 0. + */ +- (void)seekToRow:(unsigned long long)targetRow +{ + if (targetRow == currentRowIndex) return; + + if (targetRow >= numberOfRows) { + targetRow = numberOfRows - 1; + } + + mysql_data_seek(resultSet, targetRow); + currentRowIndex = targetRow; +} + +/** + * Retrieve the next row in the result set, using the internal pointer, in the + * instance-specified setDefaultRowReturnType: row format (defaulting to NSDictionary). + * If there are no rows remaining, returns nil. + */ +- (id)getRow +{ + return SPMySQLResultGetRow(self, SPMySQLResultRowAsDefault); +} + +/** + * Retrieve the next row in the result set, using the internal pointer, in the + * instance-specified setDefaultRowReturnType: row format (defaulting to NSDictionary). + * If there are no rows remaining, returns nil. + */ +- (NSArray *)getRowAsArray +{ + return SPMySQLResultGetRow(self, SPMySQLResultRowAsArray); +} + +/** + * Retrieve the next row in the result set, using the internal pointer, in the + * instance-specified setDefaultRowReturnType: row format (defaulting to NSDictionary). + * If there are no rows remaining, returns nil. + */ +- (NSDictionary *)getRowAsDictionary +{ + return SPMySQLResultGetRow(self, SPMySQLResultRowAsDictionary); +} + +/** + * Retrieve the next row in the result set, using the internal pointer, in the specified + * return format. + * If there are no rows remaining in the current iteration, returns nil. + */ +- (id)getRowAsType:(SPMySQLResultRowType)theType +{ + MYSQL_ROW theRow; + unsigned long *theRowDataLengths; + id theReturnData; + + // Retrieve the row in MySQL format, and the length of the data within the row + theRow = mysql_fetch_row(resultSet); + theRowDataLengths = mysql_fetch_lengths(resultSet); + + // If no row was returned, likely at the end of the result set - return nil + if (!theRow) return nil; + + // If the target type was unspecified, use the instance default + if (theType == SPMySQLResultRowAsDefault) theType = defaultRowReturnType; + + // Set up the return data as appropriate + if (theType == SPMySQLResultRowAsArray) { + theReturnData = [NSMutableArray arrayWithCapacity:numberOfFields]; + } else { + theReturnData = [NSMutableDictionary dictionaryWithCapacity:numberOfFields]; + } + + // Convert each of the cells in the row in turn + for (NSUInteger i = 0; i < numberOfFields; i++) { + id cellData = SPMySQLResultGetObject(self, theRow[i], theRowDataLengths[i], fieldTypes[i], i); + + // If object creation failed, display a null + if (!cellData) cellData = NSNullPointer; + + // Add to the result array/dictionary + if (theType == SPMySQLResultRowAsArray) { + SPMySQLMutableArrayInsertObject(theReturnData, cellData, i); + } else { + [(NSMutableDictionary *)theReturnData setObject:cellData forKey:fieldNames[i]]; + } + } + + // Increment the row pointer index and set to NSNotFound if the end of the result set has + // been reached + currentRowIndex++; + if (currentRowIndex > numberOfRows) currentRowIndex = NSNotFound; + + return theReturnData; +} + +#pragma mark - +#pragma mark Data retrieval for fast enumeration + +/** + * Implement the fast enumeration endpoint. Rows for fast enumeration are retrieved in + * the instance default, as specified in setDefaultRowReturnType: or defaulting to + * NSDictionary. + */ +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len +{ + + // If the start index is out of bounds, return 0 to indicate end of results + if (state->state >= numberOfRows) return 0; + + // Sync up the MySQL pointer position with the requested state if necessary + if (state->state != currentRowIndex) [self seekToRow:state->state]; + + // Determine how many objects to return - 128, len, or all items remaining + NSUInteger itemsToReturn = 128; + if (len < 128) itemsToReturn = len; + if (numberOfRows - state->state < itemsToReturn) { + itemsToReturn = (unsigned long)(numberOfRows - state->state); + } + + // Loop through the rows and add them to the result stack + NSUInteger i; + for (i = 0; i < itemsToReturn; i++) { + stackbuf[i] = SPMySQLResultGetRow(self, SPMySQLResultRowAsDefault); + } + + state->state += itemsToReturn; + state->itemsPtr = stackbuf; + state->mutationsPtr = (unsigned long *)self; + + return itemsToReturn; +} + +#pragma mark - +#pragma mark Data conversion + +/** + * Provides a binary representation of the supplied bytes as a returned NSString. + * The resulting binary representation will be zero-padded according to the supplied + * field length. + */ ++ (NSString *)bitStringWithBytes:(const char *)bytes length:(NSUInteger)length padToLength:(NSUInteger)padLength +{ + if (bytes == NULL) return nil; + + NSUInteger i = 0; + length--; + padLength--; + + // Generate a C string representation of the binary data + char *cStringBuffer = malloc(length + 1); + while (i <= padLength) { + cStringBuffer[padLength - i++] = ( (bytes[length - (i >> 3)] >> (i & 0x7)) & 1 ) ? '1' : '0'; + } + cStringBuffer[padLength+1] = '\0'; + + // Convert to a string + NSString *returnString = [NSString stringWithUTF8String:cStringBuffer]; + + // Free up memory and return + free(cStringBuffer); + return returnString; +} + +@end + +#pragma mark - +#pragma mark Result set internals + +@implementation SPMySQLResult (Private_API) + +/** + * Support internal string conversions which take a supplied byte sequence and length + * and convert them to an NSString using the instance encoding. Will preserve nul + * characters within the string. + */ +- (id)_stringWithBytes:(const void *)bytes length:(NSUInteger)length +{ + return [[[NSString alloc] initWithBytes:bytes length:length encoding:stringEncoding] autorelease]; +} + +/** + * Allow setting the execution time for the original query (including connection lag) + * so it can be requested later without relying on connection state. + */ +- (void)_setQueryExecutionTime:(double)theExecutionTime +{ + queryExecutionTime = theExecutionTime; +} + +/** + * Core data conversion function, taking C data provided by MySQL and converting + * to an appropriate return type. + * Note that the data passed in currently is *not* nul-terminated for fast + * streaming results, which is safe for the current implementation but should be + * kept in mind for future changes. + */ +- (id)_getObjectFromBytes:(char *)bytes ofLength:(NSUInteger)length fieldType:(unsigned int)fieldType fieldDefinitionIndex:(NSUInteger)fieldIndex +{ + + // A NULL pointer for the data indicates a null value; return a NSNull object. + if (bytes == NULL) return NSNullPointer; + + // Determine the field processor to use + SPMySQLResultFieldProcessor dataProcessor = fieldProcessingMap[fieldType]; + + // Switch the method to process the cell data based on the field type mapping. + // Do this in two passes: the first as logic may cause a change in processor required. + switch (dataProcessor) { + + // STRING and VAR_STRING types may be strings or binary types; check the binary flag + case SPMySQLResultFieldAsStringOrBlob: + if (fieldDefinitions[fieldIndex].flags & BINARY_FLAG) { + dataProcessor = SPMySQLResultFieldAsBlob; + } + break; + + // Blob types may be automatically be converted to strings, or may be non-binary + case SPMySQLResultFieldAsBlob: + if (!(fieldDefinitions[fieldIndex].flags & BINARY_FLAG)) { + dataProcessor = SPMySQLResultFieldAsString; + } + break; + + // In most cases, use the original data processor. + default: + break; + } + + // If this instance is set to convert all data as strings, alter the processor. + if (returnDataAsStrings && dataProcessor == SPMySQLResultFieldAsBlob) { + dataProcessor = SPMySQLResultFieldAsString; + } + + // Now switch the processing method again to actually process the data. + switch (dataProcessor) { + + // Convert string types using a method that will preserve any nul characters + // within the string + case SPMySQLResultFieldAsString: + case SPMySQLResultFieldAsStringOrBlob: + return [[[NSString alloc] initWithBytes:bytes length:length encoding:stringEncoding] autorelease]; + + // Convert BLOB types to NSData + case SPMySQLResultFieldAsBlob: + return [NSData dataWithBytes:bytes length:length]; + + // For Geometry types, use a special Geometry object to handle their complexity + case SPMySQLResultFieldAsGeometry: + return [SPMySQLGeometryData dataWithBytes:bytes length:length]; + + // For bit fields, get a zero-padded representation of the data + case SPMySQLResultFieldAsBit: + return [SPMySQLResult bitStringWithBytes:bytes length:length padToLength:fieldDefinitions[fieldIndex].length]; + + // Convert null types to NSNulls + case SPMySQLResultFieldAsNull: + return NSNullPointer; + + case SPMySQLResultFieldAsUnhandled: + NSLog(@"SPMySQLResult processing encountered an unknown field type (%d), falling back to NSData handling", fieldType); + return [NSData dataWithBytes:bytes length:length]; + } + + [NSException raise:NSInternalInconsistencyException format:@"Unhandled field type when processing SPMySQLResults"]; + return nil; +} + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResult.h b/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResult.h new file mode 100644 index 00000000..5abb85db --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResult.h @@ -0,0 +1,54 @@ +// +// $Id$ +// +// SPMySQLStreamingResult.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 18, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +@interface SPMySQLStreamingResult : SPMySQLResult { + + // Keep a link to the parent connection for locking purposes + SPMySQLConnection *parentConnection; + + // Streaming result information and tracking + BOOL connectionUnlocked; + BOOL dataDownloaded; + + // Counts and memory length tracking + NSUInteger downloadedRowCount; + + IMP isConnectedPtr; + SEL isConnectedSelector; +} + +- (id)initWithMySQLResult:(void *)theResult stringEncoding:(NSStringEncoding)theStringEncoding connection:(SPMySQLConnection *)theConnection; + +// Allow result fetching to be cancelled +- (void)cancelResultLoad; + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResult.m b/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResult.m new file mode 100644 index 00000000..b19e5356 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResult.m @@ -0,0 +1,246 @@ +// +// $Id$ +// +// SPMySQLStreamingResult.m +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 18, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "SPMySQLStreamingResult.h" +#import "SPMySQL Private APIs.h" + + +/** + * This type of streaming result allows each row to be accessed on-demand; this can + * be dangerous as it means a SELECT will tie up the server for longer, as for MyISAM + * tables updates (and subsequent reads) must block while a SELECT is still running. + * However this can be useful for certain processes such as working with very large + * tables to keep memory usage low. + */ + +@implementation SPMySQLStreamingResult + +#pragma mark - + +/** + * Prevent SPMySQLStreamingResults from being init'd as SPMySQLResults. + */ +- (id)initWithMySQLResult:(void *)theResult stringEncoding:(NSStringEncoding)theStringEncoding +{ + [NSException raise:NSInternalInconsistencyException format:@"SPMySQLFullStreamingResults should not be init'd as SPMySQLResults; use initWithMySQLResult:stringEncoding:connection:withFullStreaming: instead."]; + return nil; +} + +/** + * Standard init method, constructing the SPMySQLStreamingResult around a MySQL + * result pointer and the encoding to use when working with the data. + * As opposed to SPMySQLResult, defaults to returning rows as arrays, as the result + * sets are likely to be larger and processed in loops. + */ +- (id)initWithMySQLResult:(void *)theResult stringEncoding:(NSStringEncoding)theStringEncoding connection:(SPMySQLConnection *)theConnection +{ + + // If no result set was passed in, return nil. + if (!theResult) return nil; + + if ((self = [super initWithMySQLResult:theResult stringEncoding:theStringEncoding])) { + parentConnection = theConnection; + numberOfRows = NSNotFound; + + // Start with no rows downloaded + downloadedRowCount = 0; + dataDownloaded = NO; + connectionUnlocked = NO; + + // Cache the isConnected selector and pointer for fast connection checks + isConnectedSelector = @selector(isConnected); + isConnectedPtr = [parentConnection methodForSelector:isConnectedSelector]; + + // Default to returning rows as arrays + defaultRowReturnType = SPMySQLResultRowAsArray; + } + + return self; +} + +/** + * Deallocate the result and ensure the parent connection is unlocked for further use. + */ +- (void)dealloc +{ + + // Ensure all data is processed and the parent connection is unlocked + [self cancelResultLoad]; + + // Throw an exception if in invalid state + if (!connectionUnlocked) { + [parentConnection _unlockConnection]; + [NSException raise:NSInternalInconsistencyException format:@"Parent connection remains locked after SPMySQLStreamingResult use"]; + } + + [super dealloc]; +} + +#pragma mark - +#pragma mark Result set information + +/** + * Override the return of the number of rows in the data set. If this is used before the + * data is fully downloaded, the number of results is still unknown (the server may still + * be seeking/matching), so return NSNotFound; otherwise the number of rows is returned. + */ +- (unsigned long long)numberOfRows +{ + if (!dataDownloaded) return NSNotFound; + + return downloadedRowCount; +} + +#pragma mark - +#pragma mark Data retrieval + +/** + * Override seeking behaviour: seeking cannot be used in streaming result sets. + */ +- (void)seekToRow:(unsigned long long)targetRow +{ + [NSException raise:NSInternalInconsistencyException format:@"Seeking is not supported in streaming SPMySQL result sets."]; +} + +/** + * Override the convenience selectors so that forwarding works correctly. + */ +- (id)getRow +{ + return SPMySQLResultGetRow(self, SPMySQLResultRowAsDefault); +} +- (NSArray *)getRowAsArray +{ + return SPMySQLResultGetRow(self, SPMySQLResultRowAsArray); +} +- (NSDictionary *)getRowAsDictionary +{ + return SPMySQLResultGetRow(self, SPMySQLResultRowAsDictionary); +} + +/** + * Retrieve the next row in the result set, using the internal pointer, in the specified + * return format. + * If there are no rows remaining in the current iteration, returns nil. + */ +- (id)getRowAsType:(SPMySQLResultRowType)theType +{ + id theRow = nil; + + // Ensure that the connection is still up before performing a row fetch + if ((*isConnectedPtr)(parentConnection, isConnectedSelector)) { + + // The core of result fetching in streaming mode is still based around mysql_fetch_row, + // so use the super to perform normal processing. + theRow = [super getRowAsType:theType]; + } + + // If no row was returned, the end of the result set has been reached. Clear markers, + // unlock the parent connection, and return nil. + if (!theRow) { + dataDownloaded = YES; + [parentConnection _unlockConnection]; + connectionUnlocked = YES; + return nil; + } + + // Otherwise increment the data downloaded counter and return the row + downloadedRowCount++; + + return theRow; +} + +/* + * Ensure the result set is fully processed and freed without any processing + * This method ensures that the connection is unlocked. + */ +- (void)cancelResultLoad +{ + + // If data has already been downloaded successfully, no further action is required + if (dataDownloaded) return; + + MYSQL_ROW theRow; + + // Loop through all the rows and ensure the rows are fetched. + while (1) { + theRow = mysql_fetch_row(resultSet); + + // If no data was returned, we're at the end of the result set - return. + if (theRow == NULL) { + dataDownloaded = YES; + if (!connectionUnlocked) { + [parentConnection _unlockConnection]; + connectionUnlocked = YES; + } + return; + } + + downloadedRowCount++; + } +} + +#pragma mark - +#pragma mark Data retrieval for fast enumeration + +/** + * Implement the fast enumeration endpoint. Rows for fast enumeration are retrieved in + * the instance default, as specified in setDefaultRowReturnType: or defaulting to + * NSDictionary. Full streaming mode - return one row at a time. + */ +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len +{ + + // If all rows have been retrieved, return 0 to stop iteration. + if (dataDownloaded) return 0; + + // If the MySQL row pointer does not match the requested state, throw an exception + if (state->state != currentRowIndex) { + [NSException raise:NSRangeException format:@"SPMySQLStreamingResult results can only be accessed linearly"]; + } + + // In full streaming mode return one row at a time. Retrieve the row. + id theRow = SPMySQLResultGetRow(self, SPMySQLResultRowAsDefault); + + // If nil was returned the end of the result resource has been reached + if (!theRow) return 0; + + // Add the row to the result stack and update state + stackbuf[0] = theRow; + state->state += 1; + state->itemsPtr = stackbuf; + state->mutationsPtr = (unsigned long *)self; + + return 1; +} + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLStringAdditions.h b/Frameworks/SPMySQLFramework/Source/SPMySQLStringAdditions.h new file mode 100644 index 00000000..b1f9ccd0 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLStringAdditions.h @@ -0,0 +1,39 @@ +// +// $Id$ +// +// SPMySQLStringAdditions.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 8, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + + +@interface NSString (SPMySQLStringAdditions) + +- (NSString *)mySQLBacktickQuotedString; +- (NSString *)mySQLTickQuotedString; + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLStringAdditions.m b/Frameworks/SPMySQLFramework/Source/SPMySQLStringAdditions.m new file mode 100644 index 00000000..1ea966de --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLStringAdditions.m @@ -0,0 +1,57 @@ +// +// $Id$ +// +// SPMySQLStringAdditions.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 8, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "SPMySQLStringAdditions.h" + +@implementation NSString (SPMySQLStringAdditions) + +/** + * Returns the string quoted with backticks as required for MySQL identifiers + * eg.: tablename => `tablename` + * my`table => `my``table` + */ +- (NSString *)mySQLBacktickQuotedString +{ + return [NSString stringWithFormat: @"`%@`", [self stringByReplacingOccurrencesOfString:@"`" withString:@"``"]]; +} + +/** + * Returns the string quoted with ticks as required for MySQL identifiers + * eg.: tablename => 'tablename' + * my'table => 'my''table' + */ +- (NSString *)mySQLTickQuotedString +{ + return [NSString stringWithFormat: @"'%@'", [self stringByReplacingOccurrencesOfString:@"'" withString:@"''"]]; +} + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLUtilities.h b/Frameworks/SPMySQLFramework/Source/SPMySQLUtilities.h new file mode 100644 index 00000000..0a8c19b0 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLUtilities.h @@ -0,0 +1,45 @@ +// +// $Id$ +// +// Locking.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 6, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#include <mach/mach_time.h> + +/** + * Define a project function to make it easier to use mach_absolute_time() + * to track monotonically increasing time. + */ +static double _elapsedSecondsSinceAbsoluteTime(uint64_t comparisonTime) +{ + uint64_t elapsedTime_t = mach_absolute_time() - comparisonTime; + Nanoseconds elapsedTime = AbsoluteToNanoseconds(*(AbsoluteTime *)&(elapsedTime_t)); + + return (((double)UnsignedWideToUInt64(elapsedTime)) * 1e-9); +}
\ No newline at end of file diff --git a/Frameworks/SPMySQLFramework/build-mysql-client.sh b/Frameworks/SPMySQLFramework/build-mysql-client.sh new file mode 100755 index 00000000..45e3c465 --- /dev/null +++ b/Frameworks/SPMySQLFramework/build-mysql-client.sh @@ -0,0 +1,171 @@ +#! /bin/ksh + +# +# $Id$ +# +# build-mysql-client.sh +# sequel-pro +# +# Created by Stuart Connolly (stuconnolly.com) +# Copyright (c) 2009 Stuart Connolly. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# More info at <http://code.google.com/p/sequel-pro/> + +# Builds the MySQL client libraries for distrubution in Sequel Pro's MySQL framework. +# +# Paramters: -s -- The path to the MySQL source directory. +# -q -- Quiet. Don't output any compiler messages. +# -c -- Clean the source after build completes. +# -d -- Debug. Output the build statements. + +QUIET='NO' +DEBUG='NO' +CLEAN='NO' + +# C/C++ compiler flags +export CFLAGS='-isysroot /Developer/SDKs/MacOSX10.5.sdk -arch ppc -arch i386 -arch x86_64 -O3 -fno-omit-frame-pointer -fno-exceptions -mmacosx-version-min=10.5' +export CXXFLAGS='-isysroot /Developer/SDKs/MacOSX10.5.sdk -arch ppc -arch i386 -arch x86_64 -O3 -fno-omit-frame-pointer -felide-constructors -fno-exceptions -fno-rtti -mmacosx-version-min=10.5' + +CONFIGURE_OPTIONS='--without-server --enable-thread-safe-client --disable-dependency-tracking --enable-local-infile --with-ssl --enable-assembler --with-mysqld-ldflags=-all-static' +BINARY_DISTRIBUTION_SCRIPT='scripts/make_binary_distribution' + +usage() +{ + cat <<!EOF +Usage: $(basename $0): -s <mysql_source_path> [-q -c -d] + +Where: -s -- Path to the MySQL source directory + -q -- Be quiet during the build. Suppress all compiler messages + -c -- Clean the source directory after the build completes + -d -- Debug. Output all the build commands +!EOF +} + +if [ $# -eq 0 ] +then + echo "Invalid number of arguments. I need the path to the MySQL source directory." + echo '' + usage + exit 1 +fi + +echo '' +echo "This script builds the MySQL client libraries for distribution in Sequel Pro's MySQL framework." +echo 'They are all built as 3-way binaries (32 bit PPC, 32/64 bit i386).' +echo '' +echo -n 'This may take a while, are you sure you want to continue [y | n]: ' + +read CONTINUE + +if [ "x${CONTINUE}" == 'xn' ] +then + echo 'Aborting...' + exit 0 +fi + +while getopts ':s:qcd' OPTION +do + case "$OPTION" in + s) MYSQL_SOURCE_DIR="$OPTARG";; + q) QUIET='YES';; + c) CLEAN='YES';; + d) DEBUG='YES';; + *) echo 'Unrecognised option'; usage; exit 1;; + esac +done + +if [ ! -d "$MYSQL_SOURCE_DIR" ] +then + echo "MySQL source directory does not exist at path '${MYSQL_SOURCE_DIR}'." + echo 'Exiting...' + exit 1 +fi + +# Change to source directory +cd "$MYSQL_SOURCE_DIR" + +echo 'Configuring MySQL source...' + +if [ "x${DEBUG}" == 'xYES' ] +then + echo "${MYSQL_SOURCE_DIR}/configure" "$CONFIGURE_OPTIONS" +fi + +if [ "x${QUIET}" == 'xYES' ] +then + ./configure $CONFIGURE_OPTIONS > /dev/null +else + ./configure $CONFIGURE_OPTIONS +fi + +if [ $? -eq 0 ] +then + echo 'Configure successfully completed' +else + echo 'Configure failed. Exiting...' + exit 1 +fi + +echo 'Building client libraries...' + +if [ "x${QUIET}" == 'xYES' ] +then + make > /dev/null +else + make +fi + +if [ $? -eq 0 ] +then + echo 'Building libraries successfully completed' +else + echo 'Building libraries failed. Exiting...' + exit 1 +fi + +echo 'Building binary distribution...' + +if [ "x${QUIET}" == 'xYES' ] +then + $BINARY_DISTRIBUTION_SCRIPT > /dev/null +else + $BINARY_DISTRIBUTION_SCRIPT +fi + +if [ $? -eq 0 ] +then + echo 'Building binary distribution successfully completed' +else + echo 'Building binary distribution failed. Exiting...' + exit 1 +fi + +if [ "x${CLEAN}" == 'xYES' ] +then + echo 'Cleaning build...' + + if [ "x${QUIET}" == 'xYES' ] + then + make clean > /dev/null + else + make clean + fi +fi + +echo 'Building MySQL client libraries successfully completed.' + +exit 0 |