/* * libdbi - database independent abstraction layer for C. * Copyright (C) 2001-2002, David Parker and Mark Tobenkin. * http://libdbi.sourceforge.net * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * dbd_mysql.c: MySQL database support (using libmysqlclient) * Copyright (C) 2001-2002, Mark Tobenkin * http://libdbi.sourceforge.net * * $Id: dbd_mysql.c,v 1.110 2013/10/27 22:40:36 mhoenicka Exp $ */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE /* we need asprintf */ #ifndef HAVE_STRTOLL long long strtoll(const char *nptr, char **endptr, int base); #endif #ifdef __MINGW32__ #include #endif #include #include #include #include #include #include #include #include #include "dbd_mysql.h" static const dbi_info_t driver_info = { "mysql", "MySQL database support (using libmysqlclient)", "Mark M. Tobenkin ", "http://libdbi-drivers.sourceforge.net", "dbd_mysql v" VERSION, __DATE__ }; static const char *custom_functions[] = MYSQL_CUSTOM_FUNCTIONS; static const char *reserved_words[] = MYSQL_RESERVED_WORDS; /* encoding strings, array is terminated by a pair of empty strings */ static const char mysql_encoding_hash[][16] = { /* MySQL, www.iana.org */ "ascii", "US-ASCII", "ujis", "EUC-JP", "euc_kr", "EUC-KR", "euckr", "EUC-KR", "utf8", "UTF-8", "latin1", "ISO-8859-1", "latin2", "ISO-8859-2", "latin5", "ISO-8859-9", "latin7", "ISO-8859-13", "greek", "ISO-8859-7", "hebrew", "ISO-8859-8", "koi8_ru", "KOI8-R", "koi8r", "KOI8-R", "koi8_ukr","KOI8-U", "koi8u","KOI8-U", "cp1251","windows-1251", "win1250","windows-1250", "cp1250","windows-1250", "cp1256","windows-1256", "win1257","windows-1257", "cp1257","windows-1257", "cp850","IBM850", "cp852","IBM852", "dos","IBM865", "cp866","IBM866", "big5","Big5", "gb2312","GB2312", "sjis","Shift_JIS", "danish","ISO646-NO", "hungarian","ISO646-HU", "gbk","GBK", "ucs2","ISO-10646-UCS-2", "dec8","DEC-MCS", "tis620","TIS-620", "hp8","hp-roman8", "", "" }; /* forward declarations of local functions */ void _translate_mysql_type(MYSQL_FIELD *field, unsigned short *type, unsigned int *attribs); void _get_field_info(dbi_result_t *result); void _get_row_data(dbi_result_t *result, dbi_row_t *row, unsigned long long rowidx); void dbd_register_driver(const dbi_info_t **_driver_info, const char ***_custom_functions, const char ***_reserved_words) { /* this is the first function called after the driver module is loaded into memory */ *_driver_info = &driver_info; *_custom_functions = custom_functions; *_reserved_words = reserved_words; } int dbd_initialize(dbi_driver_t *driver) { /* perform any database-specific server initialization. * this is called right after dbd_register_driver(). * return -1 on error, 0 on success. * if -1 is returned, the driver will not * be added to the list of available drivers. */ /* this indicates the driver can be safely unloaded when libdbi is shut down. Change the value to '0' (zero) if the driver, or a library it is linked against, installs exit handlers via atexit() */ _dbd_register_driver_cap(driver, "safe_dlclose", 1); /* this indicates the database engine supports transactions */ _dbd_register_driver_cap(driver, "transaction_support", 1); /* this indicates the database engine supports savepoints */ _dbd_register_driver_cap(driver, "savepoint_support", 1); return 0; } int dbd_finalize(dbi_driver_t *driver) { /* perform any database-specific client library shutdown. * this is called right before dlclose()ing the driver. * return -1 on error, 0 on success. */ /* free memory allocated by the library. mysql_server_end() is deprecated as of MySQL 5.0.3, but the replacement mysql_library_end() is simply an alias defined in mysql.h. Leave this as is until it breaks */ mysql_server_end(); return 0; } int dbd_connect(dbi_conn_t *conn) { MYSQL *mycon; dbi_result_t *result = NULL; char* sql_cmd; unsigned long client_flags = 0; long n_port = 0; const char *host = dbi_conn_get_option(conn, "host"); const char *username = dbi_conn_get_option(conn, "username"); const char *password = dbi_conn_get_option(conn, "password"); const char *dbname = dbi_conn_get_option(conn, "dbname"); const char *encoding = dbi_conn_get_option(conn, "encoding"); const char *port = dbi_conn_get_option(conn, "port"); int reconnect; if (port) { n_port = strtol(port, NULL, 10); } else { n_port = (long)dbi_conn_get_option_numeric(conn, "port"); } /* if no port was specified, use MySQL's default port to let things run gracefully */ if (n_port == 0) { n_port = 3306; } int timeout = dbi_conn_get_option_numeric(conn, "timeout"); /* mysql specific options */ const char *unix_socket = dbi_conn_get_option(conn, "mysql_unix_socket"); /* we honor the mysql_compression flag for the sake of backwards compatibility. Newer code should use the mysql_client_whatever flags instead */ client_flags |= (dbi_conn_get_option_numeric(conn, "mysql_compression") > 0) ? CLIENT_COMPRESS : 0; client_flags |= (dbi_conn_get_option_numeric(conn, "mysql_client_compress") > 0) ? CLIENT_COMPRESS : 0; client_flags |= (dbi_conn_get_option_numeric(conn, "mysql_client_found_rows") > 0) ? CLIENT_FOUND_ROWS : 0; client_flags |= (dbi_conn_get_option_numeric(conn, "mysql_client_ignore_SPACE") > 0) ? CLIENT_IGNORE_SPACE : 0; client_flags |= (dbi_conn_get_option_numeric(conn, "mysql_client_interactive") > 0) ? CLIENT_INTERACTIVE : 0; client_flags |= (dbi_conn_get_option_numeric(conn, "mysql_client_local_files") > 0) ? CLIENT_LOCAL_FILES : 0; client_flags |= (dbi_conn_get_option_numeric(conn, "mysql_client_multi_statements") > 0) ? CLIENT_MULTI_STATEMENTS : 0; client_flags |= (dbi_conn_get_option_numeric(conn, "mysql_client_multi_results") > 0) ? CLIENT_MULTI_RESULTS : 0; client_flags |= (dbi_conn_get_option_numeric(conn, "mysql_client_no_schema") > 0) ? CLIENT_NO_SCHEMA : 0; client_flags |= (dbi_conn_get_option_numeric(conn, "mysql_client_odbc") > 0) ? CLIENT_ODBC : 0; mycon = mysql_init(NULL); if (!mycon) { _dbd_internal_error_handler(conn, NULL, DBI_ERROR_NOMEM); return -2; } if (timeout != -1) { /* option was specified */ /* the mysql_options prototype is braindead */ mysql_options(mycon, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); } reconnect = dbi_conn_require_option_numeric(conn, "reconnect"); if (reconnect >= 0) { my_bool opt = reconnect; mysql_options(mycon, MYSQL_OPT_RECONNECT, &opt); } if (!mysql_real_connect(mycon, host, username, password, dbname, (unsigned int)n_port, unix_socket, client_flags)) { /* printf("mysql_real_connect failed with host=%s\nusername=%s\npassword=%s\ndbname=%s\nport=%s\n", username,password,dbname,port); */ conn->connection = mycon; // still need this set so _error_handler can grab information _dbd_internal_error_handler(conn, NULL, DBI_ERROR_DBD); mysql_close(mycon); conn->connection = NULL; // myconn no longer valid return -2; } else { conn->connection = mycon; if (dbname) conn->current_db = strdup(dbname); } if (encoding && *encoding) { /* set connection encoding */ if (!strcmp(encoding, "auto")) { encoding = dbd_get_encoding(conn); if (encoding) { if (mysql_set_character_set(conn->connection, dbd_encoding_from_iana(encoding)) != 0) printf("failure on charset\n"); } } else { if (mysql_set_character_set(conn->connection, dbd_encoding_from_iana(encoding)) != 0) printf("failure on charset\n"); } } return 0; } int dbd_disconnect(dbi_conn_t *conn) { if (conn->connection) { mysql_close(conn->connection); /* added to resolve memory leak in threadsafe mysqlclient library: assume each thread has it's own connection */ if(mysql_thread_safe()) { mysql_thread_end(); } } return 0; } int dbd_fetch_row(dbi_result_t *result, unsigned long long rowidx) { dbi_row_t *row = NULL; if (result->result_state == NOTHING_RETURNED) return 0; if (result->result_state == ROWS_RETURNED) { /* get row here */ row = _dbd_row_allocate(result->numfields); _get_row_data(result, row, rowidx); _dbd_row_finalize(result, row, rowidx); } return 1; /* 0 on error, 1 on successful fetchrow */ } int dbd_free_query(dbi_result_t *result) { if (result->result_handle) mysql_free_result(result->result_handle); return 0; } int dbd_goto_row(dbi_result_t *result, unsigned long long rowidx, unsigned long long currowidx) { /* * libmysqlclient, as of mysql-community-server-5.6.12, still uses * a singly-linked list to collect rows, which explains the abysmal * performance when seeking to large row indices. */ if (rowidx != currowidx + 1) { /* * The return type of this function is indeed void, so it is * unspecified what happens if rowidx is outside the range. The * calling function must therefore make sure the row index is * valid. */ mysql_data_seek(result->result_handle, rowidx); } return 1; } int dbd_get_socket(dbi_conn_t *conn){ MYSQL *mycon = conn->connection; return mycon ? mycon->net.fd : -1; } const char *dbd_get_encoding(dbi_conn_t *conn){ MYSQL *mycon = conn->connection; const char* my_enc = NULL; const char* iana_enc = NULL; dbi_result dbires = NULL; dbi_result dbires1 = NULL; dbi_result dbires2 = NULL; const char* schemastring; const char* encodingopt; char* sql_cmd; if (!mycon) return NULL; /* quite a mess. Set the dbi option "encoding" to "auto" to get a behaviour similar to PostgreSQL where the default connection encoding is identical to the database encoding. If you do not use this option, or if you explicitly requested a particular connection encoding, this encoding will be returned [it has been set in dbd_connect()] */ encodingopt = dbi_conn_get_option(conn, "encoding"); if (encodingopt && !strcmp(encodingopt, "auto")) { asprintf(&sql_cmd, "SHOW CREATE DATABASE %s", conn->current_db); /* printf("SHOW CREATE DATABASE %s", conn->current_db); */ dbires = dbi_conn_query(conn, sql_cmd); if (dbires && dbi_result_next_row(dbires)) { /* 4.x apparently returns these values as binary, 5.x as string */ if (dbi_result_get_field_type_idx(dbires, 2) == DBI_TYPE_STRING) { schemastring = dbi_result_get_string_idx(dbires, 2); } else { schemastring = dbi_result_get_binary_idx(dbires, 2); } if (schemastring && *schemastring) { my_enc = strstr(schemastring, "CHARACTER SET"); /* printf("schemastring:%s<current_db); */ dbires1 = dbi_conn_query(conn, sql_cmd); if (dbires1 && dbi_result_next_row(dbires1)) { if (dbi_result_get_field_type_idx(dbires1, 2) == DBI_TYPE_STRING) { my_enc = dbi_result_get_string_idx(dbires1, 2); } else { my_enc = dbi_result_get_binary_idx(dbires1, 2); } } /* my_enc = mysql_character_set_name(mycon); */ /* printf("mysql claims enc:%s<<\n", my_enc); */ if (!my_enc) { /* this may be a pre-4.1 server w/o per-connection encoding */ asprintf(&sql_cmd, "SHOW VARIABLES LIKE '%s'", "character_set"); /* printf("SHOW CREATE DATABASE %s", conn->current_db); */ dbires2 = dbi_conn_query(conn, sql_cmd); if (dbires2 && dbi_result_next_row(dbires2)) { if (dbi_result_get_field_type_idx(dbires2, 2) == DBI_TYPE_STRING) { my_enc = dbi_result_get_string_idx(dbires2, 2); } else { my_enc = dbi_result_get_binary_idx(dbires2, 2); } } } free(sql_cmd); if (!my_enc) { if (dbires) { dbi_result_free(dbires); } if (dbires1) { dbi_result_free(dbires1); } if (dbires2) { dbi_result_free(dbires1); } return NULL; } } iana_enc = dbd_encoding_to_iana(my_enc); if (dbires) { dbi_result_free(dbires); } if (dbires1) { dbi_result_free(dbires1); } if (dbires2) { dbi_result_free(dbires2); } return iana_enc; } const char* dbd_encoding_to_iana(const char *db_encoding) { int i = 0; /* loop over all even entries in hash and compare to menc */ while (*mysql_encoding_hash[i]) { if (!strncmp(mysql_encoding_hash[i], db_encoding, strlen(mysql_encoding_hash[i]))) { /* return corresponding odd entry */ return mysql_encoding_hash[i+1]; } i += 2; } /* don't know how to translate, return original encoding */ return db_encoding; } const char* dbd_encoding_from_iana(const char *iana_encoding) { int i = 0; /* loop over all odd entries in hash and compare to ienc */ while (*mysql_encoding_hash[i+1]) { if (!strcmp(mysql_encoding_hash[i+1], iana_encoding)) { /* return corresponding even entry */ return mysql_encoding_hash[i]; } i += 2; } /* don't know how to translate, return original encoding */ return iana_encoding; } char *dbd_get_engine_version(dbi_conn_t *conn, char *versionstring) { dbi_result_t *dbi_result; const char *versioninfo = NULL; /* initialize return string */ *versionstring = '\0'; dbi_result = dbd_query(conn, "SELECT VERSION()"); if (dbi_result) { if (dbi_result_next_row(dbi_result)) { versioninfo = dbi_result_get_string_idx(dbi_result, 1); strncpy(versionstring, versioninfo, VERSIONSTRING_LENGTH-1); versionstring[VERSIONSTRING_LENGTH-1] = '\0'; } dbi_result_free(dbi_result); } return versionstring; } dbi_result_t *dbd_list_dbs(dbi_conn_t *conn, const char *pattern) { dbi_result_t *res; char *sql_cmd; if (pattern == NULL) { return dbd_query(conn, "SHOW DATABASES"); } else { asprintf(&sql_cmd, "SHOW DATABASES LIKE '%s'", pattern); res = dbd_query(conn, sql_cmd); free(sql_cmd); return res; } } dbi_result_t *dbd_list_tables(dbi_conn_t *conn, const char *db, const char *pattern) { dbi_result_t *res; char *sql_cmd; if (db == NULL || db[0] == '\0') { return dbd_query(conn, "SHOW TABLES"); } if (pattern == NULL) { asprintf(&sql_cmd, "SHOW TABLES FROM %s", db); res = dbd_query(conn, sql_cmd); free(sql_cmd); return res; } else { asprintf(&sql_cmd, "SHOW TABLES FROM %s LIKE '%s'", db, pattern); res = dbd_query(conn, sql_cmd); free(sql_cmd); return res; } } size_t dbd_quote_string(dbi_driver_t *driver, const char *orig, char *dest) { /* foo's -> 'foo\'s' */ unsigned long len; strcpy(dest, "'"); len = mysql_escape_string(dest+1, orig, strlen(orig)); strcat(dest, "'"); return (size_t)(len+2); } size_t dbd_conn_quote_string(dbi_conn_t *conn, const char *orig, char *dest) { /* foo's -> 'foo\'s' */ unsigned long len; MYSQL *mycon = conn->connection; strcpy(dest, "'"); len = mysql_real_escape_string(mycon, dest+1, orig, strlen(orig)); strcat(dest, "'"); return (size_t)(len+2); } size_t dbd_quote_binary(dbi_conn_t *conn, const unsigned char* orig, size_t from_length, unsigned char **ptr_dest) { unsigned char *temp; size_t len; MYSQL *mycon = conn->connection; /* we allocate what mysql_real_escape_string needs, plus an extra two escape chars and a terminating zero*/ temp = malloc(2*from_length+1+2); if (!temp) { return DBI_LENGTH_ERROR; } strcpy(temp, "'"); len = mysql_real_escape_string(mycon, temp + 1, orig, from_length); strcpy(temp + len + 1, "\'"); *ptr_dest = temp; return len + 2; } dbi_result_t *dbd_query(dbi_conn_t *conn, const char *statement) { /* allocate a new dbi_result_t and fill its applicable members: * * result_handle, numrows_matched, and numrows_changed. * everything else will be filled in by DBI */ dbi_result_t *result; MYSQL_RES *res; if (mysql_query(conn->connection, statement)) { fprintf(stderr, "mysql error: %s\n", mysql_error(conn->connection)); return NULL; } res = mysql_store_result(conn->connection); /* if res is null and mysql reports an error, we encountered a select failure */ if (!res && mysql_errno(conn->connection)) { return NULL; } /* else: if res is null and no error is indicated, the query was something that doesn't return rows (like an INSERT) */ result = _dbd_result_create(conn, res, (res ? mysql_num_rows(res) : 0), mysql_affected_rows(conn->connection)); if (res) { _dbd_result_set_numfields(result, mysql_num_fields(result->result_handle)); _get_field_info(result); } else { _dbd_result_set_numfields(result, 0); } return result; } dbi_result_t *dbd_query_null(dbi_conn_t *conn, const unsigned char *statement, size_t st_length) { dbi_result_t *result; MYSQL_RES *res; if (mysql_real_query(conn->connection, statement, st_length)) { return NULL; } res = mysql_store_result(conn->connection); /* if res is null, the query was something that doesn't return rows (like an INSERT) */ result = _dbd_result_create(conn, res, (res ? mysql_num_rows(res) : 0), mysql_affected_rows(conn->connection)); if (res) { _dbd_result_set_numfields(result, mysql_num_fields(result->result_handle)); _get_field_info(result); } return result; } int dbd_transaction_begin(dbi_conn_t *conn) { if (dbd_query(conn, "START TRANSACTION") == NULL) { return 1; } else { return 0; } } int dbd_transaction_commit(dbi_conn_t *conn) { if (dbd_query(conn, "COMMIT") == NULL) { return 1; } else { return 0; } } int dbd_transaction_rollback(dbi_conn_t *conn) { if (dbd_query(conn, "ROLLBACK") == NULL) { return 1; } else { return 0; } } int dbd_savepoint(dbi_conn_t *conn, const char *savepoint) { char* query; if (!savepoint) { return 1; } asprintf(&query, "SAVEPOINT %s", savepoint); if (dbd_query(conn, query) == NULL) { free(query); return 1; } else { free(query); return 0; } } int dbd_rollback_to_savepoint(dbi_conn_t *conn, const char *savepoint) { char* query; if (!savepoint) { return 1; } asprintf(&query, "ROLLBACK TO SAVEPOINT %s", savepoint); if (dbd_query(conn, query) == NULL) { free(query); return 1; } else { free(query); return 0; } } int dbd_release_savepoint(dbi_conn_t *conn, const char *savepoint) { char* query; if (!savepoint) { return 1; } asprintf(&query, "RELEASE SAVEPOINT %s", savepoint); if (dbd_query(conn, query) == NULL) { free(query); return 1; } else { free(query); return 0; } } const char *dbd_select_db(dbi_conn_t *conn, const char *db) { if (mysql_select_db(conn->connection, db)) { _dbd_internal_error_handler(conn, NULL, DBI_ERROR_DBD); return ""; } if (conn->current_db) { free(conn->current_db); } conn->current_db = strdup(db); return db; } int dbd_geterror(dbi_conn_t *conn, int *err_no, char **errstr) { /* put error number into err_no, error string into errstr * return 0 if error, 1 if err_no filled, 2 if errstr filled, 3 if both err_no and errstr filled */ if (strcmp("",mysql_error(conn->connection)) == 0) { return -1; } *err_no = mysql_errno(conn->connection); *errstr = strdup(mysql_error(conn->connection)); return 3; } unsigned long long dbd_get_seq_last(dbi_conn_t *conn, const char *sequence) { return mysql_insert_id(conn->connection); } unsigned long long dbd_get_seq_next(dbi_conn_t *conn, const char *sequence) { return 0; } int dbd_ping(dbi_conn_t *conn) { MYSQL *mysql = conn->connection; if (mysql_ping(mysql) == 0) { // server is alive and kicking return 1; } return 0; } /* CORE MYSQL DATA FETCHING STUFF */ void _translate_mysql_type(MYSQL_FIELD *field, unsigned short *type, unsigned int *attribs) { unsigned int _type = 0; unsigned int _attribs = 0; switch (field->type) { case FIELD_TYPE_TINY: _type = DBI_TYPE_INTEGER; _attribs |= DBI_INTEGER_SIZE1; break; case FIELD_TYPE_YEAR: _attribs |= DBI_INTEGER_UNSIGNED; case FIELD_TYPE_SHORT: _type = DBI_TYPE_INTEGER; _attribs |= DBI_INTEGER_SIZE2; break; case FIELD_TYPE_INT24: _type = DBI_TYPE_INTEGER; _attribs |= DBI_INTEGER_SIZE3; break; case FIELD_TYPE_LONG: _type = DBI_TYPE_INTEGER; _attribs |= DBI_INTEGER_SIZE4; break; #ifdef FIELD_TYPE_BIT case FIELD_TYPE_BIT: // BIT field (MySQL 5.0.3 and up) _attribs |= DBI_INTEGER_UNSIGNED; #endif case FIELD_TYPE_LONGLONG: _type = DBI_TYPE_INTEGER; _attribs |= DBI_INTEGER_SIZE8; break; case FIELD_TYPE_FLOAT: _type = DBI_TYPE_DECIMAL; _attribs |= DBI_DECIMAL_SIZE4; break; case FIELD_TYPE_DOUBLE: _type = DBI_TYPE_DECIMAL; _attribs |= DBI_DECIMAL_SIZE8; break; case FIELD_TYPE_DATE: /* TODO parse n stuph to native DBI unixtime type. for now, string */ _type = DBI_TYPE_DATETIME; _attribs |= DBI_DATETIME_DATE; break; case FIELD_TYPE_TIME: _type = DBI_TYPE_DATETIME; _attribs |= DBI_DATETIME_TIME; break; case FIELD_TYPE_DATETIME: case FIELD_TYPE_TIMESTAMP: _type = DBI_TYPE_DATETIME; _attribs |= DBI_DATETIME_DATE | DBI_DATETIME_TIME; break; case FIELD_TYPE_TINY_BLOB: case FIELD_TYPE_MEDIUM_BLOB: case FIELD_TYPE_LONG_BLOB: case FIELD_TYPE_BLOB: default: // see MySQL reference manual 22.2.1 on detecting binary flavour // http://dev.mysql.com/doc/refman/5.0/en/c-api-datatypes.html if(field->charsetnr == 63){ _type = DBI_TYPE_BINARY; break; } /* fallthru */ case FIELD_TYPE_VAR_STRING: case FIELD_TYPE_STRING: case FIELD_TYPE_ENUM: case FIELD_TYPE_SET: _type = DBI_TYPE_STRING; break; #ifdef FIELD_TYPE_NEWDECIMAL case FIELD_TYPE_NEWDECIMAL: // Precision math DECIMAL or NUMERIC field (MySQL 5.0.3 and up) #endif case FIELD_TYPE_DECIMAL: /* decimal is actually a string, has arbitrary precision, no floating point rounding */ _type = DBI_TYPE_XDECIMAL; break; } *type = _type; *attribs = _attribs; } void _get_field_info(dbi_result_t *result) { unsigned int idx = 0; MYSQL_FIELD *field; unsigned short fieldtype; unsigned int fieldattribs; field = mysql_fetch_fields(result->result_handle); while (idx < result->numfields) { _translate_mysql_type(&field[idx], &fieldtype, &fieldattribs); if ((fieldtype == DBI_TYPE_INTEGER) && (field[idx].flags & UNSIGNED_FLAG)) fieldattribs |= DBI_INTEGER_UNSIGNED; _dbd_result_add_field(result, idx, field[idx].name, fieldtype, fieldattribs); idx++; } } void _get_row_data(dbi_result_t *result, dbi_row_t *row, unsigned long long rowidx) { MYSQL_RES *_res = result->result_handle; MYSQL_ROW _row; unsigned int curfield = 0; char *raw = NULL; long *strsizes; unsigned int sizeattrib; dbi_data_t *data; _row = mysql_fetch_row(_res); if (_row == NULL) return; strsizes = mysql_fetch_lengths(_res); while (curfield < result->numfields) { raw = _row[curfield]; data = &row->field_values[curfield]; row->field_sizes[curfield] = 0; /* this will be set to the string size later on if the field is indeed a string */ if ((strsizes[curfield] == 0) && (raw == NULL)) { _set_field_flag( row, curfield, DBI_VALUE_NULL, 1); curfield++; continue; } if (raw == NULL && strsizes[curfield] != 0) { fprintf(stderr, "WARNING: field size indicates non-NULL field, but raw is NULL.\n"); ++curfield; continue; } switch (result->field_types[curfield]) { case DBI_TYPE_INTEGER: switch (result->field_attribs[curfield] & DBI_INTEGER_SIZEMASK) { case DBI_INTEGER_SIZE1: data->d_char = (char) strtol(raw,NULL,10); break; case DBI_INTEGER_SIZE2: data->d_short = (short) strtol(raw,NULL,10); break; case DBI_INTEGER_SIZE3: case DBI_INTEGER_SIZE4: data->d_long = (int) strtol(raw,NULL,10); break; case DBI_INTEGER_SIZE8: data->d_longlong = (long long) strtoll(raw,NULL,10); break; default: break; } break; case DBI_TYPE_DECIMAL: switch (result->field_attribs[curfield] & DBI_DECIMAL_SIZEMASK) { case DBI_DECIMAL_SIZE4: data->d_float = (float) strtod(raw, NULL); break; case DBI_DECIMAL_SIZE8: data->d_double = (double) strtod(raw, NULL); break; default: break; } break; default: case DBI_TYPE_STRING: case DBI_TYPE_XDECIMAL: data->d_string = strdup(raw); row->field_sizes[curfield] = strsizes[curfield]; break; case DBI_TYPE_BINARY: row->field_sizes[curfield] = strsizes[curfield]; data->d_string = malloc(strsizes[curfield]+1); // one extra char for libdbi's null if (!data->d_string) { break; } memcpy(data->d_string, raw, strsizes[curfield]); /* todo: revise as this looks kinda strange */ data->d_string[strsizes[curfield]] = '\0'; // manually null-terminate the data so C string casting still works if (dbi_conn_get_option_numeric(result->conn, "mysql_include_trailing_null") == 1) { row->field_sizes[curfield]++; // the extra null we added was actually part of the original data } break; case DBI_TYPE_DATETIME: sizeattrib = result->field_attribs[curfield] & (DBI_DATETIME_DATE|DBI_DATETIME_TIME); _dbd_parse_datetimex(raw, sizeattrib, &data->d_datetimex); break; } curfield++; } }