From 25f9c0425081cc141e836482c5c3881c78fae1fc Mon Sep 17 00:00:00 2001 From: scott Date: Thu, 29 Nov 2018 16:55:52 +0000 Subject: [PATCH] Prepared queries and parameter support * New con:prepare method returning a prepared statement * Both con:execute and stmt:execute now take optional arguments to pass to the SQL query e.g. ```lua stmt = con:prepare("INSERT INTO T1(F1,F2) VALUES(?,?)") stmt:execute(12, 34) ``` --- src/ls_firebird.c | 1138 ++++++++++++++++++++++++++++++++------------ tests/firebird.lua | 44 ++ 2 files changed, 881 insertions(+), 301 deletions(-) diff --git a/src/ls_firebird.c b/src/ls_firebird.c index d8ba9c0..9f0631e 100644 --- a/src/ls_firebird.c +++ b/src/ls_firebird.c @@ -15,34 +15,72 @@ #include "luasql.h" +/* backwards compat for Lua 5.1 */ +#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM == 501 +static void *luaL_testudata (lua_State *L, int i, const char *tname) +{ + void *p = lua_touserdata(L, i); + luaL_checkstack(L, 2, "not enough stack slots"); + if (p == NULL || !lua_getmetatable(L, i)) { + return NULL; + } else { + int res = 0; + luaL_getmetatable(L, tname); + res = lua_rawequal(L, -1, -2); + lua_pop(L, 2); + if (!res) { + p = NULL; + } + } + return p; +} +#endif + #define LUASQL_ENVIRONMENT_FIREBIRD "Firebird environment" #define LUASQL_CONNECTION_FIREBIRD "Firebird connection" +#define LUASQL_STATEMENT_FIREBIRD "Firebird statement" #define LUASQL_CURSOR_FIREBIRD "Firebird cursor" typedef struct { - short closed; - ISC_STATUS status_vector[20]; /* for error results */ - int lock; /* lock count for open connections */ + short closed; + int lock; /* lock count for open connections */ + ISC_STATUS status_vector[20];/* for error results */ } env_data; typedef struct { - short closed; - env_data* env; /* the DB enviroment this is in */ - isc_db_handle db; /* the database handle */ - char dpb_buffer[256];/* holds the database paramet buffer */ - short dpb_length; /* the used amount of the dpb */ - isc_tr_handle transaction; /* the transaction handle */ - int lock; /* lock count for open cursors */ - int autocommit; /* should each statement be commited */ + /* general */ + short closed; + int lock; /* lock count for open cursors */ + env_data *env; /* the DB enviroment this is in */ + int autocommit; /* should each statement be commited */ + /* implimentation */ + isc_db_handle db; /* the database handle */ + char dpb_buffer[1024]; /* holds the database paramet buffer */ + short dpb_length; /* the used amount of the dpb */ + isc_tr_handle transaction; /* the transaction handle */ + /* config */ + unsigned short dialect; /* dialect of SQL used */ } conn_data; typedef struct { - short closed; - env_data* env; /* the DB enviroment this is in */ - conn_data* conn; /* the DB connection this cursor is from */ - isc_stmt_handle stmt; /* the statement handle */ - int stmt_type; /* the type of the statment */ - XSQLDA *out_sqlda; /* the cursor data array */ + short closed; + int lock; /* lock count for open statements */ + env_data *env; /* the DB enviroment this is in */ + conn_data *conn; /* the DB connection this cursor is from */ + /* implimentation */ + XSQLDA *in_sqlda; /* the parameter data array */ + isc_stmt_handle handle; /* the statement handle */ + int type; /* the statment's type (SELECT, UPDATE, + etc...) */ + unsigned char hidden; /* statement was used interally i.e. from a + direct con:execute */ +} stmt_data; + +typedef struct { + short closed; + env_data *env; /* the DB enviroment this is in */ + stmt_data *stmt; /* the DB statment this cursor is from */ + XSQLDA *out_sqlda; /* the cursor data array */ } cur_data; /* How many fields to pre-alloc to the cursor */ @@ -65,8 +103,26 @@ typedef struct { #define luasql_pushinteger lua_pushnumber #endif +/* MSVC still doesn't support C99 properly until 2015 */ +#if defined(_MSC_VER) && _MSC_VER<1900 +#pragma warning(disable:4996) /* and complains if you try to work around it */ +#define snprintf _snprintf +#endif + LUASQL_API int luaopen_luasql_firebird (lua_State *L); +/* +** Sets a simple custom error status with given message +*/ +static void custom_fb_error(ISC_STATUS *pvector, const char *msg) +{ + *pvector++ = 1; /* isc_arg_gds */ + *pvector++ = 335544382L; /* isc_random */ + *pvector++ = 2; /* isc_arg_string */ + *pvector++ = (ISC_STATUS)msg; + *pvector++ = 0; /* isc_arg_end */ +} + /* ** Returns a standard database error message */ @@ -89,7 +145,7 @@ static int return_db_error(lua_State *L, const ISC_STATUS *pvector) /* ** Registers a given C object in the registry to avoid GC */ -static void lua_registerobj(lua_State *L, int index, void *obj) +void registerobj(lua_State *L, int index, void *obj) { lua_pushvalue(L, index); lua_pushlightuserdata(L, obj); @@ -101,7 +157,7 @@ static void lua_registerobj(lua_State *L, int index, void *obj) /* ** Unregisters a given C object from the registry */ -static void lua_unregisterobj(lua_State *L, void *obj) +void unregisterobj(lua_State *L, void *obj) { lua_pushlightuserdata(L, obj); lua_pushnil(L); @@ -109,22 +165,141 @@ static void lua_unregisterobj(lua_State *L, void *obj) } /* -** Free's up the memory alloc'd to the cursor data +** Allocates and initialises an XSQLDA struct */ -static void free_cur(cur_data* cur) +static XSQLDA *malloc_xsqlda(ISC_SHORT len) +{ + XSQLDA *res = (XSQLDA *)malloc(XSQLDA_LENGTH(len)); + + memset(res, 0, XSQLDA_LENGTH(len)); + res->version = SQLDA_VERSION1; + res->sqln = len; + + return res; +} + +static void *malloc_zero(size_t len) +{ + void *res = malloc(len); + memset(res, 0, len); + return res; +} + +/* +** Allocate memory for XSQLDA data +*/ +static void malloc_sqlda_vars(XSQLDA *sqlda) { int i; XSQLVAR *var; - /* free the field memory blocks */ - for (i=0, var = cur->out_sqlda->sqlvar; i < cur->out_sqlda->sqld; i++, var++) { - free(var->sqldata); - if(var->sqlind != NULL) + /* prep the result set ready to handle the data */ + for (i=0, var = sqlda->sqlvar; i < sqlda->sqld; i++, var++) { + switch(var->sqltype & ~1) { + case SQL_VARYING: + var->sqldata = (char *)malloc_zero(sizeof(char)*var->sqllen + 2); + break; + case SQL_TEXT: + var->sqldata = (char *)malloc_zero(sizeof(char)*var->sqllen); + break; + case SQL_SHORT: + var->sqldata = (char *)malloc_zero(sizeof(ISC_SHORT)); + break; + case SQL_LONG: + var->sqldata = (char *)malloc_zero(sizeof(ISC_LONG)); + break; + case SQL_INT64: + var->sqldata = (char *)malloc_zero(sizeof(ISC_INT64)); + break; + case SQL_FLOAT: + var->sqldata = (char *)malloc_zero(sizeof(float)); + break; + case SQL_DOUBLE: + var->sqldata = (char *)malloc_zero(sizeof(double)); + break; + case SQL_TYPE_TIME: + var->sqldata = (char *)malloc_zero(sizeof(ISC_TIME)); + break; + case SQL_TYPE_DATE: + var->sqldata = (char *)malloc_zero(sizeof(ISC_DATE)); + break; + case SQL_TIMESTAMP: + var->sqldata = (char *)malloc_zero(sizeof(ISC_TIMESTAMP)); + break; + case SQL_BLOB: + var->sqldata = (char *)malloc_zero(sizeof(ISC_QUAD)); + break; + /* TODO : add extra data type handles here */ + } + + if (var->sqltype & 1) { + /* allocate variable to hold NULL status */ + var->sqlind = (short *)malloc(sizeof(short)); + } else { + var->sqlind = NULL; + } + } +} + +/* +** Frees memory allocated to XSQLDA data +*/ +static void free_sqlda_vars(XSQLDA *sqlda) +{ + int i; + XSQLVAR *var; + + if(sqlda != NULL) { + for (i=0, var = sqlda->sqlvar; i < sqlda->sqln; i++, var++) { + free(var->sqldata); free(var->sqlind); + } } +} - /* free the data array */ - free(cur->out_sqlda); +/* +** Frees all XSQLDA data +*/ +static void free_xsqlda(XSQLDA *sqlda) +{ + free_sqlda_vars(sqlda); + free(sqlda); +} + +/* +** Free's up the memory alloc'd to the statement data +*/ +static void free_stmt(stmt_data *stmt) +{ + /* free the input DA */ + free_xsqlda(stmt->in_sqlda); +} + +static int stmt_shut(lua_State *L, stmt_data *stmt) +{ + isc_dsql_free_statement(stmt->env->status_vector, &stmt->handle, DSQL_drop); + if ( CHECK_DB_ERROR(stmt->env->status_vector) ) { + return return_db_error(L, stmt->env->status_vector); + } + + free_stmt(stmt); + + /* remove statement from lock count and check if connection can be unregistered */ + stmt->closed = 1; + if(--stmt->conn->lock == 0) { + unregisterobj(L, stmt->conn); + } + + return 0; +} + +/* +** Free's up the memory alloc'd to the cursor data +*/ +static void free_cur(cur_data *cur) +{ + /* free the output DA */ + free_xsqlda(cur->out_sqlda); } /* @@ -132,7 +307,7 @@ static void free_cur(cur_data* cur) */ static int cur_shut(lua_State *L, cur_data *cur) { - isc_dsql_free_statement(cur->env->status_vector, &cur->stmt, + isc_dsql_free_statement(cur->env->status_vector, &cur->stmt->handle, DSQL_close); if ( CHECK_DB_ERROR(cur->env->status_vector) ) { return return_db_error(L, cur->env->status_vector); @@ -143,11 +318,14 @@ static int cur_shut(lua_State *L, cur_data *cur) /* remove cursor from lock count and check if statment can be unregistered */ cur->closed = 1; - --cur->conn->lock; + if(--cur->stmt->lock == 0) { + unregisterobj(L, cur->stmt); - /* check if connection can be unregistered */ - if(cur->conn->lock == 0) - lua_unregisterobj(L, cur->conn); + /* hidden statement, needs closing now */ + if(cur->stmt->hidden) { + return stmt_shut(L, cur->stmt); + } + } return 0; } @@ -155,7 +333,8 @@ static int cur_shut(lua_State *L, cur_data *cur) /* ** Check for valid environment. */ -static env_data *getenvironment (lua_State *L, int i) { +static env_data *getenvironment (lua_State *L, int i) +{ env_data *env = (env_data *)luaL_checkudata (L, i, LUASQL_ENVIRONMENT_FIREBIRD); luaL_argcheck (L, env != NULL, i, "environment expected"); luaL_argcheck (L, !env->closed, i, "environment is closed"); @@ -165,27 +344,84 @@ static env_data *getenvironment (lua_State *L, int i) { /* ** Check for valid connection. */ -static conn_data *getconnection (lua_State *L, int i) { - conn_data *conn = (conn_data *)luaL_checkudata (L, i, LUASQL_CONNECTION_FIREBIRD); +static conn_data *getconnection (lua_State *L, int i) +{ + conn_data *conn = (conn_data *)luaL_checkudata (L, i, + LUASQL_CONNECTION_FIREBIRD); luaL_argcheck (L, conn != NULL, i, "connection expected"); luaL_argcheck (L, !conn->closed, i, "connection is closed"); return conn; } +/* +** Check for valid statement. +*/ +static stmt_data *getstatement (lua_State *L, int i) +{ + stmt_data *stmt = (stmt_data *)luaL_checkudata (L, i, + LUASQL_STATEMENT_FIREBIRD); + luaL_argcheck (L, stmt != NULL, i, "statement expected"); + luaL_argcheck (L, !stmt->closed, i, "statement is closed"); + return stmt; +} + /* ** Check for valid cursor. */ -static cur_data *getcursor (lua_State *L, int i) { +static cur_data *getcursor (lua_State *L, int i) +{ cur_data *cur = (cur_data *)luaL_checkudata (L, i, LUASQL_CURSOR_FIREBIRD); luaL_argcheck (L, cur != NULL, i, "cursor expected"); luaL_argcheck (L, !cur->closed, i, "cursor is closed"); return cur; } +/* +** Dumps the list of item's types into a new table +*/ +static int dump_xsqlda_types(lua_State *L, XSQLDA *sqlda) +{ + int i; + XSQLVAR *var; + + lua_newtable(L); + + for (i=1, var = sqlda->sqlvar; i <= sqlda->sqld; i++, var++) { + lua_pushnumber(L, i); + switch(var->sqltype & ~1) { + case SQL_VARYING: + case SQL_TEXT: + case SQL_TYPE_TIME: + case SQL_TYPE_DATE: + case SQL_TIMESTAMP: + case SQL_BLOB: + lua_pushstring(L, "string"); + break; + case SQL_SHORT: + case SQL_LONG: + case SQL_INT64: +#if LUA_VERSION_NUM>=503 + lua_pushstring(L, "integer"); + break; +#endif + case SQL_FLOAT: + case SQL_DOUBLE: + lua_pushstring(L, "number"); + break; + default: + lua_pushstring(L, "unknown"); + break; + } + lua_settable(L, -3); + } + + return 1; +} + /* ** Returns the statement type */ -static int get_statement_type(cur_data* cur) +static int get_statement_type(stmt_data *stmt) { int length, type; char type_item[] = { isc_info_sql_stmt_type }; @@ -193,22 +429,23 @@ static int get_statement_type(cur_data* cur) pres = res_buffer; - isc_dsql_sql_info( cur->env->status_vector, &cur->stmt, - sizeof(type_item), type_item, - sizeof(res_buffer), res_buffer ); - if (cur->env->status_vector[0] == 1 && cur->env->status_vector[1] > 0) + isc_dsql_sql_info( stmt->env->status_vector, &stmt->handle, + sizeof(type_item), type_item, + sizeof(res_buffer), res_buffer ); + if (stmt->env->status_vector[0] == 1 && stmt->env->status_vector[1] > 0) { return -1; + } /* check the type of the statement */ - if (*pres == isc_info_sql_stmt_type) - { + if (*pres == isc_info_sql_stmt_type) { pres++; length = isc_vax_integer(pres, 2); pres += 2; type = isc_vax_integer(pres, length); pres += length; - } else - return -2; /* should have had the isc_info_sql_stmt_type info */ + } else { + return -2; /* should have had the isc_info_sql_stmt_type info */ + } return type; } @@ -216,7 +453,7 @@ static int get_statement_type(cur_data* cur) /* ** Return the number of rows affected by last operation */ -static int count_rows_affected(cur_data* cur) +static int count_rows_affected(env_data *env, cur_data *cur) { int length, type, res=0; int del_count = 0, ins_count = 0, upd_count = 0, sel_count = 0; @@ -225,31 +462,32 @@ static int count_rows_affected(cur_data* cur) pres = res_buffer; - isc_dsql_sql_info( cur->env->status_vector, &cur->stmt, - sizeof(type_item), type_item, - sizeof(res_buffer), res_buffer ); - if (cur->env->status_vector[0] == 1 && cur->env->status_vector[1] > 0) + isc_dsql_sql_info( env->status_vector, &cur->stmt->handle, + sizeof(type_item), type_item, + sizeof(res_buffer), res_buffer ); + if (cur->env->status_vector[0] == 1 && cur->env->status_vector[1] > 0) { return -1; + } /* check the type of the statement */ - if (*pres == isc_info_sql_stmt_type) - { + if (*pres == isc_info_sql_stmt_type) { pres++; length = isc_vax_integer(pres, 2); pres += 2; type = isc_vax_integer(pres, length); pres += length; - } else - return -2; /* should have had the isc_info_sql_stmt_type info */ + } else { + return -2; /* should have had the isc_info_sql_stmt_type info */ + } - if(type > 4) - return 0; /* not a SELECT, INSERT, UPDATE or DELETE SQL statement */ + if(type > 4) { + return 0; /* not a SELECT, INSERT, UPDATE or DELETE SQL statement */ + } - if (*pres == isc_info_sql_records) - { + if (*pres == isc_info_sql_records) { pres++; length = isc_vax_integer(pres, 2); /* normally 29 bytes */ - pres += 2; + pres += 2; while(*pres != 1) { switch(*pres) { @@ -286,8 +524,9 @@ static int count_rows_affected(cur_data* cur) break; } } - } else + } else { return -3; + } switch(type) { case isc_info_sql_stmt_select: @@ -306,62 +545,219 @@ static int count_rows_affected(cur_data* cur) return res; } -static void *malloc_zero(size_t len) +static void fill_param(XSQLVAR *var, ISC_SHORT type, ISC_SCHAR *data, + ISC_SHORT len) { - void *res = malloc(len); - memset(res, 0, len); - return res; + var->sqltype = type; + *var->sqlind = 0; + var->sqllen = len; + + if((type & ~1) == SQL_TEXT) { + --var->sqllen; + } + + if(var->sqldata != NULL) { + free(var->sqldata); + } + if(var->sqldata = (ISC_SCHAR *)malloc(len)) { + memcpy(var->sqldata, data, len); + } +} + +static void write_blob(stmt_data *stmt, ISC_QUAD *blob_id, ISC_SCHAR *data, + size_t len) +{ + isc_blob_handle blob_handle = NULL; + memset(blob_id, 0, sizeof(ISC_QUAD)); + + isc_create_blob2( + stmt->env->status_vector, + &stmt->conn->db, &stmt->conn->transaction, + &blob_handle, blob_id, + 0, NULL); + if(CHECK_DB_ERROR(stmt->env->status_vector)) { + return; + } + + while(len > 10000) { + isc_put_segment(stmt->env->status_vector, &blob_handle, 10000, data); + if(CHECK_DB_ERROR(stmt->env->status_vector)) { + return; + } + + len -= 10000; + data += 10000; + } + isc_put_segment(stmt->env->status_vector, &blob_handle, len, data); + if(CHECK_DB_ERROR(stmt->env->status_vector)) { + return; + } + + isc_close_blob(stmt->env->status_vector, &blob_handle); +} + +static void set_param(lua_State *L, stmt_data *stmt, XSQLVAR *var) +{ + const char *str; + ISC_QUAD blob_id; + ISC_INT64 inum; + double fnum; + + if(var->sqlind == NULL) { + var->sqlind = (ISC_SHORT *)malloc(sizeof(ISC_SHORT)); + } + + if(lua_isnil(L, -1)) { + // nil -> NULL + *var->sqlind = -1; + } else { + switch(var->sqltype & ~1) { + case SQL_VARYING: + case SQL_BLOB: + case SQL_TEXT: + str = lua_tostring(L, -1); + if(strlen(str) > 0x7FF0) { + /* need to use BLOB for >32K chars */ + write_blob(stmt, &blob_id, (ISC_SCHAR *)str, strlen(str)); + fill_param(var, SQL_BLOB+1, (ISC_SCHAR *)&blob_id, sizeof(ISC_QUAD)); + } else { + /* plain text */ + fill_param(var, SQL_TEXT+1, (ISC_SCHAR *)str, strlen(str)+1); + } + break; + + case SQL_INT64: + case SQL_LONG: + case SQL_SHORT: + inum = (ISC_INT64)lua_tonumber(L, -1); + fill_param(var, SQL_INT64+1, (ISC_SCHAR *)&inum, + sizeof(ISC_INT64)); + break; + + case SQL_DOUBLE: + case SQL_D_FLOAT: + case SQL_FLOAT: + fnum = (double)lua_tonumber(L, -1); + fill_param(var, SQL_DOUBLE+1, (ISC_SCHAR *)&fnum, + sizeof(double)); + break; + + case SQL_TIMESTAMP: + case SQL_TYPE_TIME: + case SQL_TYPE_DATE: + switch(lua_type(L, -1)) { + case LUA_TNUMBER: { + /* os.time type value passed */ + time_t t_time = (time_t)lua_tointeger(L,-1); + struct tm *tm_time = localtime(&t_time); + ISC_TIMESTAMP isc_ts; + isc_encode_timestamp(tm_time, &isc_ts); + + fill_param(var, SQL_TIMESTAMP+1, (ISC_SCHAR *)&isc_ts, + sizeof(ISC_TIMESTAMP)); + } break; + + case LUA_TSTRING: { + /* date/time string passed */ + str = lua_tostring(L, -1); + fill_param(var, SQL_TEXT+1, (ISC_SCHAR *)str, strlen(str)+1); + } break; + + default: { + /* unknown pass empty string, which should error out */ + str = lua_tostring(L, -1); + fill_param(var, SQL_TEXT+1, (ISC_SCHAR *)"", 1); + } break; + } + break; + } + + if(!var->sqldata) { + custom_fb_error(stmt->conn->env->status_vector, "Problem allocating SQL param memory"); + return; + } + } +} + +static void parse_params(lua_State *L, stmt_data *stmt, int params) +{ + XSQLVAR *var; + int i, ltop; + + if(lua_type(L, params) == LUA_TTABLE) { + for(i=0; iin_sqlda->sqln; ++i) { + lua_pushnumber(L, i+1); + lua_gettable(L, params); + + var = &stmt->in_sqlda->sqlvar[i]; + set_param(L, stmt, var); + + lua_pop(L,1); /* param value */ + } + } else { + ltop = lua_gettop(L); + for(i=0; iin_sqlda->sqln; ++i, ++params) { + if(params > ltop) { + lua_pushnil(L); + } else { + lua_pushvalue(L, params); + } + + var = &stmt->in_sqlda->sqlvar[i]; + set_param(L, stmt, var); + + lua_pop(L,1); /* param value */ + } + } } /* -** Executes a SQL statement. +** Prepares a SQL statement. +** Lua input: +** SQL statement +** [parmeter table] ** Returns -** cursor object: if there are results or -** row count: number of rows affected by statement if no results +** statement object ready for setting parameters +** nil and error message otherwise. */ -static int conn_execute (lua_State *L) { +static int conn_prepare (lua_State *L) +{ conn_data *conn = getconnection(L,1); const char *statement = luaL_checkstring(L, 2); - int dialect = (int)luaL_optnumber(L, 3, 3); - XSQLVAR *var; - long dtype; - int i, n, count; + stmt_data *user_stmt; - cur_data cur; + stmt_data stmt; - cur.closed = 0; - cur.env = conn->env; - cur.conn = conn; - cur.stmt = 0; + memset(&stmt, 0, sizeof(stmt_data)); - cur.out_sqlda = (XSQLDA *)malloc(XSQLDA_LENGTH(CURSOR_PREALLOC)); - cur.out_sqlda->version = SQLDA_VERSION1; - cur.out_sqlda->sqln = CURSOR_PREALLOC; + stmt.closed = 0; + stmt.env = conn->env; + stmt.conn = conn; /* create a statement to handle the query */ - isc_dsql_allocate_statement(conn->env->status_vector, &conn->db, &cur.stmt); + isc_dsql_allocate_statement(conn->env->status_vector, &conn->db, &stmt.handle); if ( CHECK_DB_ERROR(conn->env->status_vector) ) { - free(cur.out_sqlda); return return_db_error(L, conn->env->status_vector); } /* process the SQL ready to run the query */ - isc_dsql_prepare(conn->env->status_vector, &conn->transaction, &cur.stmt, 0, (char*)statement, dialect, cur.out_sqlda); + isc_dsql_prepare(conn->env->status_vector, &conn->transaction, &stmt.handle, 0, + (char *)statement, conn->dialect, NULL); if ( CHECK_DB_ERROR(conn->env->status_vector) ) { - free(cur.out_sqlda); + free_stmt(&stmt); return return_db_error(L, conn->env->status_vector); } /* what type of SQL statement is it? */ - cur.stmt_type = get_statement_type(&cur); - if(cur.stmt_type < 0) { - free(cur.out_sqlda); - return return_db_error(L, conn->env->status_vector); + stmt.type = get_statement_type(&stmt); + if(stmt.type < 0) { + free_stmt(&stmt); + return return_db_error(L, stmt.env->status_vector); } /* an unsupported SQL statement (something like COMMIT) */ - switch(cur.stmt_type) { + switch(stmt.type) { case isc_info_sql_stmt_select: case isc_info_sql_stmt_insert: case isc_info_sql_stmt_update: @@ -370,122 +766,189 @@ static int conn_execute (lua_State *L) { case isc_info_sql_stmt_exec_procedure: break; default: - free(cur.out_sqlda); + free_stmt(&stmt); return luasql_faildirect(L, "unsupported SQL statement"); } - /* resize the result set if needed */ - if (cur.out_sqlda->sqld > cur.out_sqlda->sqln) - { - n = cur.out_sqlda->sqld; - free(cur.out_sqlda); - cur.out_sqlda = (XSQLDA *)malloc(XSQLDA_LENGTH(n)); - cur.out_sqlda->sqln = n; - cur.out_sqlda->version = SQLDA_VERSION1; - isc_dsql_describe(conn->env->status_vector, &cur.stmt, 1, cur.out_sqlda); + /* bind the input parameters */ + stmt.in_sqlda = malloc_xsqlda(1); + isc_dsql_describe_bind(conn->env->status_vector, &stmt.handle, 1, + stmt.in_sqlda); + if ( CHECK_DB_ERROR(conn->env->status_vector) ) { + free_stmt(&stmt); + return return_db_error(L, conn->env->status_vector); + } + /* resize the parameter set if needed */ + if (stmt.in_sqlda->sqld > stmt.in_sqlda->sqln) { + ISC_SHORT n = stmt.in_sqlda->sqld; + free_xsqlda(stmt.in_sqlda); + stmt.in_sqlda = malloc_xsqlda(n); + isc_dsql_describe_bind(conn->env->status_vector, &stmt.handle, 1, + stmt.in_sqlda); if ( CHECK_DB_ERROR(conn->env->status_vector) ) { - free(cur.out_sqlda); + free_stmt(&stmt); return return_db_error(L, conn->env->status_vector); } } + malloc_sqlda_vars(stmt.in_sqlda); - /* prep the result set ready to handle the data */ - for (i=0, var = cur.out_sqlda->sqlvar; i < cur.out_sqlda->sqld; i++, var++) { - dtype = (var->sqltype & ~1); /* drop flag bit for now */ - switch(dtype) { - case SQL_VARYING: - var->sqldata = (char *)malloc_zero(sizeof(char)*var->sqllen + 2); - break; - case SQL_TEXT: - var->sqldata = (char *)malloc_zero(sizeof(char)*var->sqllen); - break; - case SQL_SHORT: - var->sqldata = (char *)malloc_zero(sizeof(ISC_SHORT)); - break; - case SQL_LONG: - var->sqldata = (char *)malloc_zero(sizeof(ISC_LONG)); - break; - case SQL_INT64: - var->sqldata = (char *)malloc_zero(sizeof(ISC_INT64)); - break; - case SQL_FLOAT: - var->sqldata = (char *)malloc_zero(sizeof(float)); - break; - case SQL_DOUBLE: - var->sqldata = (char *)malloc_zero(sizeof(double)); - break; - case SQL_TYPE_TIME: - var->sqldata = (char *)malloc_zero(sizeof(ISC_TIME)); - break; - case SQL_TYPE_DATE: - var->sqldata = (char *)malloc_zero(sizeof(ISC_DATE)); - break; - case SQL_TIMESTAMP: - var->sqldata = (char *)malloc_zero(sizeof(ISC_TIMESTAMP)); - break; - case SQL_BLOB: - var->sqldata = (char *)malloc_zero(sizeof(ISC_QUAD)); - break; - /* TODO : add extra data type handles here */ + /* are there any input params */ + if(stmt.in_sqlda->sqld > 0) { + parse_params(L, &stmt, 3); + if ( CHECK_DB_ERROR(conn->env->status_vector) ) { + free_stmt(&stmt); + return return_db_error(L, conn->env->status_vector); } + } - if (var->sqltype & 1) { - /* allocate variable to hold NULL status */ - var->sqlind = (short *)malloc(sizeof(short)); - } else { - var->sqlind = NULL; - } + /* copy the statement into a new lua userdata object */ + user_stmt = (stmt_data *)lua_newuserdata(L, sizeof(stmt_data)); + luasql_setmeta (L, LUASQL_STATEMENT_FIREBIRD); + memcpy((void *)user_stmt, (void *)&stmt, sizeof(stmt_data)); + + /* add statement to the lock count */ + registerobj(L, 1, conn); + ++conn->lock; + + return 1; +} + +static int raw_execute (lua_State *L, int stmt_indx) +{ + int count; + cur_data cur; + stmt_data *stmt; + + if(stmt_indx < 0) { + stmt_indx = lua_gettop(L) + stmt_indx + 1; } - /* run the query */ - isc_dsql_execute(conn->env->status_vector, &conn->transaction, &cur.stmt, 1, NULL); - if ( CHECK_DB_ERROR(conn->env->status_vector) ) { - free_cur(&cur); - return return_db_error(L, conn->env->status_vector); + stmt = getstatement(L,stmt_indx); + + /* is there already a cursor open */ + if(stmt->lock > 0) { + return luasql_faildirect(L, "statement already has an open cursor"); } - /* if autocommit is set and it's a non SELECT query, commit change */ - if(conn->autocommit != 0 && cur.stmt_type > 1) { - isc_commit_retaining(conn->env->status_vector, &conn->transaction); - if ( CHECK_DB_ERROR(conn->env->status_vector) ) { + memset(&cur, 0, sizeof(cur_data)); + cur.closed = 0; + cur.stmt = stmt; + cur.env = stmt->env; + + /* size the result, set if needed */ + cur.out_sqlda = malloc_xsqlda(1); + isc_dsql_describe(cur.env->status_vector, &cur.stmt->handle, 1, cur.out_sqlda); + if (cur.out_sqlda->sqld > cur.out_sqlda->sqln) { + ISC_SHORT n = cur.out_sqlda->sqld; + free_xsqlda(cur.out_sqlda); + cur.out_sqlda = malloc_xsqlda(n); + isc_dsql_describe(cur.env->status_vector, &cur.stmt->handle, 1, + cur.out_sqlda); + if ( CHECK_DB_ERROR(cur.env->status_vector) ) { free_cur(&cur); - return return_db_error(L, conn->env->status_vector); + return return_db_error(L, cur.env->status_vector); } } + malloc_sqlda_vars(cur.out_sqlda); - /* what do we return? a cursor or a count */ - if(cur.out_sqlda->sqld > 0) { /* a cursor */ - char cur_name[32]; - cur_data* user_cur = (cur_data*)lua_newuserdata(L, sizeof(cur_data)); - luasql_setmeta (L, LUASQL_CURSOR_FIREBIRD); - - sprintf(cur_name, "dyn_cursor_%p", (void *)user_cur); + /* does the statment return data? allocate a cursor */ + if(cur.out_sqlda->sqld > 0) { + char cur_name[64]; + snprintf(cur_name, sizeof(cur_name), "dyn_cursor_%p", (void *)stmt); /* open the cursor ready for fetch cycles */ - isc_dsql_set_cursor_name(cur.env->status_vector, &cur.stmt, cur_name, 0); - if ( CHECK_DB_ERROR(conn->env->status_vector) ) { + isc_dsql_set_cursor_name(cur.env->status_vector, &cur.stmt->handle, + cur_name, 0); + if ( CHECK_DB_ERROR(cur.env->status_vector) ) { lua_pop(L, 1); /* the userdata */ free_cur(&cur); - return return_db_error(L, conn->env->status_vector); + return return_db_error(L, cur.env->status_vector); } + } + + /* run the query */ + isc_dsql_execute(stmt->env->status_vector, &stmt->conn->transaction, + &stmt->handle, 1, stmt->in_sqlda); + if ( CHECK_DB_ERROR(stmt->env->status_vector) ) { + free_cur(&cur); + return return_db_error(L, cur.env->status_vector); + } + + /* what do we return? a cursor or a count */ + if(cur.out_sqlda->sqld > 0) { /* a cursor */ + cur_data *user_cur = (cur_data *)lua_newuserdata(L, sizeof(cur_data)); + luasql_setmeta (L, LUASQL_CURSOR_FIREBIRD); /* copy the cursor into a new lua userdata object */ - memcpy((void*)user_cur, (void*)&cur, sizeof(cur_data)); + memcpy((void *)user_cur, (void *)&cur, sizeof(cur_data)); /* add cursor to the lock count */ - lua_registerobj(L, 1, conn); - ++conn->lock; + registerobj(L, stmt_indx, user_cur->stmt); + ++user_cur->stmt->lock; } else { /* a count */ - if( (count = count_rows_affected(&cur)) < 0 ) { - free(cur.out_sqlda); - return return_db_error(L, conn->env->status_vector); + /* if autocommit is set, commit change */ + if(cur.stmt->conn->autocommit) { + isc_commit_retaining(cur.env->status_vector, + &cur.stmt->conn->transaction); + if ( CHECK_DB_ERROR(cur.env->status_vector) ) { + free_cur(&cur); + return return_db_error(L, cur.env->status_vector); + } + } + + if( (count = count_rows_affected(cur.env, &cur)) < 0 ) { + free_cur(&cur); + return return_db_error(L, cur.env->status_vector); } - lua_pushnumber(L, count); + luasql_pushinteger(L, count); - /* totaly finnished with the cursor */ - isc_dsql_free_statement(conn->env->status_vector, &cur.stmt, DSQL_drop); - free(cur.out_sqlda); + /* totaly finished with the cursor */ + isc_dsql_free_statement(cur.env->status_vector, &cur.stmt->handle, + DSQL_close); + free_cur(&cur); + } + + return 1; +} + +/* +** Executes a SQL statement. +** Lua input: +** SQL statement +** [parameter table] +** Returns +** cursor object: if there are results or +** row count: number of rows affected by statement if no results +** nil and error message otherwise. +*/ +static int conn_execute (lua_State *L) +{ + int ret; + stmt_data *stmt; + + /* prepare the statement */ + if( (ret = conn_prepare(L)) != 1) { + return ret; + } + + /* execute and check result */ + if((ret = raw_execute(L, -1)) != 1) { + return ret; + } + + /* for neatness, remove stmt from stack */ + stmt = getstatement(L, -(ret+1)); + lua_remove(L, -(ret+1)); + + /* this will be an internal, hidden statment */ + stmt->hidden = 1; + + /* if statement doesn't return a cursor, close it */ + if(luaL_testudata (L, -1, LUASQL_CURSOR_FIREBIRD) == NULL) { + if((ret = stmt_shut(L, stmt)) != 0) { + return ret; + } } return 1; @@ -546,7 +1009,8 @@ static int conn_setautocommit(lua_State *L) { ** 1 if close was sucsessful, 0 if already closed ** nil and error message otherwise. */ -static int conn_close (lua_State *L) { +static int conn_close (lua_State *L) +{ conn_data *conn = (conn_data *)luaL_checkudata(L,1,LUASQL_CONNECTION_FIREBIRD); luaL_argcheck (L, conn != NULL, 1, "connection expected"); @@ -556,27 +1020,32 @@ static int conn_close (lua_State *L) { return 1; } - /* are all related cursors closed? */ - if(conn->lock > 0) - return luasql_faildirect(L, "there are still open cursors"); + /* are all related statements closed? */ + if(conn->lock > 0) { + return luasql_faildirect(L, "there are still open statements/cursors"); + } - if(conn->autocommit != 0) + if(conn->autocommit != 0) { isc_commit_transaction(conn->env->status_vector, &conn->transaction); - else + } else { isc_rollback_transaction(conn->env->status_vector, &conn->transaction); - if ( CHECK_DB_ERROR(conn->env->status_vector) ) + } + if ( CHECK_DB_ERROR(conn->env->status_vector) ) { return return_db_error(L, conn->env->status_vector); + } isc_detach_database(conn->env->status_vector, &conn->db); - if ( CHECK_DB_ERROR(conn->env->status_vector) ) + if ( CHECK_DB_ERROR(conn->env->status_vector) ) { return return_db_error(L, conn->env->status_vector); + } conn->closed = 1; --conn->env->lock; /* check environment can be GC'd */ - if(conn->env->lock == 0) - lua_unregisterobj(L, conn->env); + if(conn->env->lock == 0) { + unregisterobj(L, conn->env); + } lua_pushboolean(L, 1); return 1; @@ -585,14 +1054,18 @@ static int conn_close (lua_State *L) { /* ** GCs an connection object */ -static int conn_gc (lua_State *L) { +static int conn_gc (lua_State *L) +{ conn_data *conn = (conn_data *)luaL_checkudata(L,1,LUASQL_CONNECTION_FIREBIRD); if(conn->closed == 0) { - if(conn->autocommit != 0) - isc_commit_transaction(conn->env->status_vector, &conn->transaction); - else - isc_rollback_transaction(conn->env->status_vector, &conn->transaction); + if(conn->autocommit != 0) { + isc_commit_transaction(conn->env->status_vector, + &conn->transaction); + } else { + isc_rollback_transaction(conn->env->status_vector, + &conn->transaction); + } isc_detach_database(conn->env->status_vector, &conn->db); @@ -600,8 +1073,9 @@ static int conn_gc (lua_State *L) { --conn->env->lock; /* check environment can be GC'd */ - if(conn->env->lock == 0) - lua_unregisterobj(L, conn->env); + if(conn->env->lock == 0) { + unregisterobj(L, conn->env); + } } return 0; @@ -638,7 +1112,8 @@ static int conn_escape(lua_State *L) { /* ** Pushes the indexed value onto the lua stack */ -static void push_column(lua_State *L, int i, cur_data *cur) { +static void push_column(lua_State *L, int i, cur_data *cur) +{ int varcharlen; struct tm timevar; char timestr[256]; @@ -650,7 +1125,7 @@ static void push_column(lua_State *L, int i, cur_data *cur) { unsigned short actual_seg_len; if( (cur->out_sqlda->sqlvar[i].sqlind != NULL) && - (*(cur->out_sqlda->sqlvar[i].sqlind) != 0) ) { + (*(cur->out_sqlda->sqlvar[i].sqlind) != 0) ) { /* a null field? */ lua_pushnil(L); } else { @@ -660,57 +1135,61 @@ static void push_column(lua_State *L, int i, cur_data *cur) { lua_pushlstring(L, cur->out_sqlda->sqlvar[i].sqldata+2, varcharlen); break; case SQL_TEXT: - lua_pushlstring(L, cur->out_sqlda->sqlvar[i].sqldata, cur->out_sqlda->sqlvar[i].sqllen); + lua_pushlstring(L, cur->out_sqlda->sqlvar[i].sqldata, + cur->out_sqlda->sqlvar[i].sqllen); break; case SQL_SHORT: - luasql_pushinteger(L, *(ISC_SHORT*)(cur->out_sqlda->sqlvar[i].sqldata)); + luasql_pushinteger(L, *(ISC_SHORT *)(cur->out_sqlda->sqlvar[i].sqldata)); break; case SQL_LONG: - luasql_pushinteger(L, *(ISC_LONG*)(cur->out_sqlda->sqlvar[i].sqldata)); + luasql_pushinteger(L, *(ISC_LONG *)(cur->out_sqlda->sqlvar[i].sqldata)); break; case SQL_INT64: - luasql_pushinteger(L, *(ISC_INT64*)(cur->out_sqlda->sqlvar[i].sqldata)); + luasql_pushinteger(L, *(ISC_INT64 *)(cur->out_sqlda->sqlvar[i].sqldata)); break; case SQL_FLOAT: - lua_pushnumber(L, *(float*)(cur->out_sqlda->sqlvar[i].sqldata)); + lua_pushnumber(L, *(float *)(cur->out_sqlda->sqlvar[i].sqldata)); break; case SQL_DOUBLE: - lua_pushnumber(L, *(double*)(cur->out_sqlda->sqlvar[i].sqldata)); + lua_pushnumber(L, *(double *)(cur->out_sqlda->sqlvar[i].sqldata)); break; case SQL_TYPE_TIME: - isc_decode_sql_time((ISC_TIME*)(cur->out_sqlda->sqlvar[i].sqldata), &timevar); + isc_decode_sql_time((ISC_TIME *)(cur->out_sqlda->sqlvar[i].sqldata), + &timevar); strftime(timestr, 255, "%X", &timevar); lua_pushstring(L, timestr); break; case SQL_TYPE_DATE: - isc_decode_sql_date((ISC_DATE*)(cur->out_sqlda->sqlvar[i].sqldata), &timevar); - strftime(timestr, 255, "%x", &timevar); + isc_decode_sql_date((ISC_DATE *)(cur->out_sqlda->sqlvar[i].sqldata), + &timevar); + strftime(timestr, 255, "%Y-%m-%d", &timevar); lua_pushstring(L, timestr); break; case SQL_TIMESTAMP: - isc_decode_timestamp((ISC_TIMESTAMP*)(cur->out_sqlda->sqlvar[i].sqldata), &timevar); - strftime(timestr, 255, "%x %X", &timevar); + isc_decode_timestamp((ISC_TIMESTAMP *)(cur->out_sqlda->sqlvar[i].sqldata), + &timevar); + strftime(timestr, 255, "%Y-%m-%d %X", &timevar); lua_pushstring(L, timestr); break; case SQL_BLOB: /* get the BLOB ID and open it */ memcpy(&blob_id, cur->out_sqlda->sqlvar[i].sqldata, sizeof(ISC_QUAD)); - isc_open_blob2( cur->env->status_vector, - &cur->conn->db, &cur->conn->transaction, - &blob_handle, &blob_id, 0, NULL ); + isc_open_blob2(cur->env->status_vector, &cur->stmt->conn->db, + &cur->stmt->conn->transaction, + &blob_handle, &blob_id, 0, NULL ); /* fetch the blob data */ luaL_buffinit(L, &b); buffer = luaL_prepbuffer(&b); - blob_stat = isc_get_segment( cur->env->status_vector, - &blob_handle, &actual_seg_len, - LUAL_BUFFERSIZE, buffer ); + blob_stat = isc_get_segment(cur->env->status_vector, + &blob_handle, &actual_seg_len, + LUAL_BUFFERSIZE, buffer ); while(blob_stat == 0 || cur->env->status_vector[1] == isc_segment) { luaL_addsize(&b, actual_seg_len); buffer = luaL_prepbuffer(&b); - blob_stat = isc_get_segment( cur->env->status_vector, - &blob_handle, &actual_seg_len, - LUAL_BUFFERSIZE, buffer ); + blob_stat = isc_get_segment(cur->env->status_vector, + &blob_handle, &actual_seg_len, + LUAL_BUFFERSIZE, buffer ); } /* finnished, close the BLOB */ @@ -726,13 +1205,95 @@ static void push_column(lua_State *L, int i, cur_data *cur) { } } +/* +** Returns a map of parameter IDs to their types +*/ +static int stmt_get_params (lua_State *L) +{ + stmt_data *stmt = getstatement(L,1); + + return dump_xsqlda_types(L, stmt->in_sqlda); +} + +/* +** Executes the statement +** Lua input: +** [table of values] +** Returns +** cursor object: if there are results or +** row count: number of rows affected by statement if no results +** nil and error message otherwise. +*/ +static int stmt_execute (lua_State *L) +{ + stmt_data *stmt = getstatement(L,1); + + /* are there input params */ + if(stmt->in_sqlda->sqld > 0) { + parse_params(L, stmt, 2); + if ( CHECK_DB_ERROR(stmt->conn->env->status_vector) ) { + return return_db_error(L, stmt->conn->env->status_vector); + } + } + + return raw_execute(L, 1); +} + +/* +** Closes a statement object +** Lua Returns: +** true if close was sucsessful, false if already closed +** nil and error message otherwise. +*/ +static int stmt_close (lua_State *L) +{ + stmt_data *stmt = (stmt_data *)luaL_checkudata(L,1,LUASQL_STATEMENT_FIREBIRD); + luaL_argcheck (L, stmt != NULL, 1, "statement expected"); + + if(stmt->lock > 0) { + return luasql_faildirect(L, "there are still open cursors"); + } + + if(stmt->closed == 0) { + int res = stmt_shut(L, stmt); + if(res != 0) { + return res; + } + + /* return sucsess */ + lua_pushboolean(L, 1); + return 1; + } + + lua_pushboolean(L, 0); + return 1; +} + +/* +** Frees up memory alloc'd to a statement +*/ +static int stmt_gc (lua_State *L) +{ + stmt_data *stmt = (stmt_data *)luaL_checkudata(L,1,LUASQL_STATEMENT_FIREBIRD); + luaL_argcheck (L, stmt != NULL, 1, "statement expected"); + + if(stmt->closed == 0) { + if(stmt_shut(L, stmt) != 0) { + return 1; + } + } + + return 0; +} + /* ** Returns a row of data from the query ** Lua Returns: ** list of results or table of results depending on call ** nil and error message otherwise. */ -static int cur_fetch (lua_State *L) { +static int cur_fetch (lua_State *L) +{ ISC_STATUS fetch_stat; int i, res; cur_data *cur = (cur_data *)luaL_checkudata (L, 1, LUASQL_CURSOR_FIREBIRD); @@ -746,7 +1307,8 @@ static int cur_fetch (lua_State *L) { return 0; } - if ((fetch_stat = isc_dsql_fetch(cur->env->status_vector, &cur->stmt, 1, cur->out_sqlda)) == 0) { + if ((fetch_stat = isc_dsql_fetch(cur->env->status_vector, &cur->stmt->handle, + 1, cur->out_sqlda)) == 0) { if (lua_istable (L, 2)) { /* remove the option string */ lua_settop(L, 2); @@ -762,7 +1324,8 @@ static int cur_fetch (lua_State *L) { } if (alpha) { - lua_pushlstring(L, cur->out_sqlda->sqlvar[i].aliasname, cur->out_sqlda->sqlvar[i].aliasname_length); + lua_pushlstring(L, cur->out_sqlda->sqlvar[i].aliasname, + cur->out_sqlda->sqlvar[i].aliasname_length); lua_pushvalue(L, -2); lua_settable(L, 2); } @@ -773,8 +1336,9 @@ static int cur_fetch (lua_State *L) { /* returning given table */ res = 1; } else { - for (i = 0; i < cur->out_sqlda->sqld; i++) + for (i = 0; i < cur->out_sqlda->sqld; i++) { push_column(L, i, cur); + } /* returning a list of values */ res = cur->out_sqlda->sqld; @@ -782,7 +1346,7 @@ static int cur_fetch (lua_State *L) { /* close cursor for procedures/returnings as they (currently) only return one result, and error on subsequent fetches */ - if (cur->stmt_type == isc_info_sql_stmt_exec_procedure) { + if (cur->stmt->type == isc_info_sql_stmt_exec_procedure) { cur_shut(L, cur); } @@ -791,24 +1355,12 @@ static int cur_fetch (lua_State *L) { /* isc_dsql_fetch returns 100 if no more rows remain to be retrieved so this can be ignored */ - if (fetch_stat != 100L) - return return_db_error(L, cur->env->status_vector); - - /* last row has been fetched, close cursor */ - isc_dsql_free_statement(cur->env->status_vector, &cur->stmt, DSQL_drop); - if ( CHECK_DB_ERROR(cur->env->status_vector) ) + if (fetch_stat != 100L) { return return_db_error(L, cur->env->status_vector); + } - /* free the cursor data */ - free_cur(cur); - - cur->closed = 1; - - /* remove cursor from lock count */ - --cur->conn->lock; - - /* return sucsess */ - return 0; + /* shut cursor */ + return cur_shut(L, cur); } /* @@ -817,7 +1369,8 @@ static int cur_fetch (lua_State *L) { ** a table of column names ** nil and error message otherwise. */ -static int cur_colnames (lua_State *L) { +static int cur_colnames (lua_State *L) +{ int i; XSQLVAR *var; cur_data *cur = getcursor(L,1); @@ -839,60 +1392,28 @@ static int cur_colnames (lua_State *L) { ** a table of column types ** nil and error message otherwise. */ -static int cur_coltypes (lua_State *L) { - int i; - XSQLVAR *var; +static int cur_coltypes (lua_State *L) +{ cur_data *cur = getcursor(L,1); - lua_newtable(L); - - for (i=1, var = cur->out_sqlda->sqlvar; i <= cur->out_sqlda->sqld; i++, var++) { - lua_pushnumber(L, i); - switch(var->sqltype & ~1) { - case SQL_VARYING: - case SQL_TEXT: - case SQL_TYPE_TIME: - case SQL_TYPE_DATE: - case SQL_TIMESTAMP: - case SQL_BLOB: - lua_pushstring(L, "string"); - break; - case SQL_SHORT: - case SQL_LONG: - case SQL_INT64: -#if LUA_VERSION_NUM>=503 - lua_pushstring(L, "integer"); - break; -#endif - case SQL_FLOAT: - case SQL_DOUBLE: - lua_pushstring(L, "number"); - break; - default: - lua_pushstring(L, "unknown"); - break; - } - lua_settable(L, -3); - } - - return 1; + return dump_xsqlda_types(L, cur->out_sqlda); } /* ** Closes a cursor object ** Lua Returns: -** 1 if close was sucsessful, 0 if already closed +** true if close was sucsessful, false if already closed ** nil and error message otherwise. */ -static int cur_close (lua_State *L) { +static int cur_close (lua_State *L) +{ cur_data *cur = (cur_data *)luaL_checkudata(L,1,LUASQL_CURSOR_FIREBIRD); luaL_argcheck (L, cur != NULL, 1, "cursor expected"); - int shut_res; if(cur->closed == 0) { - shut_res = cur_shut(L, cur); - if(shut_res > 0) { - return shut_res; + int res = cur_shut(L, cur); + if(res != 0) { + return res; } /* return sucsess */ @@ -907,12 +1428,15 @@ static int cur_close (lua_State *L) { /* ** GCs a cursor object */ -static int cur_gc (lua_State *L) { +static int cur_gc (lua_State *L) +{ cur_data *cur = (cur_data *)luaL_checkudata(L,1,LUASQL_CURSOR_FIREBIRD); luaL_argcheck (L, cur != NULL, 1, "cursor expected"); if(cur->closed == 0) { - cur_shut(L, cur); + if(cur_shut(L, cur) != 0) { + return 1; + } } return 0; @@ -921,17 +1445,13 @@ static int cur_gc (lua_State *L) { /* ** Creates an Environment and returns it. */ -static int create_environment (lua_State *L) { - int i; +static int create_environment (lua_State *L) +{ env_data *env; env = (env_data *)lua_newuserdata (L, sizeof (env_data)); luasql_setmeta (L, LUASQL_ENVIRONMENT_FIREBIRD); - /* fill in structure */ - for(i=0; i<20; i++) - env->status_vector[i] = 0; - env->closed = 0; - env->lock = 0; + memset (env, 0, sizeof (env_data)); return 1; } @@ -963,6 +1483,7 @@ static int env_connect (lua_State *L) { conn.transaction = 0L; conn.lock = 0; conn.autocommit = 0; + conn.dialect = 3; /* Construct a database parameter buffer. */ dpb = conn.dpb_buffer; @@ -985,30 +1506,33 @@ static int env_connect (lua_State *L) { conn.dpb_length = (short)(dpb - conn.dpb_buffer); /* do the job */ - isc_attach_database(env->status_vector, (short)strlen(sourcename), (char*)sourcename, &conn.db, - conn.dpb_length, conn.dpb_buffer); + isc_attach_database(env->status_vector, (short)strlen(sourcename), + (char *)sourcename, &conn.db, + conn.dpb_length, conn.dpb_buffer); /* an error? */ - if ( CHECK_DB_ERROR(conn.env->status_vector) ) + if ( CHECK_DB_ERROR(conn.env->status_vector) ) { return return_db_error(L, conn.env->status_vector); + } /* open up the transaction handle */ - isc_start_transaction( env->status_vector, &conn.transaction, 1, - &conn.db, (unsigned short)sizeof(isc_tpb), - isc_tpb ); + isc_start_transaction(env->status_vector, &conn.transaction, 1, + &conn.db, (unsigned short)sizeof(isc_tpb), + isc_tpb ); - /* return NULL on error */ - if ( CHECK_DB_ERROR(conn.env->status_vector) ) + /* an error? */ + if ( CHECK_DB_ERROR(conn.env->status_vector) ) { return return_db_error(L, conn.env->status_vector); + } /* create the lua object and add the connection to the lock */ - res_conn = (conn_data*)lua_newuserdata(L, sizeof(conn_data)); + res_conn = (conn_data *)lua_newuserdata(L, sizeof(conn_data)); luasql_setmeta (L, LUASQL_CONNECTION_FIREBIRD); memcpy(res_conn, &conn, sizeof(conn_data)); res_conn->closed = 0; /* connect now officially open */ /* register the connection */ - lua_registerobj(L, 1, env); + registerobj(L, 1, env); ++env->lock; return 1; @@ -1020,10 +1544,11 @@ static int env_connect (lua_State *L) { ** 1 if close was sucsessful, 0 if already closed ** nil and error message otherwise. */ -static int env_close (lua_State *L) { +static int env_close (lua_State *L) +{ env_data *env = (env_data *)luaL_checkudata (L, 1, LUASQL_ENVIRONMENT_FIREBIRD); luaL_argcheck (L, env != NULL, 1, "environment expected"); - + /* already closed? */ if(env->closed == 1) { lua_pushboolean(L, 0); @@ -1031,11 +1556,12 @@ static int env_close (lua_State *L) { } /* check the lock */ - if(env->lock > 0) + if(env->lock > 0) { return luasql_faildirect(L, "there are still open connections"); + } /* unregister */ - lua_unregisterobj(L, env); + unregisterobj(L, env); /* mark as closed */ env->closed = 1; @@ -1055,7 +1581,8 @@ static int env_gc (lua_State *L) { /* ** Create metatables for each class of object. */ -static void create_metatables (lua_State *L) { +static void create_metatables (lua_State *L) +{ struct luaL_Reg environment_methods[] = { {"__gc", env_gc}, {"close", env_close}, @@ -1065,6 +1592,7 @@ static void create_metatables (lua_State *L) { struct luaL_Reg connection_methods[] = { {"__gc", conn_gc}, {"close", conn_close}, + {"prepare", conn_prepare}, {"execute", conn_execute}, {"commit", conn_commit}, {"rollback", conn_rollback}, @@ -1072,6 +1600,13 @@ static void create_metatables (lua_State *L) { {"escape", conn_escape}, {NULL, NULL}, }; + struct luaL_Reg statement_methods[] = { + {"__gc", stmt_gc}, + {"close", stmt_close}, + {"getparamtypes", stmt_get_params}, + {"execute", stmt_execute}, + {NULL, NULL}, + }; struct luaL_Reg cursor_methods[] = { {"__gc", cur_gc}, {"close", cur_close}, @@ -1082,8 +1617,9 @@ static void create_metatables (lua_State *L) { }; luasql_createmeta (L, LUASQL_ENVIRONMENT_FIREBIRD, environment_methods); luasql_createmeta (L, LUASQL_CONNECTION_FIREBIRD, connection_methods); + luasql_createmeta (L, LUASQL_STATEMENT_FIREBIRD, statement_methods); luasql_createmeta (L, LUASQL_CURSOR_FIREBIRD, cursor_methods); - lua_pop (L, 3); + lua_pop (L, 4); } /* diff --git a/tests/firebird.lua b/tests/firebird.lua index 3603cd1..33884dd 100644 --- a/tests/firebird.lua +++ b/tests/firebird.lua @@ -48,3 +48,47 @@ END io.write (" returning") end) +-- Check parameterized queires +table.insert (EXTENSIONS, function() + local cur = assert (CONN:execute ([[ +EXECUTE BLOCK (X INTEGER = ?, Y INTEGER = ?) +RETURNS (A INTEGER, B INTEGER) +AS +BEGIN + A = X; + B = Y; + SUSPEND; +END +]], 123, 321)) + + local x, y = cur:fetch () + assert2 (123, x) + assert2 (321, y) + + io.write (" parameter_queries") +end) + +-- Check prepared statements, with params +table.insert (EXTENSIONS, function() + local stmt = assert (CONN:prepare[[ +EXECUTE BLOCK (X INTEGER = ?, Y INTEGER = ?) +RETURNS (A INTEGER, B INTEGER) +AS +BEGIN + A = X; + B = Y; + SUSPEND; +END +]]) + + local cur = assert (stmt:execute ( 123, 321 )) + + local x, y = cur:fetch () + assert2 (123, x) + assert2 (321, y) + + stmt:close() + + io.write (" prepared_queries") +end) +