/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * libdbi - database independent abstraction layer for C. * Copyright (C) 2001-2004, 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_firebird.c: Firebird/Interbase database support * Copyright (C) 2004-2005, Christian M. Stamgren * http://libdbi-drivers.sourceforge.net * */ /** * Warning!! Warning!! Warning!! * This driver has a big gotha, You can't use dbi_result_numrows() * This is because I don't know of a way to fetch the number of rows in a result set from firebird. * The only way to do it is to fetch all rows and count them..... */ #ifdef HAVE_CONFIG_H # include #endif #define _GNU_SOURCE /* we need asprintf */ #include #include #include #include #include #include "dbd_firebird.h" #include "firebird_charsets.h" #include "utility.h" /* is this correct? Firebird 1.5 used this instead: #define FB_ALIGN(n,b) ((n+b-1)&~(b-1)) */ #ifndef FB_ALIGN # define FB_ALIGN(n,b) ((n+1) & ~1) /* #define FB_ALIGN(n,b) ((n+b-1)&~(b-1)) */ #endif /* firebird versions prior to 2.0 do not typedef ISC_SCHAR but use TEXT instead. ISC_SCHAR's presence is checked for by the configure script */ #ifndef HAVE_ISC_SCHAR #define ISC_SCHAR TEXT #endif static const dbi_info_t driver_info = { "firebird", "Firebird/Interbase database support", "Christian M. Stamgren ", "http://libdbi-drivers.sourceforge.net", "dbd_firebird v" VERSION, __DATE__ }; static const char *custom_functions[] = FIREBIRD_CUSTOM_FUNCTIONS; static const char *reserved_words[] = FIREBIRD_RESERVED_WORDS; static const char firebird_encoding_NONE[] = "NONE"; /* the encoding strings */ extern char version[]; //this is dirty void dbd_register_driver(const dbi_info_t **_driver_info, const char ***_custom_functions, const char ***_reserved_words) { *_driver_info = &driver_info; *_custom_functions = custom_functions; *_reserved_words = reserved_words; } int dbd_initialize(dbi_driver_t *driver) { /* this indicates the driver cannot be safely unloaded when libdbi is shut down. Change the value to '1' if the driver, or a library it is linked against, is finally fixed to not install exit handlers via atexit() */ _dbd_register_driver_cap(driver, "safe_dlclose", 0); /* 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. */ return 0; } int dbd_connect(dbi_conn_t *conn) { return _dbd_real_connect(conn, NULL); } int dbd_disconnect(dbi_conn_t *conn) { ibase_conn_t *iconn = conn->connection; if(iconn != NULL) { isc_commit_transaction(iconn->status_vector, &(iconn->trans)); isc_detach_database(iconn->status_vector, &(iconn->db)); dealocate_iconn(iconn); } 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 -1; if (result->result_state == ROWS_RETURNED) { row = _dbd_row_allocate(result->numfields); if( _get_row_data(result, row, rowidx) == 0 ) return 0; _dbd_row_finalize(result, row, rowidx); } return 1; } int dbd_free_query(dbi_result_t *result) { dbi_conn_t *conn = dbi_result_get_conn(result); ibase_conn_t *iconn = conn->connection; ibase_stmt_t *istmt = result->result_handle; if(istmt != NULL) { isc_dsql_free_statement(iconn->status_vector, &(istmt->stmt), DSQL_drop); free(istmt->osqlda); free(istmt); } istmt = NULL; return 0; } int dbd_goto_row(dbi_result_t *result, unsigned long long rowidx, unsigned long long currowidx) { /* no-op */ return 1; } int dbd_get_socket(dbi_conn_t *conn) { return 0; } /* This implemetation is not the best, * but i cant find a coprrect way of doing this */ const char *dbd_get_encoding(dbi_conn_t *conn) { ibase_conn_t *iconn = conn->connection; if( iconn != NULL) return dbd_encoding_to_iana( iconn->charset ); return firebird_encoding_NONE; } const char* dbd_encoding_to_iana(const char *db_encoding) { register int i = 0; /* loop over all even entries in hash and compare to menc */ while (*firebird_encoding_hash[i]) { if (!strncmp(firebird_encoding_hash[i], db_encoding, strlen(firebird_encoding_hash[i]))) { /* return corresponding odd entry */ return firebird_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) { register int i = 0; /* loop over all odd entries in hash and compare to ienc */ while (*firebird_encoding_hash[i+1]) { if (!strcmp(firebird_encoding_hash[i+1], iana_encoding)) { /* return corresponding even entry */ return firebird_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) { ibase_conn_t *iconn = conn->connection; char *dot; char *start; char *stop; /* Firebird make some easy things hard ... this is one of them ... */ isc_version(&(iconn->db), (isc_callback)_get_firebird_version, NULL); /* version now contains something like: Firebird/linux Intel (access method), version "LI-V1.5.1.4500 Firebird 1.5" */ /* try to locate the version number. Look for the first dot, go back where the number before the dot starts, then walk forward to the last dot or number */ dot = strchr(version, (int)'.'); if (dot) { start = dot-1; while (start>version && isdigit((int)(*(start-1)))) { start--; } stop = start; while (*(stop+1) && (isdigit((int)(*(stop+1))) || *(stop+1)=='.')) { stop++; } if (stop-start < VERSIONSTRING_LENGTH) { /* BAD BAD BAD hack: we chop off the last two digits of the version string as the numeric form can't handle 3+digit sub-versions */ strncpy(versionstring, start, stop-start-1); versionstring[stop-start-1] = '\0'; } } return versionstring; } dbi_result_t *dbd_list_dbs(dbi_conn_t *conn, const char *pattern) { return NULL; } dbi_result_t *dbd_list_tables(dbi_conn_t *conn, const char *db, const char *pattern) { if (pattern == NULL) { return dbd_query(conn, "SELECT RDB$RELATION_NAME FROM RDB$RELATIONS WHERE RDB$SYSTEM_FLAG = 0"); } else { dbi_result_t *res; char *sql_cmd; asprintf(&sql_cmd, "SELECT RDB$RELATION_NAME FROM RDB$RELATIONS WHERE RDB$SYSTEM_FLAG = 0 " " AND RDB$RELATION_NAME LIKE '%s'", 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) { const char *worker = orig; register int i = 0, j = 1; size_t length = strlen(orig); strcpy(dest, "'"); for(i = 0; i <= length; i++) { switch(worker[i]) { case '\'': dest[j++] = '\''; break; } dest[j++] = worker[i]; } strcat(dest, "'"); return j; } size_t dbd_conn_quote_string(dbi_conn_t *conn, const char *orig, char *dest) { return dbd_quote_string(conn->driver, orig, dest); } 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; /* todo: this uses the libdbi builtin encoding routine. firebird may have its own version */ if ((temp = malloc(from_length*2)) == NULL) { return 0; } strcpy((char *)temp, "\'"); if (from_length) { len = _dbd_encode_binary(orig, from_length, temp+1); } else { len = 0; } strcat((char *)temp, "'"); *ptr_dest = temp; return len+2; } dbi_result_t *dbd_query_null(dbi_conn_t *conn, const char unsigned *statement, size_t st_length) { return NULL; } dbi_result_t *dbd_query(dbi_conn_t *conn, const char *statement) { XSQLVAR *var; XSQLDA *sqlda; /* output SQLDA */ isc_stmt_handle stmt = NULL; /* statement handle */ ibase_stmt_t *res; dbi_result_t *result; static char stmt_info[] = { isc_info_sql_stmt_type }; char info_buffer[20]; short l; long statement_type; short num_cols, i; short length, alignment, type, offset; /* long buffer[MAXLEN*8]; */ void* buffer = NULL; unsigned long long numrows = 0, affectedrows = 0; ibase_conn_t *iconn = conn->connection; if (isc_dsql_allocate_statement(iconn->status_vector, &(iconn->db), &stmt)) { if (iconn->status_vector[0] == 1 && iconn->status_vector[1]) { char msg[512]; long* pvector = iconn->status_vector; isc_interprete(msg, &pvector); _dbd_internal_error_handler(conn, msg, DBI_ERROR_CLIENT); } return NULL; } sqlda = malloc(XSQLDA_LENGTH(1)); sqlda->sqln = 1; sqlda->version = 1; if (isc_dsql_prepare(iconn->status_vector, &(iconn->trans), &stmt, 0, (char *)statement, 3, sqlda)) { if (iconn->status_vector[0] == 1 && iconn->status_vector[1]) { char msg[512]; long* pvector = iconn->status_vector; isc_interprete(msg, &pvector); _dbd_internal_error_handler(conn, msg, DBI_ERROR_CLIENT); } free(sqlda); isc_dsql_free_statement(iconn->status_vector, &stmt, DSQL_drop); return NULL; } if (!isc_dsql_sql_info(iconn->status_vector, &stmt, sizeof(stmt_info), stmt_info, sizeof(info_buffer), info_buffer)) { l = (short) isc_vax_integer((char *) info_buffer + 1, 2); statement_type = isc_vax_integer((char *) info_buffer + 3, l); } /* Execute a non-select statement.*/ if (!sqlda->sqld) { if (isc_dsql_execute(iconn->status_vector, &(iconn->trans), &stmt , SQL_DIALECT_V6, NULL)) { if (iconn->status_vector[0] == 1 && iconn->status_vector[1]) { char msg[512]; long* pvector = iconn->status_vector; isc_interprete(msg, &pvector); _dbd_internal_error_handler(conn, msg, DBI_ERROR_CLIENT); } free(sqlda); isc_dsql_free_statement(iconn->status_vector, &stmt, DSQL_drop); return NULL; } /* Commit DDL statements if that is what sql_info says */ if (iconn->trans && (statement_type == isc_info_sql_stmt_ddl)) { if (isc_commit_transaction(iconn->status_vector, &(iconn->trans))) { if (iconn->status_vector[0] == 1 && iconn->status_vector[1]) { char msg[512]; long* pvector = iconn->status_vector; isc_interprete(msg, &pvector); _dbd_internal_error_handler(conn, msg, DBI_ERROR_CLIENT); } free(sqlda); isc_dsql_free_statement(iconn->status_vector, &stmt, DSQL_drop); return NULL; } isc_start_transaction(iconn->status_vector, &(iconn->trans), 1, &(iconn->db), 0, NULL); } /* Process select statements. */ } else { num_cols = sqlda->sqld; numrows = 1; /* Firebird can't say how many rows there is, in this early stage. We need to fetch all rows and count them :( */ /* HACK HACK HACK MH:2008-01-02 */ /* I don't really know how much needs to be allocated here. The Firebird example code and the docs won't tell me. I just know that the previously used hard limit (4096) is not enough to run the test program successfully. I'm assuming here that in the worst case num_cols columns contain strings of the maximum allowed length, and that this is just about sufficient. I may be wasting memory here, or the code just so happens to work for the tests applied. If crashes or strange results are reported, revisit this issue */ buffer = malloc(32768*num_cols); /* Need more room. */ if (sqlda->sqln < num_cols) { sqlda = realloc(sqlda, XSQLDA_LENGTH(num_cols)); sqlda->sqln = num_cols; sqlda->version = 1; if (isc_dsql_describe(iconn->status_vector, &stmt, SQL_DIALECT_V6, sqlda)) { if (iconn->status_vector[0] == 1 && iconn->status_vector[1]) { char msg[512]; long* pvector = iconn->status_vector; isc_interprete(msg, &pvector); _dbd_internal_error_handler(conn, msg, DBI_ERROR_CLIENT); } free(sqlda); isc_dsql_free_statement(iconn->status_vector, &stmt, DSQL_drop); return NULL; } } for (var = sqlda->sqlvar, offset = 0, i = 0; i < num_cols; var++, i++) { length = alignment = var->sqllen; type = var->sqltype & ~1; if (type == SQL_TEXT) alignment = 1; else if (type == SQL_VARYING) { length += sizeof (short) + 1; alignment = sizeof (short); } offset = FB_ALIGN(offset, alignment); var->sqldata = (char *) buffer + offset; offset += length; offset = FB_ALIGN(offset, sizeof (short)); var->sqlind = (short*) ((char *) buffer + offset); offset += sizeof (short); } if (isc_dsql_execute(iconn->status_vector, &(iconn->trans), &stmt, SQL_DIALECT_V6, NULL)) { free(sqlda); if (iconn->status_vector[0] == 1 && iconn->status_vector[1]) { char msg[512]; long* pvector = iconn->status_vector; isc_interprete(msg, &pvector); _dbd_internal_error_handler(conn, msg, DBI_ERROR_CLIENT); } isc_dsql_free_statement(iconn->status_vector, &stmt, DSQL_drop); return NULL; } } res = malloc(sizeof(ibase_stmt_t)); res->stmt = stmt; res->osqlda = sqlda; res->rowcount = 1; result = _dbd_result_create(conn, (void *)res, numrows , affectedrows); _dbd_result_set_numfields(result, res->osqlda->sqld); _get_field_info(result); /* obviously we're not supposed to free this buffer here, but who else is going to do this? */ /* if (buffer) { */ /* free(buffer); */ /* } */ return result; } int dbd_transaction_begin(dbi_conn_t *conn) { if (dbd_query(conn, "SET 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) { ibase_conn_t *iconn = conn->connection; if (!db || !*db) { return NULL; } if (iconn) { isc_commit_transaction(iconn->status_vector, &(iconn->trans)); isc_detach_database(iconn->status_vector, &(iconn->db)); if(conn->current_db) free(conn->current_db); free(iconn); iconn = NULL; } dbi_conn_set_option(conn, "dbname", db); if (dbd_connect(conn)) { return NULL; } return db; } int dbd_geterror(dbi_conn_t *conn, int *err_no, char **errstr) { /* error_message and error_number were already set by calls to _dbd_internal_error_handler */ *err_no = conn->error_number; *errstr = (conn->error_message) ? strdup(conn->error_message):NULL; return 1; } unsigned long long dbd_get_seq_last(dbi_conn_t *conn, const char *sequence) { return return_generator_value(conn, sequence, 0); //0 is currval } unsigned long long dbd_get_seq_next(dbi_conn_t *conn, const char *sequence) { return return_generator_value(conn, sequence, 1); //1 is nextval } int dbd_ping(dbi_conn_t *conn) { char buf[100]; ibase_conn_t *iconn = conn->connection; if (isc_database_info(iconn->status_vector, &(iconn->db), 0, NULL, sizeof(buf), buf)) { free(iconn); if (conn->current_db ) free(conn->current_db); if(! dbd_connect(conn)) return 0; } return 1; }