/* -*- 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 #include "dbd_firebird.h" #include "firebird_charsets.h" #include "utility.h" 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[] = { NULL }; 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) { 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, &(iconn->trans)); isc_detach_database(iconn->status, &(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) { ISC_STATUS_ARRAY status; ibase_stmt_t *istmt = result->result_handle; if(istmt != NULL) { isc_dsql_free_statement(status, &(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) { 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), _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]; unsigned long long numrows = 0, affectedrows = 0; ibase_conn_t *iconn = conn->connection; if (isc_dsql_allocate_statement(iconn->status, &(iconn->db), &stmt)) { return NULL; } sqlda = (XSQLDA *) malloc(XSQLDA_LENGTH (1)); sqlda->sqln = 1; sqlda->version = 1; if (isc_dsql_prepare(iconn->status, &(iconn->trans), &stmt, 0, (char *)statement, 3, sqlda)) { free(sqlda); isc_dsql_free_statement(iconn->status, &stmt, DSQL_drop); return NULL; } if (!isc_dsql_sql_info(iconn->status, &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, &(iconn->trans), &stmt , SQL_DIALECT_V6, NULL)) { free(sqlda); isc_dsql_free_statement(iconn->status, &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, &(iconn->trans))) { free(sqlda); isc_dsql_free_statement(iconn->status, &stmt, DSQL_drop); return NULL; } isc_start_transaction(iconn->status, &(iconn->trans), 1, &(iconn->db), 0, NULL); } } else { /* Process select statements. */ 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 :( */ /* Need more room. */ if (sqlda->sqln < num_cols) { sqlda = (XSQLDA *) realloc(sqlda, XSQLDA_LENGTH(num_cols)); sqlda->sqln = num_cols; sqlda->version = 1; if (isc_dsql_describe(iconn->status, &stmt, SQL_DIALECT_V6, sqlda)) { free(sqlda); isc_dsql_free_statement(iconn->status, &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, &(iconn->trans), &stmt, SQL_DIALECT_V6, NULL)) { free(sqlda); isc_dsql_free_statement(iconn->status, &stmt, DSQL_drop); return NULL; } } res = (ibase_stmt_t *)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); return result; } 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, &(iconn->trans)); isc_detach_database(iconn->status, &(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 *errno, char **errstr) { ibase_conn_t *iconn = conn->connection; TEXT errbuf[MAXLEN]; long sqlcode; if ( conn->connection == NULL) { *errstr = strdup("Unable to connect to database."); return 1; } sqlcode = isc_sqlcode(iconn->status); isc_sql_interprete(sqlcode, errbuf, sizeof(errbuf)); *errstr = strdup(errbuf); 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, &(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; }