diff --git a/Makefile b/Makefile index b7b9135..a5b148b 100644 --- a/Makefile +++ b/Makefile @@ -1,37 +1,39 @@ -V= 2.3.0 +V= 3.0.0 CONFIG= ./config include $(CONFIG) -OBJS= src/luasql.o src/ls_$T.o +OBJS= src/luasql.o +SRCS= src/luasql.h src/luasql.c +# list of all driver names +DRIVER_LIST= $(subst src/ls_,,$(basename $(wildcard src/ls_*.c))) -SRCS= src/luasql.h src/luasql.c \ - src/ls_firebird.c \ - src/ls_postgres.c \ - src/ls_odbc.c \ - src/ls_oci8.c \ - src/ls_mysql.c \ - src/ls_sqlite.c \ - src/ls_sqlite3.c +# used for help formatting +EMPTY= +SPACE= $(EMPTY) $(EMPTY) -AR= ar rcu -RANLIB= ranlib +all : + @echo "usage: make { $(subst $(SPACE),$(SPACE)|$(SPACE),$(DRIVER_LIST)) }" +# explicity matches against the list of avilable driver names +$(DRIVER_LIST) : % : src/%.so -lib: src/$(LIBNAME) +# builds the specified driver +src/%.so : src/ls_%.c $(OBJS) + $(CC) $(CFLAGS) src/ls_$*.c -o $@ $(LIB_OPTION) $(OBJS) $(DRIVER_INCS_$*) $(DRIVER_LIBS_$*) -src/$(LIBNAME): $(OBJS) - export MACOSX_DEPLOYMENT_TARGET="10.3"; $(CC) $(CFLAGS) -o $@ $(LIB_OPTION) $(OBJS) $(DRIVER_LIBS) +# builds the general LuaSQL functions +$(OBJS) : $(SRCS) + $(CC) $(CFLAGS) -c src/luasql.c -o src/luasql.o install: mkdir -p $(LUA_LIBDIR)/luasql - cp src/$(LIBNAME) $(LUA_LIBDIR)/luasql + cp src/*.so $(LUA_LIBDIR)/luasql jdbc_driver: cd src/jdbc; make $@ clean: - rm -f src/$(LIBNAME) src/*.o + rm -f src/*.so src/*.o -# $Id: Makefile,v 1.56 2008/05/30 17:21:18 tomas Exp $ diff --git a/README b/README deleted file mode 100644 index f6c614f..0000000 --- a/README +++ /dev/null @@ -1,20 +0,0 @@ -LuaSQL 2.1 -http://www.keplerproject.org/luasql/ - -LuaSQL is a simple interface from Lua to a DBMS. It enables a Lua program to: - - * Connect to ODBC, ADO, Oracle, MySQL, SQLite and PostgreSQL databases; - * Execute arbitrary SQL statements; - * Retrieve results in a row-by-row cursor fashion. - -LuaSQL is free software and uses the same license as Lua 5.1. - - -Source code for LuaSQL can be downloaded from the LuaForge page. - -If you are using LuaBinaries a Windows binary version of LuaSQL can be found at the same LuaForge page. - -LuaSQL 2.1.1 [29/Oct/2007] -* Fixed a bug in the SQLite3 error handling (patch by David Burgess) -* Fixed bug [#1834] for SQLite 3 (found by Savin Zlobec, patch by Marc Nijdam) -* Fixed bug [#1770] for SQLite 3 (found by Enrico Tassi, patch by Marc Nijdam) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d1b725 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# LuaSQL 3.0 (proposed) +http://www.keplerproject.org/luasql/ +https://github.com/keplerproject/luasql/ + +LuaSQL is a simple interface from Lua to a DBMS. It enables a Lua program to: + + * Connect to ODBC, ADO, Oracle, MySQL, SQLite and PostgreSQL databases; + * Execute arbitrary SQL statements; + * Retrieve results in a row-by-row cursor fashion. + +LuaSQL is free software and uses the same license as Lua 5.1. + +Source code for LuaSQL can be downloaded from the Kepler Project Github page. + +- - - + +## LuaSQL 3.0.0 [09/Apr/2015] +This is a proposed branch to add much needed features + * Prepared statements with parameters + + Really a major requirement for any DB system that takes data from the web + * More flexible connection method + + Current method of just passing DB name/path with user name+password is not enough to support the complexities of connecting to a DB + * Explicit handling of BLOB fields (? maybe) + + It might be useful to handle true binary data from BLOB fields rather than blindly convert them to Lua strings + diff --git a/config b/config index 7dd267b..1bbf9df 100644 --- a/config +++ b/config @@ -1,68 +1,54 @@ -# $Id: config,v 1.10 2008/05/30 17:21:18 tomas Exp $ - -# Driver (leave uncommented ONLY the line with the name of the driver) -T= mysql -#T= oci8 -#T= odbc -#T= postgres -#T= sqlite -#T=sqlite3 -#T=firebird - # Installation directories # Default prefix -PREFIX = /usr/local - -# System's libraries directory (where binary libraries are installed) -LUA_LIBDIR= $(PREFIX)/lib/lua/5.1 - -# System's lua directory (where Lua libraries are installed) -LUA_DIR= $(PREFIX)/share/lua/5.1 - -# Lua includes directory -LUA_INC= $(PREFIX)/include +PREFIX ?= /usr -# Lua version number (first and second digits of target version) -LUA_VERSION_NUM= 501 +# Lua version and dirs +LUA_SYS_VER ?= 5.2 +LUA_LIBDIR ?= $(PREFIX)/lib/lua/$(LUA_SYS_VER) +LUA_DIR ?= $(PREFIX)/share/lua/$(LUA_SYS_VER) +LUA_INC ?= $(PREFIX)/include/lua$(LUA_SYS_VER) # OS dependent -LIB_OPTION= -shared #for Linux -#LIB_OPTION= -bundle -undefined dynamic_lookup #for MacOS X - -LIBNAME= $T.so - -# Compilation parameters -# Driver specific -######## MySQL -#DRIVER_LIBS= -L/usr/local/mysql/lib -lmysqlclient -lz -#DRIVER_INCS= -I/usr/local/mysql/include -DRIVER_LIBS= -L/usr/lib -lmysqlclient -lz -DRIVER_INCS= -I/usr/include/mysql -######## Oracle OCI8 -#DRIVER_LIBS= -L/home/oracle/OraHome1/lib -lz -lclntsh -#DRIVER_INCS= -I/home/oracle/OraHome1/rdbms/demo -I/home/oracle/OraHome1/rdbms/public -######## PostgreSQL -#DRIVER_LIBS= -L/usr/local/pgsql/lib -lpq -#DRIVER_INCS= -I/usr/local/pgsql/include/ -#DRIVER_LIBS= -L/usr/lib -lpq -#DRIVER_INCS= -I/usr/include/postgresql -######## SQLite -#DRIVER_LIBS= -lsqlite -#DRIVER_INCS= -######## SQLite3 -#DRIVER_LIBS= -L/opt/local/lib -lsqlite3 -#DRIVER_INCS= -I/opt/local/include -######## ODBC -#DRIVER_LIBS= -L/usr/local/lib -lodbc -#DRIVER_INCS= -DUNIXODBC -I/usr/local/include -######## Firebird -#DRIVER_LIBS= -L/usr/local/firebird -lfbclient -#DRIVER_INCS= - -WARN= -Wall -Wmissing-prototypes -Wmissing-declarations -ansi -pedantic -INCS= -I$(LUA_INC) -CFLAGS= -O2 $(WARN) -fPIC $(DRIVER_INCS) $(INCS) -DLUASQL_VERSION_NUMBER='"$V"' $(DEFS) +UNAME_S := $(shell uname -s) +ifeq ($(UNAME_S),Darwin) # MacOS + LIB_OPTION ?= -bundle -undefined dynamic_lookup -mmacosx-version-min=10.3 +else # Linux/BSD + LIB_OPTION ?= -shared +endif + +# driver specific params +# - MySQL +#DRIVER_LIBS_mysql ?= -L/usr/local/mysql/lib -lmysqlclient -lz +#DRIVER_INCS_mysql ?= -I/usr/local/mysql/include +DRIVER_LIBS_mysql ?= -L/usr/lib -lmysqlclient -lz +DRIVER_INCS_mysql ?= -I/usr/include/mysql +# - Oracle OCI8 +DRIVER_LIBS_oci8 ?= -L/home/oracle/OraHome1/lib -lz -lclntsh +DRIVER_INCS_oci8 ?= -I/home/oracle/OraHome1/rdbms/demo \ + -I/home/oracle/OraHome1/rdbms/public +# - PostgreSQL +#DRIVER_LIBS_postgres ?= -L/usr/local/pgsql/lib -lpq +#DRIVER_INCS_postgres ?= -I/usr/local/pgsql/include/ +DRIVER_LIBS_postgres ?= -L/usr/lib -lpq +DRIVER_INCS_postgres ?= -I/usr/include/postgresql +# - SQLite +DRIVER_LIBS_sqlite ?= -lsqlite +DRIVER_INCS_sqlite ?= +# - SQLite3 +DRIVER_LIBS_sqlite3 ?= -L/opt/local/lib -lsqlite3 +DRIVER_INCS_sqlite3 ?= -I/opt/local/include +# - ODBC +DRIVER_LIBS_odbc ?= -L/usr/local/lib -lodbc +DRIVER_INCS_odbc ?= -DUNIXODBC -I/usr/local/include +# - Firebird +DRIVER_LIBS_firebird ?= -L/usr/local/firebird -lfbclient +DRIVER_INCS_firebird ?= + +# general compilation parameters +WARN = -Wall -Wmissing-prototypes -Wmissing-declarations -pedantic +INCS = -I$(LUA_INC) +DEFS = +CFLAGS = -O2 -std=gnu99 $(WARN) -fPIC $(DRIVER_INCS) $(INCS) \ + -DLUASQL_VERSION_NUMBER='"$V"' $(DEFS) CC= gcc - -# $Id: config,v 1.10 2008/05/30 17:21:18 tomas Exp $ diff --git a/src/ls_firebird.c b/src/ls_firebird.c index 9c94cee..f654463 100644 --- a/src/ls_firebird.c +++ b/src/ls_firebird.c @@ -1,1068 +1,1560 @@ -/* -** LuaSQL, Firebird driver -** Authors: Scott Morgan -** ls_firebird.c -*/ - -#include /* The Firebird API*/ -#include /* For managing time values */ -#include -#include - -/* Lua API */ -#include -#include - -#include "luasql.h" - -#define LUASQL_ENVIRONMENT_FIREBIRD "Firebird environment" -#define LUASQL_CONNECTION_FIREBIRD "Firebird connection" -#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 */ -} 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 */ -} 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 */ - XSQLDA *out_sqlda; /* the cursor data array */ -} cur_data; - -/* How many fields to pre-alloc to the cursor */ -#define CURSOR_PREALLOC 10 - -/* Macro to ease code reading */ -#define CHECK_DB_ERROR( X ) ( (X)[0] == 1 && (X)[1] ) - -/* Use the new interpret API if available */ -#undef FB_INTERPRET -#if FB_API_VER >= 20 - #define FB_INTERPRET(BUF, LEN, VECTOR) fb_interpret(BUF, LEN, VECTOR) -#else - #define FB_INTERPRET(BUF, LEN, VECTOR) isc_interpret(BUF, VECTOR) -#endif - -#if LUA_VERSION_NUM>=503 -#define luasql_pushinteger lua_pushinteger -#else -#define luasql_pushinteger lua_pushnumber -#endif - -LUASQL_API int luaopen_luasql_firebird (lua_State *L); - -/* -** Returns a standard database error message -*/ -static int return_db_error(lua_State *L, const ISC_STATUS *pvector) -{ - char errmsg[512]; - - lua_pushnil(L); - FB_INTERPRET(errmsg, 512, &pvector); - lua_pushstring(L, errmsg); - while(FB_INTERPRET(errmsg, 512, &pvector)) { - lua_pushstring(L, "\n * "); - lua_pushstring(L, errmsg); - lua_concat(L, 3); - } - - return 2; -} - -/* -** Registers a given C object in the registry to avoid GC -*/ -static void lua_registerobj(lua_State *L, int index, void *obj) -{ - lua_pushvalue(L, index); - lua_pushlightuserdata(L, obj); - lua_pushvalue(L, -2); - lua_settable(L, LUA_REGISTRYINDEX); - lua_pop(L, 1); -} - -/* -** Unregisters a given C object from the registry -*/ -static void lua_unregisterobj(lua_State *L, void *obj) -{ - lua_pushlightuserdata(L, obj); - lua_pushnil(L); - lua_settable(L, LUA_REGISTRYINDEX); -} - -/* -** Free's up the memory alloc'd to the cursor data -*/ -static void free_cur(cur_data* cur) -{ - 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) - free(var->sqlind); - } - - /* free the data array */ - free(cur->out_sqlda); -} - -/* -** Check for valid environment. -*/ -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"); - return env; -} - -/* -** 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); - luaL_argcheck (L, conn != NULL, i, "connection expected"); - luaL_argcheck (L, !conn->closed, i, "connection is closed"); - return conn; -} - -/* -** Check for valid cursor. -*/ -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; -} - -/* -** Returns the statement type -*/ -static int get_statement_type(cur_data* cur) -{ - int length, type; - char type_item[] = { isc_info_sql_stmt_type }; - char res_buffer[88], *pres; - - 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) - return -1; - - /* check the type of the statement */ - 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 */ - - return type; -} - -/* -** Return the number of rows affected by last operation -*/ -static int count_rows_affected(cur_data* cur) -{ - int length, type, res=0; - int del_count = 0, ins_count = 0, upd_count = 0, sel_count = 0; - char type_item[] = { isc_info_sql_stmt_type, isc_info_sql_records }; - char res_buffer[88], *pres; - - 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) - return -1; - - /* check the type of the statement */ - 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 */ - - if(type > 4) - return 0; /* not a SELECT, INSERT, UPDATE or DELETE SQL statement */ - - if (*pres == isc_info_sql_records) - { - pres++; - length = isc_vax_integer(pres, 2); /* normally 29 bytes */ - pres += 2; - - while(*pres != 1) { - switch(*pres) { - case isc_info_req_select_count: - pres++; - length = isc_vax_integer(pres, 2); - pres += 2; - sel_count = isc_vax_integer(pres, length); - pres += length; - break; - case isc_info_req_insert_count: - pres++; - length = isc_vax_integer(pres, 2); - pres += 2; - ins_count = isc_vax_integer(pres, length); - pres += length; - break; - case isc_info_req_update_count: - pres++; - length = isc_vax_integer(pres, 2); - pres += 2; - upd_count = isc_vax_integer(pres, length); - pres += length; - break; - case isc_info_req_delete_count: - pres++; - length = isc_vax_integer(pres, 2); - pres += 2; - del_count = isc_vax_integer(pres, length); - pres += length; - break; - default: - pres++; - break; - } - } - } else - return -3; - - switch(type) { - case isc_info_sql_stmt_select: - res = sel_count; - break; - case isc_info_sql_stmt_delete: - res = del_count; - break; - case isc_info_sql_stmt_update: - res = upd_count; - break; - case isc_info_sql_stmt_insert: - res = ins_count; - break; - } - return res; -} - -/* -** Executes a SQL statement. -** Returns -** cursor object: if there are results or -** row count: number of rows affected by statement if no results -*/ -static int conn_execute (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_type; - - cur_data cur; - - cur.closed = 0; - cur.env = conn->env; - cur.conn = conn; - cur.stmt = 0; - - cur.out_sqlda = (XSQLDA *)malloc(XSQLDA_LENGTH(CURSOR_PREALLOC)); - cur.out_sqlda->version = SQLDA_VERSION1; - cur.out_sqlda->sqln = CURSOR_PREALLOC; - - /* create a statement to handle the query */ - isc_dsql_allocate_statement(conn->env->status_vector, &conn->db, &cur.stmt); - 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); - if ( CHECK_DB_ERROR(conn->env->status_vector) ) { - free(cur.out_sqlda); - return return_db_error(L, conn->env->status_vector); - } - - /* what type of SQL statement is it? */ - stmt_type = get_statement_type(&cur); - if(stmt_type < 0) { - free(cur.out_sqlda); - return return_db_error(L, conn->env->status_vector); - } - - /* an unsupported SQL statement (something like COMMIT) */ - if(stmt_type > 5) { - free(cur.out_sqlda); - 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); - if ( CHECK_DB_ERROR(conn->env->status_vector) ) { - free(cur.out_sqlda); - return return_db_error(L, conn->env->status_vector); - } - } - - /* 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(sizeof(char)*var->sqllen + 2); - break; - case SQL_TEXT: - var->sqldata = (char *)malloc(sizeof(char)*var->sqllen); - break; - case SQL_SHORT: - var->sqldata = (char *)malloc(sizeof(short)); - break; - case SQL_LONG: - var->sqldata = (char *)malloc(sizeof(long)); - break; - case SQL_INT64: - var->sqldata = (char *)malloc(sizeof(ISC_INT64)); - break; - case SQL_FLOAT: - var->sqldata = (char *)malloc(sizeof(float)); - break; - case SQL_DOUBLE: - var->sqldata = (char *)malloc(sizeof(double)); - break; - case SQL_TYPE_TIME: - var->sqldata = (char *)malloc(sizeof(ISC_TIME)); - break; - case SQL_TYPE_DATE: - var->sqldata = (char *)malloc(sizeof(ISC_DATE)); - break; - case SQL_TIMESTAMP: - var->sqldata = (char *)malloc(sizeof(ISC_TIMESTAMP)); - break; - case SQL_BLOB: - var->sqldata = (char *)malloc(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; - } - } - - /* 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); - } - - /* if autocommit is set and it's a non SELECT query, commit change */ - if(conn->autocommit != 0 && stmt_type > 1) { - isc_commit_retaining(conn->env->status_vector, &conn->transaction); - if ( CHECK_DB_ERROR(conn->env->status_vector) ) { - free_cur(&cur); - return return_db_error(L, conn->env->status_vector); - } - } - - /* 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); - - /* 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) ) { - lua_pop(L, 1); /* the userdata */ - free_cur(&cur); - return return_db_error(L, conn->env->status_vector); - } - - /* copy the cursor into a new lua userdata object */ - memcpy((void*)user_cur, (void*)&cur, sizeof(cur_data)); - - /* add cursor to the lock count */ - lua_registerobj(L, 1, conn); - ++conn->lock; - } else { /* a count */ - if( (count = count_rows_affected(&cur)) < 0 ) { - free(cur.out_sqlda); - return return_db_error(L, conn->env->status_vector); - } - - lua_pushnumber(L, count); - - /* totaly finnished with the cursor */ - isc_dsql_free_statement(conn->env->status_vector, &cur.stmt, DSQL_drop); - free(cur.out_sqlda); - } - - return 1; -} - -/* -** Commits the current transaction -*/ -static int conn_commit(lua_State *L) { - conn_data *conn = getconnection(L,1); - - isc_commit_retaining(conn->env->status_vector, &conn->transaction); - if ( CHECK_DB_ERROR(conn->env->status_vector) ) - return return_db_error(L, conn->env->status_vector); - - lua_pushboolean(L, 1); - return 1; -} - -/* -** Rolls back the current transaction -** Lua Returns: -** 1 if rollback is sucsessful -** nil and error message otherwise. -*/ -static int conn_rollback(lua_State *L) { - conn_data *conn = getconnection(L,1); - - isc_rollback_retaining(conn->env->status_vector, &conn->transaction); - if ( CHECK_DB_ERROR(conn->env->status_vector) ) - return return_db_error(L, conn->env->status_vector); - - lua_pushboolean(L, 1); - return 1; -} - -/* -** Sets the autocommit state of the connection -** Lua Returns: -** autocommit state (0:off, 1:on) -** nil and error message on error. -*/ -static int conn_setautocommit(lua_State *L) { - conn_data *conn = getconnection(L,1); - - if(lua_toboolean(L, 2)) - conn->autocommit = 1; - else - conn->autocommit = 0; - - lua_pushboolean(L, 1); - return 1; -} - -/* -** Closes a connection. -** Lua Returns: -** 1 if close was sucsessful, 0 if already closed -** nil and error message otherwise. -*/ -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"); - - /* already closed */ - if(conn->closed != 0) { - lua_pushboolean(L, 0); - return 1; - } - - /* are all related cursors closed? */ - if(conn->lock > 0) - return luasql_faildirect(L, "there are still open cursors"); - - if(conn->autocommit != 0) - isc_commit_transaction(conn->env->status_vector, &conn->transaction); - else - isc_rollback_transaction(conn->env->status_vector, &conn->transaction); - 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) ) - 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); - - lua_pushboolean(L, 1); - return 1; -} - -/* -** GCs an connection object -*/ -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); - - isc_detach_database(conn->env->status_vector, &conn->db); - - conn->closed = 1; - --conn->env->lock; - - /* check environment can be GC'd */ - if(conn->env->lock == 0) - lua_unregisterobj(L, conn->env); - } - - return 0; -} - -/* -** Escapes a given string so that it can't break out of it's delimiting quotes -*/ -static int conn_escape(lua_State *L) { - size_t len; - const char *from = luaL_checklstring (L, 2, &len); - char *res = malloc(len*sizeof(char)*2+1); - char *to = res; - - if(res) { - while(*from != '\0') { - *(to++) = *from; - if(*from == '\'') - *(to++) = *from; - - from++; - } - *to = '\0'; - - lua_pushstring(L, res); - free(res); - return 1; - } - - luaL_error(L, "could not allocate escaped string"); - return 0; -} - -/* -** Pushes the indexed value onto the lua stack -*/ -static void push_column(lua_State *L, int i, cur_data *cur) { - int varcharlen; - struct tm timevar; - char timestr[256]; - ISC_STATUS blob_stat; - isc_blob_handle blob_handle = 0; - ISC_QUAD blob_id; - luaL_Buffer b; - char *buffer; - unsigned short actual_seg_len; - - if( (cur->out_sqlda->sqlvar[i].sqlind != NULL) && - (*(cur->out_sqlda->sqlvar[i].sqlind) != 0) ) { - /* a null field? */ - lua_pushnil(L); - } else { - switch(cur->out_sqlda->sqlvar[i].sqltype & ~1) { - case SQL_VARYING: - varcharlen = (int)isc_vax_integer(cur->out_sqlda->sqlvar[i].sqldata, 2); - 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); - break; - case SQL_SHORT: - luasql_pushinteger(L, *(short*)(cur->out_sqlda->sqlvar[i].sqldata)); - break; - case SQL_LONG: - luasql_pushinteger(L, *(long*)(cur->out_sqlda->sqlvar[i].sqldata)); - break; - case SQL_INT64: - 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)); - break; - case SQL_DOUBLE: - 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); - 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); - 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); - 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 ); - /* 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 ); - 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 ); - } - - /* finnished, close the BLOB */ - isc_close_blob(cur->env->status_vector, &blob_handle); - blob_handle = 0; - - luaL_pushresult(&b); - break; - default: - lua_pushstring(L, ""); - break; - } - } -} - -/* -** 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) { - ISC_STATUS fetch_stat; - int i; - cur_data *cur = getcursor(L,1); - const char *opts = luaL_optstring (L, 3, "n"); - int num = strchr(opts, 'n') != NULL; - int alpha = strchr(opts, 'a') != NULL; - - if ((fetch_stat = isc_dsql_fetch(cur->env->status_vector, &cur->stmt, 1, cur->out_sqlda)) == 0) { - if (lua_istable (L, 2)) { - /* remove the option string */ - lua_settop(L, 2); - - /* loop through the columns */ - for (i = 0; i < cur->out_sqlda->sqld; i++) { - push_column(L, i, cur); - - if( num ) { - lua_pushnumber(L, i+1); - lua_pushvalue(L, -2); - lua_settable(L, 2); - } - - if( alpha ) { - lua_pushlstring(L, cur->out_sqlda->sqlvar[i].aliasname, cur->out_sqlda->sqlvar[i].aliasname_length); - lua_pushvalue(L, -2); - lua_settable(L, 2); - } - - lua_pop(L, 1); - } - - /* returning given table */ - return 1; - } else { - for (i = 0; i < cur->out_sqlda->sqld; i++) - push_column(L, i, cur); - - /* returning a list of values */ - return cur->out_sqlda->sqld; - } - } - - /* 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) ) - 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; -} - -/* -** Returns a table of column names from the query -** Lua Returns: -** a table of column names -** nil and error message otherwise. -*/ -static int cur_colnames (lua_State *L) { - int i; - XSQLVAR *var; - 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); - lua_pushlstring(L, var->aliasname, var->aliasname_length); - lua_settable(L, -3); - } - - return 1; -} - -/* -** Returns a table of column types from the query -** Lua Returns: -** a table of column types -** nil and error message otherwise. -*/ -static int cur_coltypes (lua_State *L) { - int i; - XSQLVAR *var; - 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; -} - -/* -** Closes a cursor object -** Lua Returns: -** 1 if close was sucsessful, 0 if already closed -** nil and error message otherwise. -*/ -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"); - - if(cur->closed == 0) { - isc_dsql_free_statement(cur->env->status_vector, &cur->stmt, DSQL_drop); - if ( CHECK_DB_ERROR(cur->env->status_vector) ) - return return_db_error(L, cur->env->status_vector); - - /* free the cursor data */ - free_cur(cur); - - /* remove cursor from lock count */ - cur->closed = 1; - --cur->conn->lock; - - /* check if connection can be unregistered */ - if(cur->conn->lock == 0) - lua_unregisterobj(L, cur->conn); - - /* return sucsess */ - lua_pushboolean(L, 1); - return 1; - } - - lua_pushboolean(L, 0); - return 1; -} - -/* -** GCs a cursor object -*/ -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) { - isc_dsql_free_statement(cur->env->status_vector, &cur->stmt, DSQL_drop); - - /* free the cursor data */ - free_cur(cur); - - /* remove cursor from lock count */ - cur->closed = 1; - --cur->conn->lock; - - /* check if connection can be unregistered */ - if(cur->conn->lock == 0) - lua_unregisterobj(L, cur->conn); - } - - return 0; -} - -/* -** Creates an Environment and returns it. -*/ -static int create_environment (lua_State *L) { - int i; - 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; - - return 1; -} - -/* -** Creates and returns a connection object -** Lua Input: source, user, pass -** source: data source -** user, pass: data source authentication information -** Lua Returns: -** connection object if successfull -** nil and error message otherwise. -*/ -static int env_connect (lua_State *L) { - char *dpb; - int i; - static char isc_tpb[] = { isc_tpb_version3, - isc_tpb_write }; - conn_data conn; - conn_data* res_conn; - - env_data *env = (env_data *) getenvironment (L, 1); - const char *sourcename = luaL_checkstring (L, 2); - const char *username = luaL_optstring (L, 3, ""); - const char *password = luaL_optstring (L, 4, ""); - - conn.env = env; - conn.db = 0L; - conn.transaction = 0L; - conn.lock = 0; - conn.autocommit = 0; - - /* Construct a database parameter buffer. */ - dpb = conn.dpb_buffer; - *dpb++ = isc_dpb_version1; - *dpb++ = isc_dpb_num_buffers; - *dpb++ = 1; - *dpb++ = 90; - - /* add the user name and password */ - *dpb++ = isc_dpb_user_name; - *dpb++ = (char)strlen(username); - for(i=0; i<(int)strlen(username); i++) - *dpb++ = username[i]; - *dpb++ = isc_dpb_password; - *dpb++ = (char)strlen(password); - for(i=0; i<(int)strlen(password); i++) - *dpb++ = password[i]; - - /* the length of the dpb */ - 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); - - /* an error? */ - 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 ); - - /* return NULL on 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)); - 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); - ++env->lock; - - return 1; -} - -/* -** Closes an environment object -** Lua Returns: -** 1 if close was sucsessful, 0 if already closed -** nil and error message otherwise. -*/ -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); - return 1; - } - - /* check the lock */ - if(env->lock > 0) - return luasql_faildirect(L, "there are still open connections"); - - /* unregister */ - lua_unregisterobj(L, env); - - /* mark as closed */ - env->closed = 1; - - lua_pushboolean(L, 1); - return 1; -} - -/* -** GCs an environment object -*/ -static int env_gc (lua_State *L) { - /* nothing to be done for the FB envronment */ - return 0; -} - -/* -** Create metatables for each class of object. -*/ -static void create_metatables (lua_State *L) { - struct luaL_Reg environment_methods[] = { - {"__gc", env_gc}, - {"close", env_close}, - {"connect", env_connect}, - {NULL, NULL}, - }; - struct luaL_Reg connection_methods[] = { - {"__gc", conn_gc}, - {"close", conn_close}, - {"execute", conn_execute}, - {"commit", conn_commit}, - {"rollback", conn_rollback}, - {"setautocommit", conn_setautocommit}, - {"escape", conn_escape}, - {NULL, NULL}, - }; - struct luaL_Reg cursor_methods[] = { - {"__gc", cur_gc}, - {"close", cur_close}, - {"fetch", cur_fetch}, - {"getcoltypes", cur_coltypes}, - {"getcolnames", cur_colnames}, - {NULL, NULL}, - }; - luasql_createmeta (L, LUASQL_ENVIRONMENT_FIREBIRD, environment_methods); - luasql_createmeta (L, LUASQL_CONNECTION_FIREBIRD, connection_methods); - luasql_createmeta (L, LUASQL_CURSOR_FIREBIRD, cursor_methods); - lua_pop (L, 3); -} - -/* -** Creates the metatables for the objects and registers the -** driver open method. -*/ -LUASQL_API int luaopen_luasql_firebird (lua_State *L) { - struct luaL_Reg driver[] = { - {"firebird", create_environment}, - {NULL, NULL}, - }; - create_metatables (L); - lua_newtable (L); - luaL_setfuncs (L, driver, 0); - luasql_set_info (L); - return 1; -} +/* +** LuaSQL, Firebird driver +** Authors: Scott Morgan +** ls_firebird.c +*/ + +#include /* The Firebird API*/ +#include /* For managing time values */ +#include +#include +#include + +/* Lua API */ +#include +#include + +#include "luasql.h" + +#define LUASQL_ENVIRONMENT_FIREBIRD "Firebird environment" +#define LUASQL_CONNECTION_FIREBIRD "Firebird connection" +#define LUASQL_STATEMENT_FIREBIRD "Firebird statement" +#define LUASQL_CURSOR_FIREBIRD "Firebird cursor" + +/* pre-def to keep the compiler happy */ +LUASQL_API int luaopen_luasql_firebird (lua_State *L); + +typedef struct { + short closed; + int lock; /* lock count for open connections */ + ISC_STATUS status_vector[20];/* for error results */ +} env_data; + +typedef struct { + /* 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; + 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 */ +#define CURSOR_PREALLOC 10 + +/* Macro to ease code reading */ +#define CHECK_DB_ERROR( X ) ( (X)[0] == 1 && (X)[1] ) + +/* Use the new interpret API if available */ +#undef FB_INTERPRET +#if FB_API_VER >= 20 +#define FB_INTERPRET(BUF, LEN, VECTOR) fb_interpret(BUF, LEN, VECTOR) +#else +#define FB_INTERPRET(BUF, LEN, VECTOR) isc_interpret(BUF, VECTOR) +#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 + +/* +** Returns a standard database error message +*/ +static int return_db_error(lua_State *L, const ISC_STATUS *pvector) +{ + char errmsg[512]; + + lua_pushnil(L); + FB_INTERPRET(errmsg, 512, &pvector); + lua_pushstring(L, errmsg); + while(FB_INTERPRET(errmsg, 512, &pvector)) { + lua_pushstring(L, "\n * "); + lua_pushstring(L, errmsg); + lua_concat(L, 3); + } + + return 2; +} + +/* +** Allocates and initialises an XSQLDA struct +*/ +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; + + /* 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); + } + } +} + +/* +** 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) { + luasql_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); +} + +/* +** Shuts down a cursor +*/ +static int cur_shut(lua_State *L, cur_data *cur) +{ + 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); + } + + /* free the cursor data */ + free_cur(cur); + + /* remove cursor from lock count and check if statment can be unregistered */ + cur->closed = 1; + if(--cur->stmt->lock == 0) { + luasql_unregisterobj(L, cur->stmt); + + /* hidden statement, needs closing now */ + if(cur->stmt->hidden) { + return stmt_shut(L, cur->stmt); + } + } + + return 0; +} + +/* +** Check for valid environment. +*/ +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"); + return env; +} + +/* +** 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); + 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) +{ + 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(stmt_data *stmt) +{ + int length, type; + char type_item[] = { isc_info_sql_stmt_type }; + char res_buffer[88], *pres; + + pres = res_buffer; + + 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) { + 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 */ + } + + return type; +} + +/* +** Return the number of rows affected by last operation +*/ +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; + char type_item[] = { isc_info_sql_stmt_type, isc_info_sql_records }; + char res_buffer[88], *pres; + + pres = res_buffer; + + 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) { + 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 */ + } + + if(type > 4) { + return 0; /* not a SELECT, INSERT, UPDATE or DELETE SQL statement */ + } + + if (*pres == isc_info_sql_records) { + pres++; + length = isc_vax_integer(pres, 2); /* normally 29 bytes */ + pres += 2; + + while(*pres != 1) { + switch(*pres) { + case isc_info_req_select_count: + pres++; + length = isc_vax_integer(pres, 2); + pres += 2; + sel_count = isc_vax_integer(pres, length); + pres += length; + break; + case isc_info_req_insert_count: + pres++; + length = isc_vax_integer(pres, 2); + pres += 2; + ins_count = isc_vax_integer(pres, length); + pres += length; + break; + case isc_info_req_update_count: + pres++; + length = isc_vax_integer(pres, 2); + pres += 2; + upd_count = isc_vax_integer(pres, length); + pres += length; + break; + case isc_info_req_delete_count: + pres++; + length = isc_vax_integer(pres, 2); + pres += 2; + del_count = isc_vax_integer(pres, length); + pres += length; + break; + default: + pres++; + break; + } + } + } else { + return -3; + } + + switch(type) { + case isc_info_sql_stmt_select: + res = sel_count; + break; + case isc_info_sql_stmt_delete: + res = del_count; + break; + case isc_info_sql_stmt_update: + res = upd_count; + break; + case isc_info_sql_stmt_insert: + res = ins_count; + break; + } + return res; +} + +static void fill_param(XSQLVAR *var, ISC_SHORT type, ISC_SCHAR *data, + ISC_SHORT len) +{ + var->sqltype = type; + *var->sqlind = 0; + var->sqllen = len; + + if((type & ~1) == SQL_TEXT) { + --var->sqllen; + } + + if(var->sqldata != NULL) { + free(var->sqldata); + } + var->sqldata = (ISC_SCHAR *)malloc(len); + memcpy(var->sqldata, data, len); +} + +static void parse_params(lua_State *L, stmt_data *stmt, int params) +{ + int i; + for(i=0; iin_sqlda->sqln; i++) { + XSQLVAR *var; + const char *str; + ISC_INT64 inum; + double fnum; + + lua_pushnumber(L, i+1); + lua_gettable(L, params); + + var = &stmt->in_sqlda->sqlvar[i]; + 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); + 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; + } + } + + lua_pop(L,1); /* param value */ + } +} + +/* +** Prepares a SQL statement. +** Lua input: +** SQL statement +** [parmeter table] +** Returns +** statement object ready for setting parameters +** nil and error message otherwise. +*/ +static int conn_prepare (lua_State *L) +{ + conn_data *conn = getconnection(L,1); + const char *statement = luaL_checkstring(L, 2); + + stmt_data *user_stmt; + + stmt_data stmt; + + memset(&stmt, 0, sizeof(stmt_data)); + + 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, &stmt.handle); + if ( CHECK_DB_ERROR(conn->env->status_vector) ) { + 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, &stmt.handle, 0, + (char *)statement, conn->dialect, NULL); + if ( CHECK_DB_ERROR(conn->env->status_vector) ) { + free_stmt(&stmt); + return return_db_error(L, conn->env->status_vector); + } + + /* what type of SQL statement is it? */ + 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) */ + if(stmt.type > 5) { + free_stmt(&stmt); + return luasql_faildirect(L, "unsupported SQL statement"); + } + + /* 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_stmt(&stmt); + return return_db_error(L, conn->env->status_vector); + } + } + malloc_sqlda_vars(stmt.in_sqlda); + + /* is there a parameter table to use */ + if(lua_istable(L, 3)) { + parse_params(L, &stmt, 3); + } + + /* 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 */ + luasql_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; + } + + 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"); + } + + memset(&cur, 0, sizeof(cur_data)); + cur.closed = 0; + cur.stmt = stmt; + cur.env = stmt->env; + + /* if it's a SELECT statment, allocate a cursor */ + if(stmt->type == isc_info_sql_stmt_select) { + 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->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, 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); + } + + /* 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, 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 */ + 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)); + + /* add cursor to the lock count */ + luasql_registerobj(L, stmt_indx, user_cur->stmt); + ++user_cur->stmt->lock; + } else { /* a count */ + /* 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); + } + + luasql_pushinteger(L, count); + + /* 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(stmt->type != isc_info_sql_stmt_select) { + if((ret = stmt_shut(L, stmt)) != 0) { + return ret; + } + } + + return 1; +} + +/* +** Commits the current transaction +*/ +static int conn_commit(lua_State *L) +{ + conn_data *conn = getconnection(L,1); + + isc_commit_retaining(conn->env->status_vector, &conn->transaction); + if ( CHECK_DB_ERROR(conn->env->status_vector) ) { + return return_db_error(L, conn->env->status_vector); + } + + lua_pushboolean(L, 1); + return 1; +} + +/* +** Rolls back the current transaction +** Lua Returns: +** 1 if rollback is sucsessful +** nil and error message otherwise. +*/ +static int conn_rollback(lua_State *L) +{ + conn_data *conn = getconnection(L,1); + + isc_rollback_retaining(conn->env->status_vector, &conn->transaction); + if ( CHECK_DB_ERROR(conn->env->status_vector) ) { + return return_db_error(L, conn->env->status_vector); + } + + lua_pushboolean(L, 1); + return 1; +} + +/* +** Sets the autocommit state of the connection +** Lua Returns: +** autocommit state (0:off, 1:on) +** nil and error message on error. +*/ +static int conn_setautocommit(lua_State *L) +{ + conn_data *conn = getconnection(L,1); + + if(lua_toboolean(L, 2)) { + conn->autocommit = 1; + } else { + conn->autocommit = 0; + } + + lua_pushboolean(L, 1); + return 1; +} + +/* +** Closes a connection. +** Lua Returns: +** 1 if close was sucsessful, 0 if already closed +** nil and error message otherwise. +*/ +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"); + + /* already closed */ + if(conn->closed != 0) { + lua_pushboolean(L, 0); + return 1; + } + + /* are all related statements closed? */ + if(conn->lock > 0) { + return luasql_faildirect(L, "there are still open statements/cursors"); + } + + if(conn->autocommit != 0) { + isc_commit_transaction(conn->env->status_vector, &conn->transaction); + } else { + isc_rollback_transaction(conn->env->status_vector, &conn->transaction); + } + 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) ) { + 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) { + luasql_unregisterobj(L, conn->env); + } + + lua_pushboolean(L, 1); + return 1; +} + +/* +** GCs an connection object +*/ +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); + } + + isc_detach_database(conn->env->status_vector, &conn->db); + + conn->closed = 1; + --conn->env->lock; + + /* check environment can be GC'd */ + if(conn->env->lock == 0) { + luasql_unregisterobj(L, conn->env); + } + } + + return 0; +} + +/* +** Escapes a given string so that it can't break out of it's delimiting quotes +*/ +static int conn_escape(lua_State *L) +{ + size_t len; + const char *from = luaL_checklstring (L, 2, &len); + char *res = malloc(len*sizeof(char)*2+1); + char *to = res; + + if(res) { + while(*from != '\0') { + *(to++) = *from; + if(*from == '\'') { + *(to++) = *from; + } + + from++; + } + *to = '\0'; + + lua_pushstring(L, res); + free(res); + return 1; + } + + luaL_error(L, "could not allocate escaped string"); + return 0; +} + +/* +** Pushes the indexed value onto the lua stack +*/ +static void push_column(lua_State *L, int i, cur_data *cur) +{ + int varcharlen; + struct tm timevar; + char timestr[256]; + ISC_STATUS blob_stat; + isc_blob_handle blob_handle = 0; + ISC_QUAD blob_id; + luaL_Buffer b; + char *buffer; + unsigned short actual_seg_len; + + if( (cur->out_sqlda->sqlvar[i].sqlind != NULL) && + (*(cur->out_sqlda->sqlvar[i].sqlind) != 0) ) { + /* a null field? */ + lua_pushnil(L); + } else { + switch(cur->out_sqlda->sqlvar[i].sqltype & ~1) { + case SQL_VARYING: + varcharlen = (int)isc_vax_integer(cur->out_sqlda->sqlvar[i].sqldata, 2); + 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); + break; + case SQL_SHORT: + 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)); + break; + case SQL_INT64: + 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)); + break; + case SQL_DOUBLE: + 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); + 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, "%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, "%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->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 ); + 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 ); + } + + /* finnished, close the BLOB */ + isc_close_blob(cur->env->status_vector, &blob_handle); + blob_handle = 0; + + luaL_pushresult(&b); + break; + default: + lua_pushstring(L, ""); + break; + } + } +} + +/* +** 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); + + /* is there a parameter table to use */ + if(lua_istable(L, 2)) { + parse_params(L, stmt, 2); + } + + 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) +{ + ISC_STATUS fetch_stat; + int i; + cur_data *cur = getcursor(L,1); + const char *opts = luaL_optstring (L, 3, "n"); + int num = strchr(opts, 'n') != NULL; + int alpha = strchr(opts, 'a') != NULL; + + 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); + + /* loop through the columns */ + for (i = 0; i < cur->out_sqlda->sqld; i++) { + push_column(L, i, cur); + + if( num ) { + lua_pushnumber(L, i+1); + lua_pushvalue(L, -2); + lua_settable(L, 2); + } + + if( alpha ) { + lua_pushlstring(L, cur->out_sqlda->sqlvar[i].aliasname, + cur->out_sqlda->sqlvar[i].aliasname_length); + lua_pushvalue(L, -2); + lua_settable(L, 2); + } + + lua_pop(L, 1); + } + + /* returning given table */ + return 1; + } else { + for (i = 0; i < cur->out_sqlda->sqld; i++) { + push_column(L, i, cur); + } + + /* returning a list of values */ + return cur->out_sqlda->sqld; + } + } + + /* 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); + } + + /* shut cursor */ + return cur_shut(L, cur); +} + +/* +** Returns a table of column names from the query +** Lua Returns: +** a table of column names +** nil and error message otherwise. +*/ +static int cur_colnames (lua_State *L) +{ + int i; + XSQLVAR *var; + 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); + lua_pushlstring(L, var->aliasname, var->aliasname_length); + lua_settable(L, -3); + } + + return 1; +} + +/* +** Returns a table of column types from the query +** Lua Returns: +** a table of column types +** nil and error message otherwise. +*/ +static int cur_coltypes (lua_State *L) +{ + cur_data *cur = getcursor(L,1); + + return dump_xsqlda_types(L, cur->out_sqlda); +} + +/* +** Closes a cursor object +** Lua Returns: +** true if close was sucsessful, false if already closed +** nil and error message otherwise. +*/ +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"); + + if(cur->closed == 0) { + int res = cur_shut(L, cur); + if(res != 0) { + return res; + } + + /* return sucsess */ + lua_pushboolean(L, 1); + return 1; + } + + lua_pushboolean(L, 0); + return 1; +} + +/* +** GCs a cursor object +*/ +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) { + if(cur_shut(L, cur) != 0) { + return 1; + } + } + + return 0; +} + +/* +** Creates an Environment and returns it. +*/ +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); + memset (env, 0, sizeof (env_data)); + + return 1; +} + +/* +** Reforms old connection style to new one +** Lua Input: source, [user, [pass]] +** source: data source +** user, pass: data source authentication information +** Lua Returns: +** new connection details table +*/ +static void env_connect_fix_old (lua_State *L) +{ + static const char *const opt_names[] = { + "source", + "user", + "password", + NULL + }; + int i, t = lua_gettop(L)-1; + + lua_newtable(L); + lua_insert(L, 2); + + for(i=0; opt_names[i] != NULL && i, +** user = , +** password = , +** [charset = ,] +** [dialect = ,] +** } +** Lua Returns: +** connection object if successfull +** nil and error message otherwise. +*/ +static int env_connect (lua_State *L) +{ + char *dpb, *dpb_end; + static char isc_tpb[] = { + isc_tpb_version3, + isc_tpb_write + }; + + conn_data conn; + conn_data *res_conn; + const char *sourcename; + + env_data *env = (env_data *) getenvironment (L, 1); + + if(lua_gettop(L) < 2) { + return luasql_faildirect(L, "No connection details provided"); + } + + if(!lua_istable(L, 2)) { + env_connect_fix_old(L); + } + + sourcename = luasql_table_optstring(L, 2, "source", NULL); + + if(sourcename == NULL) { + return luasql_faildirect(L, "connection details table missing 'source'"); + } + + conn.env = env; + conn.db = 0L; + conn.transaction = 0L; + conn.lock = 0; + conn.autocommit = 0; + + /* construct a database parameter buffer. */ + dpb = conn.dpb_buffer; + dpb_end = conn.dpb_buffer + sizeof(conn.dpb_buffer); + *dpb++ = isc_dpb_version1; + *dpb++ = isc_dpb_num_buffers; + *dpb++ = 1; + *dpb++ = 90; + + /* add the user name and password */ + dpb = add_dpb_string(dpb, dpb_end, isc_dpb_user_name, + luasql_table_optstring(L, 2, "user", "")); + dpb = add_dpb_string(dpb, dpb_end, isc_dpb_password, + luasql_table_optstring(L, 2, "password", "")); + + /* other database parameters */ + dpb = add_dpb_string(dpb, dpb_end, isc_dpb_lc_ctype, + luasql_table_optstring(L, 2, "charset", "UTF8")); + conn.dialect = (unsigned short)luasql_table_optnumber(L, 2, "dialect", 3); + + /* the length of the dpb */ + 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); + + /* an error? */ + 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 ); + + /* 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)); + luasql_setmeta (L, LUASQL_CONNECTION_FIREBIRD); + memcpy(res_conn, &conn, sizeof(conn_data)); + res_conn->closed = 0; /* connect now officially open */ + + /* register the connection */ + luasql_registerobj(L, 1, env); + ++env->lock; + + return 1; +} + +/* +** Closes an environment object +** Lua Returns: +** 1 if close was sucsessful, 0 if already closed +** nil and error message otherwise. +*/ +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); + return 1; + } + + /* check the lock */ + if(env->lock > 0) { + return luasql_faildirect(L, "there are still open connections"); + } + + /* unregister */ + luasql_unregisterobj(L, env); + + /* mark as closed */ + env->closed = 1; + + lua_pushboolean(L, 1); + return 1; +} + +/* +** GCs an environment object +*/ +static int env_gc (lua_State *L) +{ + /* nothing to be done for the FB envronment */ + return 0; +} + +/* +** Create metatables for each class of object. +*/ +static void create_metatables (lua_State *L) +{ + struct luaL_Reg environment_methods[] = { + {"__gc", env_gc}, + {"close", env_close}, + {"connect", env_connect}, + {NULL, NULL}, + }; + struct luaL_Reg connection_methods[] = { + {"__gc", conn_gc}, + {"close", conn_close}, + {"prepare", conn_prepare}, + {"execute", conn_execute}, + {"commit", conn_commit}, + {"rollback", conn_rollback}, + {"setautocommit", conn_setautocommit}, + {"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}, + {"fetch", cur_fetch}, + {"getcoltypes", cur_coltypes}, + {"getcolnames", cur_colnames}, + {NULL, NULL}, + }; + 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, 4); +} + +/* +** Creates the metatables for the objects and registers the +** driver open method. +*/ +LUASQL_API int luaopen_luasql_firebird (lua_State *L) +{ + struct luaL_Reg driver[] = { + {"firebird", create_environment}, + {NULL, NULL}, + }; + + create_metatables (L); + luasql_reg_driver(L, driver); + + return 1; +} diff --git a/src/ls_mysql.c b/src/ls_mysql.c index 1abd87d..48369eb 100644 --- a/src/ls_mysql.c +++ b/src/ls_mysql.c @@ -16,40 +16,47 @@ #define NO_CLIENT_LONG_LONG #endif -#include "mysql.h" +#include #include "lua.h" #include "lauxlib.h" #include "luasql.h" +/* 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 + #define LUASQL_ENVIRONMENT_MYSQL "MySQL environment" #define LUASQL_CONNECTION_MYSQL "MySQL connection" +#define LUASQL_STATEMENT_MYSQL "MySQL statement" #define LUASQL_CURSOR_MYSQL "MySQL cursor" /* For compat with old version 4.0 */ -#if (MYSQL_VERSION_ID < 40100) -#define MYSQL_TYPE_VAR_STRING FIELD_TYPE_VAR_STRING -#define MYSQL_TYPE_STRING FIELD_TYPE_STRING -#define MYSQL_TYPE_DECIMAL FIELD_TYPE_DECIMAL -#define MYSQL_TYPE_SHORT FIELD_TYPE_SHORT -#define MYSQL_TYPE_LONG FIELD_TYPE_LONG -#define MYSQL_TYPE_FLOAT FIELD_TYPE_FLOAT -#define MYSQL_TYPE_DOUBLE FIELD_TYPE_DOUBLE -#define MYSQL_TYPE_LONGLONG FIELD_TYPE_LONGLONG -#define MYSQL_TYPE_INT24 FIELD_TYPE_INT24 -#define MYSQL_TYPE_YEAR FIELD_TYPE_YEAR -#define MYSQL_TYPE_TINY FIELD_TYPE_TINY -#define MYSQL_TYPE_TINY_BLOB FIELD_TYPE_TINY_BLOB -#define MYSQL_TYPE_MEDIUM_BLOB FIELD_TYPE_MEDIUM_BLOB -#define MYSQL_TYPE_LONG_BLOB FIELD_TYPE_LONG_BLOB -#define MYSQL_TYPE_BLOB FIELD_TYPE_BLOB -#define MYSQL_TYPE_DATE FIELD_TYPE_DATE -#define MYSQL_TYPE_NEWDATE FIELD_TYPE_NEWDATE -#define MYSQL_TYPE_DATETIME FIELD_TYPE_DATETIME -#define MYSQL_TYPE_TIME FIELD_TYPE_TIME -#define MYSQL_TYPE_TIMESTAMP FIELD_TYPE_TIMESTAMP -#define MYSQL_TYPE_ENUM FIELD_TYPE_ENUM +#if (MYSQL_VERSION_ID < 40100) +#define MYSQL_TYPE_VAR_STRING FIELD_TYPE_VAR_STRING +#define MYSQL_TYPE_STRING FIELD_TYPE_STRING +#define MYSQL_TYPE_DECIMAL FIELD_TYPE_DECIMAL +#define MYSQL_TYPE_SHORT FIELD_TYPE_SHORT +#define MYSQL_TYPE_LONG FIELD_TYPE_LONG +#define MYSQL_TYPE_FLOAT FIELD_TYPE_FLOAT +#define MYSQL_TYPE_DOUBLE FIELD_TYPE_DOUBLE +#define MYSQL_TYPE_LONGLONG FIELD_TYPE_LONGLONG +#define MYSQL_TYPE_INT24 FIELD_TYPE_INT24 +#define MYSQL_TYPE_YEAR FIELD_TYPE_YEAR +#define MYSQL_TYPE_TINY FIELD_TYPE_TINY +#define MYSQL_TYPE_TINY_BLOB FIELD_TYPE_TINY_BLOB +#define MYSQL_TYPE_MEDIUM_BLOB FIELD_TYPE_MEDIUM_BLOB +#define MYSQL_TYPE_LONG_BLOB FIELD_TYPE_LONG_BLOB +#define MYSQL_TYPE_BLOB FIELD_TYPE_BLOB +#define MYSQL_TYPE_DATE FIELD_TYPE_DATE +#define MYSQL_TYPE_NEWDATE FIELD_TYPE_NEWDATE +#define MYSQL_TYPE_DATETIME FIELD_TYPE_DATETIME +#define MYSQL_TYPE_TIME FIELD_TYPE_TIME +#define MYSQL_TYPE_TIMESTAMP FIELD_TYPE_TIMESTAMP +#define MYSQL_TYPE_ENUM FIELD_TYPE_ENUM #define MYSQL_TYPE_SET FIELD_TYPE_SET #define MYSQL_TYPE_NULL FIELD_TYPE_NULL @@ -60,21 +67,31 @@ #endif typedef struct { - short closed; + short closed; + int lock; /* lock count for open connections */ } env_data; typedef struct { - short closed; - int env; /* reference to environment */ - MYSQL *my_conn; + short closed; + int lock; /* lock count for open cursors */ + int env; /* reference to environment */ + MYSQL *my_conn; } conn_data; typedef struct { - short closed; - int conn; /* reference to connection */ - int numcols; /* number of columns */ - int colnames, coltypes; /* reference to column information tables */ - MYSQL_RES *my_res; + short closed; + int lock; /* lock count for open statements */ + unsigned char hidden; /* statement was used interally i.e. from a + direct con:execute */ + MYSQL_STMT *my_stmt; /* statement handle */ +} stmt_data; + +typedef struct { + short closed; + int stmt; /* reference to statement */ + int numcols; /* number of columns */ + int colnames, coltypes; /* reference to column information tables */ + MYSQL_RES *my_res; } cur_data; LUASQL_API int luaopen_luasql_mysql (lua_State *L); @@ -83,7 +100,8 @@ LUASQL_API int luaopen_luasql_mysql (lua_State *L); /* ** Check for valid environment. */ -static env_data *getenvironment (lua_State *L) { +static env_data *getenvironment (lua_State *L) +{ env_data *env = (env_data *)luaL_checkudata (L, 1, LUASQL_ENVIRONMENT_MYSQL); luaL_argcheck (L, env != NULL, 1, "environment expected"); luaL_argcheck (L, !env->closed, 1, "environment is closed"); @@ -94,7 +112,8 @@ static env_data *getenvironment (lua_State *L) { /* ** Check for valid connection. */ -static conn_data *getconnection (lua_State *L) { +static conn_data *getconnection (lua_State *L) +{ conn_data *conn = (conn_data *)luaL_checkudata (L, 1, LUASQL_CONNECTION_MYSQL); luaL_argcheck (L, conn != NULL, 1, "connection expected"); luaL_argcheck (L, !conn->closed, 1, "connection is closed"); @@ -102,57 +121,94 @@ static conn_data *getconnection (lua_State *L) { } +/* +** Check for valid statement. +*/ +static stmt_data *getstatement (lua_State *L) +{ + stmt_data *stmt = (stmt_data *)luaL_checkudata (L, 1, LUASQL_STATEMENT_MYSQL); + luaL_argcheck (L, stmt != NULL, 1, "statement expected"); + luaL_argcheck (L, !stmt->closed, 1, "statement is closed"); + return stmt; +} + /* ** Check for valid cursor. */ -static cur_data *getcursor (lua_State *L) { +static cur_data *getcursor (lua_State *L) +{ cur_data *cur = (cur_data *)luaL_checkudata (L, 1, LUASQL_CURSOR_MYSQL); luaL_argcheck (L, cur != NULL, 1, "cursor expected"); luaL_argcheck (L, !cur->closed, 1, "cursor is closed"); return cur; } +/* +** Shut an active statement object +*/ +static int stmt_shut(lua_State *L, stmt_data *stmt) +{ + if(!stmt->closed) { + mysql_stmt_close(stmt->my_stmt); + stmt->closed = 1; + } + + return 0; +} /* ** Push the value of #i field of #tuple row. */ -static void pushvalue (lua_State *L, void *row, long int len) { - if (row == NULL) +static void pushvalue (lua_State *L, void *row, long int len) +{ + if (row == NULL) { lua_pushnil (L); - else + } else { lua_pushlstring (L, row, len); + } } /* ** Get the internal database type of the given column. */ -static char *getcolumntype (enum enum_field_types type) { - +static char *getcolumntype (enum enum_field_types type) +{ switch (type) { - case MYSQL_TYPE_VAR_STRING: case MYSQL_TYPE_STRING: - return "string"; - case MYSQL_TYPE_DECIMAL: case MYSQL_TYPE_SHORT: case MYSQL_TYPE_LONG: - case MYSQL_TYPE_FLOAT: case MYSQL_TYPE_DOUBLE: case MYSQL_TYPE_LONGLONG: - case MYSQL_TYPE_INT24: case MYSQL_TYPE_YEAR: case MYSQL_TYPE_TINY: - return "number"; - case MYSQL_TYPE_TINY_BLOB: case MYSQL_TYPE_MEDIUM_BLOB: - case MYSQL_TYPE_LONG_BLOB: case MYSQL_TYPE_BLOB: - return "binary"; - case MYSQL_TYPE_DATE: case MYSQL_TYPE_NEWDATE: - return "date"; - case MYSQL_TYPE_DATETIME: - return "datetime"; - case MYSQL_TYPE_TIME: - return "time"; - case MYSQL_TYPE_TIMESTAMP: - return "timestamp"; - case MYSQL_TYPE_ENUM: case MYSQL_TYPE_SET: - return "set"; - case MYSQL_TYPE_NULL: - return "null"; - default: - return "undefined"; + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_STRING: + return "string"; + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_TINY: + return "number"; + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + return "binary"; + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_NEWDATE: + return "date"; + case MYSQL_TYPE_DATETIME: + return "datetime"; + case MYSQL_TYPE_TIME: + return "time"; + case MYSQL_TYPE_TIMESTAMP: + return "timestamp"; + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + return "set"; + case MYSQL_TYPE_NULL: + return "null"; + default: + return "undefined"; } } @@ -160,52 +216,61 @@ static char *getcolumntype (enum enum_field_types type) { /* ** Creates the lists of fields names and fields types. */ -static void create_colinfo (lua_State *L, cur_data *cur) { - MYSQL_FIELD *fields; - char typename[50]; +static void create_colinfo(lua_State *L, cur_data *cur) +{ + char name[50]; int i; - fields = mysql_fetch_fields(cur->my_res); - lua_newtable (L); /* names */ - lua_newtable (L); /* types */ - for (i = 1; i <= cur->numcols; i++) { - lua_pushstring (L, fields[i-1].name); - lua_rawseti (L, -3, i); - sprintf (typename, "%.20s(%ld)", getcolumntype (fields[i-1].type), fields[i-1].length); - lua_pushstring(L, typename); - lua_rawseti (L, -2, i); + MYSQL_FIELD *fields = mysql_fetch_fields(cur->my_res); + + lua_newtable(L); /* names */ + lua_newtable(L); /* types */ + + for(i = 1; i <= cur->numcols; i++) { + lua_pushstring(L, fields[i-1].name); + lua_rawseti(L, -3, i); + snprintf(name, sizeof(name), "%.20s(%ld)", getcolumntype (fields[i-1].type), + fields[i-1].length); + lua_pushstring(L, name); + lua_rawseti(L, -2, i); } + /* Stores the references in the cursor structure */ - cur->coltypes = luaL_ref (L, LUA_REGISTRYINDEX); - cur->colnames = luaL_ref (L, LUA_REGISTRYINDEX); + cur->coltypes = luaL_ref(L, LUA_REGISTRYINDEX); + cur->colnames = luaL_ref(L, LUA_REGISTRYINDEX); } /* ** Closes the cursos and nullify all structure fields. */ -static int cur_nullify (lua_State *L, cur_data *cur) { +static int cur_nullify (lua_State *L, cur_data *cur) +{ /* Nullify structure fields. */ cur->closed = 1; mysql_free_result(cur->my_res); - luaL_unref (L, LUA_REGISTRYINDEX, cur->conn); + luaL_unref (L, LUA_REGISTRYINDEX, cur->stmt); luaL_unref (L, LUA_REGISTRYINDEX, cur->colnames); luaL_unref (L, LUA_REGISTRYINDEX, cur->coltypes); + return 0; } - + /* ** Get another row of the given cursor. */ -static int cur_fetch (lua_State *L) { +static int cur_fetch (lua_State *L) +{ cur_data *cur = getcursor (L); MYSQL_RES *res = cur->my_res; unsigned long *lengths; MYSQL_ROW row = mysql_fetch_row(res); + if (row == NULL) { cur_nullify (L, cur); lua_pushnil(L); /* no more results */ return 1; } + lengths = mysql_fetch_lengths(res); if (lua_istable (L, 2)) { @@ -221,10 +286,12 @@ static int cur_fetch (lua_State *L) { if (strchr (opts, 'a') != NULL) { int i; /* Check if colnames exists */ - if (cur->colnames == LUA_NOREF) - create_colinfo(L, cur); - lua_rawgeti (L, LUA_REGISTRYINDEX, cur->colnames);/* Push colnames*/ - + if (cur->colnames == LUA_NOREF) { + create_colinfo(L, cur); + } + /* Push colnames */ + lua_rawgeti (L, LUA_REGISTRYINDEX, cur->colnames); + /* Copy values to alphanumerical indices */ for (i = 0; i < cur->numcols; i++) { lua_rawgeti(L, -1, i+1); /* push the field name */ @@ -237,12 +304,12 @@ static int cur_fetch (lua_State *L) { } lua_pushvalue(L, 2); return 1; /* return table */ - } - else { + } else { int i; luaL_checkstack (L, cur->numcols, LUASQL_PREFIX"too many columns"); - for (i = 0; i < cur->numcols; i++) + for (i = 0; i < cur->numcols; i++) { pushvalue (L, row[i], lengths[i]); + } return cur->numcols; /* return #numcols values */ } } @@ -251,10 +318,12 @@ static int cur_fetch (lua_State *L) { /* ** Cursor object collector function */ -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_MYSQL); - if (cur != NULL && !(cur->closed)) + if (cur != NULL && !(cur->closed)) { cur_nullify (L, cur); + } return 0; } @@ -263,7 +332,8 @@ static int cur_gc (lua_State *L) { ** Close the cursor on top of the stack. ** Return 1 */ -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_MYSQL); luaL_argcheck (L, cur != NULL, 1, LUASQL_PREFIX"cursor expected"); if (cur->closed) { @@ -281,13 +351,15 @@ static int cur_close (lua_State *L) { ** If the table isn't built yet, call the creator function and stores ** a reference to it on the cursor structure. */ -static void _pushtable (lua_State *L, cur_data *cur, size_t off) { +static void _pushtable (lua_State *L, cur_data *cur, size_t off) +{ int *ref = (int *)((char *)cur + off); /* If colnames or coltypes do not exist, create both. */ - if (*ref == LUA_NOREF) + if (*ref == LUA_NOREF) { create_colinfo(L, cur); - + } + /* Pushes the right table (colnames or coltypes) */ lua_rawgeti (L, LUA_REGISTRYINDEX, *ref); } @@ -297,7 +369,8 @@ static void _pushtable (lua_State *L, cur_data *cur, size_t off) { /* ** Return the list of field names. */ -static int cur_getcolnames (lua_State *L) { +static int cur_getcolnames (lua_State *L) +{ pushtable (L, getcursor(L), colnames); return 1; } @@ -306,7 +379,8 @@ static int cur_getcolnames (lua_State *L) { /* ** Return the list of field types. */ -static int cur_getcoltypes (lua_State *L) { +static int cur_getcoltypes (lua_State *L) +{ pushtable (L, getcursor(L), coltypes); return 1; } @@ -315,8 +389,9 @@ static int cur_getcoltypes (lua_State *L) { /* ** Push the number of rows. */ -static int cur_numrows (lua_State *L) { - lua_pushinteger (L, (lua_Number)mysql_num_rows (getcursor(L)->my_res)); +static int cur_numrows (lua_State *L) +{ + lua_pushinteger (L, (lua_Integer)mysql_num_rows (getcursor(L)->my_res)); return 1; } @@ -324,26 +399,48 @@ static int cur_numrows (lua_State *L) { /* ** Create a new Cursor object and push it on top of the stack. */ -static int create_cursor (lua_State *L, int conn, MYSQL_RES *result, int cols) { +static int create_cursor (lua_State *L, int stmt, MYSQL_RES *result, int cols) +{ cur_data *cur = (cur_data *)lua_newuserdata(L, sizeof(cur_data)); luasql_setmeta (L, LUASQL_CURSOR_MYSQL); /* fill in structure */ cur->closed = 0; - cur->conn = LUA_NOREF; + cur->stmt = LUA_NOREF; cur->numcols = cols; cur->colnames = LUA_NOREF; cur->coltypes = LUA_NOREF; cur->my_res = result; - lua_pushvalue (L, conn); - cur->conn = luaL_ref (L, LUA_REGISTRYINDEX); + lua_pushvalue (L, stmt); + cur->stmt = luaL_ref (L, LUA_REGISTRYINDEX); return 1; } +static int stmt_gc (lua_State *L) +{ + stmt_shut(L, getstatement(L)); -static int conn_gc (lua_State *L) { - conn_data *conn=(conn_data *)luaL_checkudata(L, 1, LUASQL_CONNECTION_MYSQL); + return 0; +} + +static int stmt_close(lua_State *L) +{ + stmt_data *stmt = getstatement(L); + if (stmt->closed) { + lua_pushboolean (L, 0); + return 1; + } + + stmt_shut(L, stmt); + + lua_pushboolean (L, 1); + return 1; +} + +static int conn_gc (lua_State *L) +{ + conn_data *conn = (conn_data *)luaL_checkudata (L, 1, LUASQL_CONNECTION_MYSQL); if (conn != NULL && !(conn->closed)) { /* Nullify structure fields. */ conn->closed = 1; @@ -357,7 +454,8 @@ static int conn_gc (lua_State *L) { /* ** Close a Connection object. */ -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_MYSQL); luaL_argcheck (L, conn != NULL, 1, LUASQL_PREFIX"connection expected"); if (conn->closed) { @@ -370,60 +468,106 @@ static int conn_close (lua_State *L) { } -static int escape_string (lua_State *L) { - size_t size, new_size; - conn_data *conn = getconnection (L); - const char *from = luaL_checklstring(L, 2, &size); - char *to; - to = (char*)malloc(sizeof(char) * (2 * size + 1)); - if(to) { - new_size = mysql_real_escape_string(conn->my_conn, to, from, size); - lua_pushlstring(L, to, new_size); - free(to); - return 1; - } - luaL_error(L, "could not allocate escaped string"); - return 0; +static int escape_string (lua_State *L) +{ + size_t size, new_size; + conn_data *conn = getconnection (L); + const char *from = luaL_checklstring(L, 2, &size); + char *to; + to = (char *)malloc(sizeof(char) * (2 * size + 1)); + if(to) { + new_size = mysql_real_escape_string(conn->my_conn, to, from, size); + lua_pushlstring(L, to, new_size); + free(to); + return 1; + } + luaL_error(L, "could not allocate escaped string"); + return 0; } /* -** Execute an SQL statement. -** Return a Cursor object if the statement is a query, otherwise -** return the number of tuples affected by the statement. +** Prepares an SQL statement +** Returns a Statement object */ -static int conn_execute (lua_State *L) { +static int conn_prepare (lua_State *L) +{ conn_data *conn = getconnection (L); size_t st_len; const char *statement = luaL_checklstring (L, 2, &st_len); - if (mysql_real_query(conn->my_conn, statement, st_len)) - /* error executing query */ - return luasql_failmsg(L, "error executing query. MySQL: ", mysql_error(conn->my_conn)); - else - { - MYSQL_RES *res = mysql_store_result(conn->my_conn); - unsigned int num_cols = mysql_field_count(conn->my_conn); - - if (res) { /* tuples returned */ - return create_cursor (L, 1, res, num_cols); - } - else { /* mysql_use_result() returned nothing; should it have? */ - if(num_cols == 0) { /* no tuples returned */ - /* query does not return data (it was not a SELECT) */ - lua_pushinteger(L, mysql_affected_rows(conn->my_conn)); - return 1; - } - else /* mysql_use_result() should have returned data */ - return luasql_failmsg(L, "error retrieving result. MySQL: ", mysql_error(conn->my_conn)); + stmt_data *stmt = (stmt_data *)lua_newuserdata(L, sizeof(stmt_data)); + + memset(stmt, 0, sizeof(stmt_data)); + luasql_setmeta (L, LUASQL_STATEMENT_MYSQL); + + stmt->my_stmt = mysql_stmt_init(conn->my_conn); + + if(stmt->my_stmt == NULL) { + return luasql_failmsg(L, "error preparing query. MySQL: ", + "out of memory"); + } + + if(mysql_stmt_prepare(stmt->my_stmt, statement, st_len) < 0) { + return luasql_failmsg(L, "error preparing query. MySQL: ", + mysql_stmt_error(stmt->my_stmt)); + } + + return 1; +} + +static int stmt_execute(lua_State *L) +{ + stmt_data *stmt = getstatement(L); + int numcols; + + if(mysql_stmt_execute(stmt->my_stmt) < 0) { + return luasql_failmsg(L, "error executing statement. MySQL: ", + mysql_stmt_error(stmt->my_stmt)); + } + + if((numcols = mysql_stmt_field_count(stmt->my_stmt)) != 0) { + /* SELECT */ + MYSQL_RES *res = mysql_stmt_result_metadata(stmt->my_stmt); + + if(res != NULL) { + return create_cursor(L, 1, res, numcols); } + + return luasql_failmsg(L, "error creating cursor. MySQL: ", + mysql_stmt_error(stmt->my_stmt)); + } else { + /* INSERT, DELETE, UPDATE, etc */ + lua_pushinteger(L, (lua_Integer)mysql_stmt_affected_rows(stmt->my_stmt)); + return 1; + } +} + +/* +** Execute an SQL statement. +** Return a Cursor object if the statement is a query, otherwise +** return the number of tuples affected by the statement. +*/ +static int conn_execute (lua_State *L) +{ + int res; + + /* prepare statement */ + if((res = conn_prepare(L)) != 1) { + return res; } + + /* execute statement */ + lua_insert(L, 1); + return stmt_execute(L); } /* ** Commit the current transaction. */ -static int conn_commit (lua_State *L) { +static int conn_commit (lua_State *L) +{ conn_data *conn = getconnection (L); + lua_pushboolean(L, !mysql_commit(conn->my_conn)); return 1; } @@ -432,8 +576,10 @@ static int conn_commit (lua_State *L) { /* ** Rollback the current transaction. */ -static int conn_rollback (lua_State *L) { +static int conn_rollback (lua_State *L) +{ conn_data *conn = getconnection (L); + lua_pushboolean(L, !mysql_rollback(conn->my_conn)); return 1; } @@ -442,14 +588,12 @@ static int conn_rollback (lua_State *L) { /* ** Set "auto commit" property of the connection. Modes ON/OFF */ -static int conn_setautocommit (lua_State *L) { +static int conn_setautocommit (lua_State *L) +{ conn_data *conn = getconnection (L); - if (lua_toboolean (L, 2)) { - mysql_autocommit(conn->my_conn, 1); /* Set it ON */ - } - else { - mysql_autocommit(conn->my_conn, 0); - } + + mysql_autocommit(conn->my_conn, lua_toboolean (L, 2) ? 1 : 0); + lua_pushboolean(L, 1); return 1; } @@ -458,16 +602,18 @@ static int conn_setautocommit (lua_State *L) { /* ** Get Last auto-increment id generated */ -static int conn_getlastautoid (lua_State *L) { - conn_data *conn = getconnection(L); - lua_pushinteger(L, mysql_insert_id(conn->my_conn)); - return 1; +static int conn_getlastautoid (lua_State *L) +{ + conn_data *conn = getconnection (L); + lua_pushinteger (L, (lua_Integer)mysql_insert_id(conn->my_conn)); + return 1; } /* ** Create a new Connection object and push it on top of the stack. */ -static int create_connection (lua_State *L, int env, MYSQL *const my_conn) { +static int create_connection (lua_State *L, int env, MYSQL *my_conn) +{ conn_data *conn = (conn_data *)lua_newuserdata(L, sizeof(conn_data)); luasql_setmeta (L, LUASQL_CONNECTION_MYSQL); @@ -475,34 +621,82 @@ static int create_connection (lua_State *L, int env, MYSQL *const my_conn) { conn->closed = 0; conn->env = LUA_NOREF; conn->my_conn = my_conn; + lua_pushvalue (L, env); conn->env = luaL_ref (L, LUA_REGISTRYINDEX); + return 1; } +/* +** Reforms old connection style to new one +** Lua Input: source, [user], [pass], [host], [port] +** source: data source +** user, pass: data source authentication information +** Lua Returns: +** new connection details table +*/ +static void env_connect_fix_old (lua_State *L) +{ + static const char *const opt_names[] = { + "source", + "user", + "password", + "host", + "port", + NULL + }; + int i, t = lua_gettop(L)-1; + + lua_newtable(L); + lua_insert(L, 2); + + for(i=0; opt_names[i] != NULL && iclosed)) +static int env_gc (lua_State *L) +{ + env_data *env = getenvironment(L); + + if (env != NULL && !(env->closed)) { env->closed = 1; + } + return 0; } @@ -525,14 +724,17 @@ static int env_gc (lua_State *L) { /* ** Close environment object. */ -static int env_close (lua_State *L) { - env_data *env= (env_data *)luaL_checkudata (L, 1, LUASQL_ENVIRONMENT_MYSQL); - luaL_argcheck (L, env != NULL, 1, LUASQL_PREFIX"environment expected"); +static int env_close (lua_State *L) +{ + env_data *env = getenvironment(L); + if (env->closed) { lua_pushboolean (L, 0); return 1; } + env->closed = 1; + lua_pushboolean (L, 1); return 1; } @@ -541,44 +743,54 @@ static int env_close (lua_State *L) { /* ** Create metatables for each class of object. */ -static void create_metatables (lua_State *L) { - struct luaL_Reg environment_methods[] = { - {"__gc", env_gc}, - {"close", env_close}, - {"connect", env_connect}, +static void create_metatables (lua_State *L) +{ + struct luaL_Reg environment_methods[] = { + {"__gc", env_gc}, + {"close", env_close}, + {"connect", env_connect}, {NULL, NULL}, }; - struct luaL_Reg connection_methods[] = { - {"__gc", conn_gc}, - {"close", conn_close}, - {"escape", escape_string}, - {"execute", conn_execute}, - {"commit", conn_commit}, - {"rollback", conn_rollback}, - {"setautocommit", conn_setautocommit}, + struct luaL_Reg connection_methods[] = { + {"__gc", conn_gc}, + {"close", conn_close}, + {"escape", escape_string}, + {"prepare", conn_prepare}, + {"execute", conn_execute}, + {"commit", conn_commit}, + {"rollback", conn_rollback}, + {"setautocommit", conn_setautocommit}, {"getlastautoid", conn_getlastautoid}, {NULL, NULL}, - }; - struct luaL_Reg cursor_methods[] = { - {"__gc", cur_gc}, - {"close", cur_close}, - {"getcolnames", cur_getcolnames}, - {"getcoltypes", cur_getcoltypes}, - {"fetch", cur_fetch}, - {"numrows", cur_numrows}, + }; + struct luaL_Reg statement_methods[] = { + {"__gc", stmt_gc}, + {"close", stmt_close}, + {"execute", stmt_execute}, + {NULL, NULL}, + }; + struct luaL_Reg cursor_methods[] = { + {"__gc", cur_gc}, + {"close", cur_close}, + {"getcolnames", cur_getcolnames}, + {"getcoltypes", cur_getcoltypes}, + {"fetch", cur_fetch}, + {"numrows", cur_numrows}, {NULL, NULL}, - }; + }; luasql_createmeta (L, LUASQL_ENVIRONMENT_MYSQL, environment_methods); luasql_createmeta (L, LUASQL_CONNECTION_MYSQL, connection_methods); + luasql_createmeta (L, LUASQL_STATEMENT_MYSQL, statement_methods); luasql_createmeta (L, LUASQL_CURSOR_MYSQL, cursor_methods); - lua_pop (L, 3); + lua_pop (L, 4); } /* ** Creates an Environment and returns it. */ -static int create_environment (lua_State *L) { +static int create_environment (lua_State *L) +{ env_data *env = (env_data *)lua_newuserdata(L, sizeof(env_data)); luasql_setmeta (L, LUASQL_ENVIRONMENT_MYSQL); @@ -592,17 +804,18 @@ static int create_environment (lua_State *L) { ** Creates the metatables for the objects and registers the ** driver open method. */ -LUASQL_API int luaopen_luasql_mysql (lua_State *L) { +LUASQL_API int luaopen_luasql_mysql (lua_State *L) +{ struct luaL_Reg driver[] = { {"mysql", create_environment}, {NULL, NULL}, }; create_metatables (L); - lua_newtable(L); - luaL_setfuncs(L, driver, 0); - luasql_set_info (L); - lua_pushliteral (L, "_MYSQLVERSION"); - lua_pushliteral (L, MYSQL_SERVER_VERSION); - lua_settable (L, -3); + luasql_reg_driver(L, driver); + + lua_pushliteral (L, "_MYSQLVERSION"); + lua_pushliteral (L, MYSQL_SERVER_VERSION); + lua_settable (L, -3); + return 1; } diff --git a/src/ls_oci8.c b/src/ls_oci8.c index 12cbdd9..aeb1a6c 100644 --- a/src/ls_oci8.c +++ b/src/ls_oci8.c @@ -851,7 +851,7 @@ LUASQL_API int luaopen_luasql_oci8 (lua_State *L) { {NULL, NULL}, }; create_metatables (L); - lua_newtable (L); + luasql_find_driver_table (L); luaL_setfuncs (L, driver, 0); luasql_set_info (L); return 1; diff --git a/src/ls_odbc.c b/src/ls_odbc.c index 1b93d6c..9a5b4d6 100644 --- a/src/ls_odbc.c +++ b/src/ls_odbc.c @@ -30,30 +30,51 @@ #define LUASQL_ENVIRONMENT_ODBC "ODBC environment" #define LUASQL_CONNECTION_ODBC "ODBC connection" +#define LUASQL_STATEMENT_ODBC "ODBC statement" #define LUASQL_CURSOR_ODBC "ODBC cursor" +/* holds data for paramter binding */ +typedef struct { + SQLPOINTER buf; + SQLINTEGER len; + SQLINTEGER type; +} param_data; +/* general form of the driver objects */ typedef struct { - short closed; - int conn_counter; - SQLHENV henv; /* environment handle */ -} env_data; + short closed; + int lock; +} obj_data; +typedef struct { + short closed; + int lock; /* lock count for open connections */ + SQLHENV henv; /* environment handle */ +} env_data; typedef struct { - short closed; - int cur_counter; - int env; /* reference to environment */ - SQLHDBC hdbc; /* database connection handle */ + short closed; + int lock; /* lock count for open statements */ + env_data *env; /* the connection's environment */ + SQLHDBC hdbc; /* database connection handle */ } conn_data; +typedef struct { + short closed; + int lock; /* lock count for open cursors */ + unsigned char hidden; /* these statement was created indirectly */ + conn_data *conn; /* the statement's connection */ + SQLHSTMT hstmt; /* statement handle */ + SQLSMALLINT numparams; /* number of input parameters */ + int paramtypes; /* reference to param type table */ + param_data *params; /* array of parater data */ +} stmt_data; typedef struct { - short closed; - int conn; /* reference to connection */ - int numcols; /* number of columns */ - int coltypes, colnames; /* reference to column information tables */ - SQLHSTMT hstmt; /* statement handle */ + short closed; + stmt_data *stmt; /* the cursor's statement */ + int numcols; /* number of columns */ + int coltypes, colnames; /* reference to column information tables */ } cur_data; @@ -61,40 +82,74 @@ typedef struct { #define hENV SQL_HANDLE_ENV #define hSTMT SQL_HANDLE_STMT #define hDBC SQL_HANDLE_DBC -#define error(a) ((a) != SQL_SUCCESS && (a) != SQL_SUCCESS_WITH_INFO) +static int error(SQLRETURN a) +{ + return (a != SQL_SUCCESS) && (a != SQL_SUCCESS_WITH_INFO) && (a != SQL_NO_DATA); +} LUASQL_API int luaopen_luasql_odbc (lua_State *L); +static int lock_obj(lua_State *L, int indx, void *ptr) +{ + obj_data *obj = (obj_data *)ptr; + + luasql_registerobj(L, indx, obj); + return ++obj->lock; +} + +static int unlock_obj(lua_State *L, void *ptr) +{ + obj_data *obj = (obj_data *)ptr; + + if(--obj->lock == 0) { + luasql_unregisterobj(L, obj); + } + + return obj->lock; +} + /* ** Check for valid environment. */ -static env_data *getenvironment (lua_State *L) { - env_data *env = (env_data *)luaL_checkudata (L, 1, LUASQL_ENVIRONMENT_ODBC); - luaL_argcheck (L, env != NULL, 1, LUASQL_PREFIX"environment expected"); - luaL_argcheck (L, !env->closed, 1, LUASQL_PREFIX"environment is closed"); +static env_data *getenvironment (lua_State *L, int i) +{ + env_data *env = (env_data *)luaL_checkudata (L, i, LUASQL_ENVIRONMENT_ODBC); + luaL_argcheck (L, env != NULL, i, LUASQL_PREFIX"environment expected"); + luaL_argcheck (L, !env->closed, i, LUASQL_PREFIX"environment is closed"); return env; } - /* ** Check for valid connection. */ -static conn_data *getconnection (lua_State *L) { - conn_data *conn = (conn_data *)luaL_checkudata (L, 1, LUASQL_CONNECTION_ODBC); - luaL_argcheck (L, conn != NULL, 1, LUASQL_PREFIX"connection expected"); - luaL_argcheck (L, !conn->closed, 1, LUASQL_PREFIX"connection is closed"); +static conn_data *getconnection (lua_State *L, int i) +{ + conn_data *conn = (conn_data *)luaL_checkudata (L, i, LUASQL_CONNECTION_ODBC); + luaL_argcheck (L, conn != NULL, i, LUASQL_PREFIX"connection expected"); + luaL_argcheck (L, !conn->closed, i, LUASQL_PREFIX"connection is closed"); return conn; } +/* +** Check for valid connection. +*/ +static stmt_data *getstatement (lua_State *L, int i) +{ + stmt_data *stmt = (stmt_data *)luaL_checkudata (L, i, LUASQL_STATEMENT_ODBC); + luaL_argcheck (L, stmt != NULL, i, LUASQL_PREFIX"statement expected"); + luaL_argcheck (L, !stmt->closed, i, LUASQL_PREFIX"statement is closed"); + return stmt; +} /* ** Check for valid cursor. */ -static cur_data *getcursor (lua_State *L) { - cur_data *cursor = (cur_data *)luaL_checkudata (L, 1, LUASQL_CURSOR_ODBC); - luaL_argcheck (L, cursor != NULL, 1, LUASQL_PREFIX"cursor expected"); - luaL_argcheck (L, !cursor->closed, 1, LUASQL_PREFIX"cursor is closed"); +static cur_data *getcursor (lua_State *L, int i) +{ + cur_data *cursor = (cur_data *)luaL_checkudata (L, i, LUASQL_CURSOR_ODBC); + luaL_argcheck (L, cursor != NULL, i, LUASQL_PREFIX"cursor expected"); + luaL_argcheck (L, !cursor->closed, i, LUASQL_PREFIX"cursor is closed"); return cursor; } @@ -102,68 +157,159 @@ static cur_data *getcursor (lua_State *L) { /* ** Pushes true and returns 1 */ -static int pass(lua_State *L) { - lua_pushboolean (L, 1); - return 1; +static int pass(lua_State *L) +{ + lua_pushboolean (L, 1); + return 1; } - /* ** Fails with error message from ODBC -** Inputs: +** Inputs: ** type: type of handle used in operation ** handle: handle used in operation */ -static int fail(lua_State *L, const SQLSMALLINT type, const SQLHANDLE handle) { - SQLCHAR State[6]; - SQLINTEGER NativeError; - SQLSMALLINT MsgSize, i; - SQLRETURN ret; - SQLCHAR Msg[SQL_MAX_MESSAGE_LENGTH]; - luaL_Buffer b; - lua_pushnil(L); - - luaL_buffinit(L, &b); - i = 1; - while (1) { - ret = SQLGetDiagRec(type, handle, i, State, &NativeError, Msg, - sizeof(Msg), &MsgSize); - if (ret == SQL_NO_DATA) break; - luaL_addlstring(&b, (char*)Msg, MsgSize); - luaL_addchar(&b, '\n'); - i++; - } - luaL_pushresult(&b); - return 2; +static int fail(lua_State *L, const SQLSMALLINT type, const SQLHANDLE handle) +{ + SQLCHAR State[6]; + SQLINTEGER NativeError; + SQLSMALLINT MsgSize, i; + SQLRETURN ret; + SQLCHAR Msg[SQL_MAX_MESSAGE_LENGTH]; + luaL_Buffer b; + lua_pushnil(L); + + luaL_buffinit(L, &b); + i = 1; + while (1) { + ret = SQLGetDiagRec(type, handle, i, State, &NativeError, Msg, sizeof(Msg), + &MsgSize); + if (ret == SQL_NO_DATA) { + break; + } + luaL_addlstring(&b, (char *)Msg, MsgSize); + luaL_addchar(&b, '\n'); + i++; + } + luaL_pushresult(&b); + return 2; +} + +static param_data *malloc_stmt_params(SQLSMALLINT c) +{ + param_data *p = (param_data *)malloc(sizeof(param_data)*c); + memset(p, 0, sizeof(param_data)*c); + + return p; +} + +static param_data *free_stmt_params(param_data *data, SQLSMALLINT c) +{ + + if(data != NULL) { + param_data *p = data; + + for(; c>0; ++p, --c) { + free(p->buf); + } + free(data); + } + + return NULL; +} + +/* +** Shuts a statement +** Returns non-zero on error +*/ +static int stmt_shut(lua_State *L, stmt_data *stmt) +{ + SQLRETURN ret; + + unlock_obj(L, stmt->conn); + stmt->closed = 1; + + luaL_unref (L, LUA_REGISTRYINDEX, stmt->paramtypes); + stmt->paramtypes = LUA_NOREF; + stmt->params = free_stmt_params(stmt->params, stmt->numparams); + + ret = SQLFreeHandle(hSTMT, stmt->hstmt); + if (error(ret)) { + return 1; + } + + return 0; +} + +/* +** Closes a cursor directly +** Returns non-zero on error +*/ +static int cur_shut(lua_State *L, cur_data *cur) +{ + /* Nullify structure fields. */ + cur->closed = 1; + if (error(SQLCloseCursor(cur->stmt->hstmt))) { + return fail(L, hSTMT, cur->stmt->hstmt); + } + + /* release col tables */ + luaL_unref (L, LUA_REGISTRYINDEX, cur->colnames); + luaL_unref (L, LUA_REGISTRYINDEX, cur->coltypes); + + /* release statement and, if hidden, shut it */ + if(unlock_obj(L, cur->stmt) == 0) { + if(cur->stmt->hidden) { + return stmt_shut(L, cur->stmt); + } + } + + return 0; } /* ** Returns the name of an equivalent lua type for a SQL type. */ -static const char *sqltypetolua (const SQLSMALLINT type) { - switch (type) { - case SQL_UNKNOWN_TYPE: case SQL_CHAR: case SQL_VARCHAR: - case SQL_TYPE_DATE: case SQL_TYPE_TIME: case SQL_TYPE_TIMESTAMP: - case SQL_DATE: case SQL_INTERVAL: case SQL_TIMESTAMP: - case SQL_LONGVARCHAR: - case SQL_WCHAR: case SQL_WVARCHAR: case SQL_WLONGVARCHAR: - return "string"; - case SQL_BIGINT: case SQL_TINYINT: - case SQL_INTEGER: case SQL_SMALLINT: +static const char *sqltypetolua (const SQLSMALLINT type) +{ + switch (type) { + case SQL_UNKNOWN_TYPE: + case SQL_CHAR: + case SQL_VARCHAR: + case SQL_TYPE_DATE: + case SQL_TYPE_TIME: + case SQL_TYPE_TIMESTAMP: + case SQL_DATE: + case SQL_INTERVAL: + case SQL_TIMESTAMP: + case SQL_LONGVARCHAR: + case SQL_WCHAR: + case SQL_WVARCHAR: + case SQL_WLONGVARCHAR: + return "string"; + case SQL_BIGINT: + case SQL_TINYINT: + case SQL_INTEGER: + case SQL_SMALLINT: #if LUA_VERSION_NUM>=503 - return "integer"; + return "integer"; #endif - case SQL_NUMERIC: case SQL_DECIMAL: - case SQL_FLOAT: case SQL_REAL: case SQL_DOUBLE: - return "number"; - case SQL_BINARY: case SQL_VARBINARY: case SQL_LONGVARBINARY: - return "binary"; /* !!!!!! nao seria string? */ - case SQL_BIT: - return "boolean"; - default: - assert(0); - return NULL; - } + case SQL_NUMERIC: + case SQL_DECIMAL: + case SQL_FLOAT: + case SQL_REAL: + case SQL_DOUBLE: + return "number"; + case SQL_BINARY: + case SQL_VARBINARY: + case SQL_LONGVARBINARY: + return "binary"; /* !!!!!! nao seria string? */ + case SQL_BIT: + return "boolean"; + default: + assert(0); + return NULL; + } } @@ -176,120 +322,143 @@ static const char *sqltypetolua (const SQLSMALLINT type) { ** Returns: ** 0 if successfull, non-zero otherwise; */ -static int push_column(lua_State *L, int coltypes, const SQLHSTMT hstmt, - SQLUSMALLINT i) { - const char *tname; - char type; - /* get column type from types table */ +static int push_column(lua_State *L, int coltypes, const SQLHSTMT hstmt, + SQLUSMALLINT i) +{ + const char *tname; + char type; + /* get column type from types table */ lua_rawgeti (L, LUA_REGISTRYINDEX, coltypes); lua_rawgeti (L, -1, i); /* typename of the column */ - tname = lua_tostring(L, -1); - if (!tname) + tname = lua_tostring(L, -1); + if (!tname) { return luasql_faildirect(L, "invalid type in table."); - type = tname[1]; - lua_pop(L, 2); /* pops type name and coltypes table */ - - /* deal with data according to type */ - switch (type) { - /* nUmber */ - case 'u': { - double num; - SQLLEN got; - SQLRETURN rc = SQLGetData(hstmt, i, SQL_C_DOUBLE, &num, 0, &got); - if (error(rc)) - return fail(L, hSTMT, hstmt); - if (got == SQL_NULL_DATA) - lua_pushnil(L); - else - lua_pushnumber(L, num); - return 0; + } + type = tname[1]; + lua_pop(L, 2); /* pops type name and coltypes table */ + + /* deal with data according to type */ + switch (type) { + /* nUmber */ + case 'u': { + double num; + SQLLEN got; + SQLRETURN rc = SQLGetData(hstmt, i, SQL_C_DOUBLE, &num, 0, &got); + if (error(rc)) { + return fail(L, hSTMT, hstmt); + } + if (got == SQL_NULL_DATA) { + lua_pushnil(L); + } else { + lua_pushnumber(L, num); } + return 0; + } #if LUA_VERSION_NUM>=503 - /* iNteger */ - case 'n': { - long int num; - SQLLEN got; - SQLRETURN rc = SQLGetData(hstmt, i, SQL_C_SLONG, &num, 0, &got); - if (error(rc)) - return fail(L, hSTMT, hstmt); - if (got == SQL_NULL_DATA) - lua_pushnil(L); - else - lua_pushinteger(L, num); - return 0; + /* iNteger */ + case 'n': { + long int num; + SQLLEN got; + SQLRETURN rc = SQLGetData(hstmt, i, SQL_C_SLONG, &num, 0, &got); + if (error(rc)) { + return fail(L, hSTMT, hstmt); + } + if (got == SQL_NULL_DATA) { + lua_pushnil(L); + } else { + lua_pushinteger(L, num); } + return 0; + } #endif - /* bOol */ - case 'o': { - char b; - SQLLEN got; - SQLRETURN rc = SQLGetData(hstmt, i, SQL_C_BIT, &b, 0, &got); - if (error(rc)) - return fail(L, hSTMT, hstmt); - if (got == SQL_NULL_DATA) - lua_pushnil(L); - else - lua_pushboolean(L, b); + /* bOol */ + case 'o': { + char b; + SQLLEN got; + SQLRETURN rc = SQLGetData(hstmt, i, SQL_C_BIT, &b, 0, &got); + if (error(rc)) { + return fail(L, hSTMT, hstmt); + } + if (got == SQL_NULL_DATA) { + lua_pushnil(L); + } else { + lua_pushboolean(L, b); + } + return 0; + } + /* sTring */ + case 't': + /* bInary */ + case 'i': { + SQLSMALLINT stype = (type == 't') ? SQL_C_CHAR : SQL_C_BINARY; + SQLLEN got; + char *buffer; + luaL_Buffer b; + SQLRETURN rc; + luaL_buffinit(L, &b); + buffer = luaL_prepbuffer(&b); + rc = SQLGetData(hstmt, i, stype, buffer, LUAL_BUFFERSIZE, &got); + if (got == SQL_NULL_DATA) { + lua_pushnil(L); return 0; } - /* sTring */ - case 't': - /* bInary */ - case 'i': { - SQLSMALLINT stype = (type == 't') ? SQL_C_CHAR : SQL_C_BINARY; - SQLLEN got; - char *buffer; - luaL_Buffer b; - SQLRETURN rc; - luaL_buffinit(L, &b); - buffer = luaL_prepbuffer(&b); - rc = SQLGetData(hstmt, i, stype, buffer, LUAL_BUFFERSIZE, &got); - if (got == SQL_NULL_DATA) { - lua_pushnil(L); - return 0; - } - /* concat intermediary chunks */ - while (rc == SQL_SUCCESS_WITH_INFO) { - if (got >= LUAL_BUFFERSIZE || got == SQL_NO_TOTAL) { - got = LUAL_BUFFERSIZE; - /* get rid of null termination in string block */ - if (stype == SQL_C_CHAR) got--; + /* concat intermediary chunks */ + while (rc == SQL_SUCCESS_WITH_INFO) { + if (got >= LUAL_BUFFERSIZE || got == SQL_NO_TOTAL) { + got = LUAL_BUFFERSIZE; + /* get rid of null termination in string block */ + if (stype == SQL_C_CHAR) { + got--; } - luaL_addsize(&b, got); - buffer = luaL_prepbuffer(&b); - rc = SQLGetData(hstmt, i, stype, buffer, - LUAL_BUFFERSIZE, &got); } - /* concat last chunk */ - if (rc == SQL_SUCCESS) { - if (got >= LUAL_BUFFERSIZE || got == SQL_NO_TOTAL) { - got = LUAL_BUFFERSIZE; - /* get rid of null termination in string block */ - if (stype == SQL_C_CHAR) got--; + luaL_addsize(&b, got); + buffer = luaL_prepbuffer(&b); + rc = SQLGetData(hstmt, i, stype, buffer, LUAL_BUFFERSIZE, &got); + } + /* concat last chunk */ + if (rc == SQL_SUCCESS) { + if (got >= LUAL_BUFFERSIZE || got == SQL_NO_TOTAL) { + got = LUAL_BUFFERSIZE; + /* get rid of null termination in string block */ + if (stype == SQL_C_CHAR) { + got--; } - luaL_addsize(&b, got); } - if (rc == SQL_ERROR) return fail(L, hSTMT, hstmt); - /* return everything we got */ - luaL_pushresult(&b); - return 0; + luaL_addsize(&b, got); + } + if (rc == SQL_ERROR) { + return fail(L, hSTMT, hstmt); } - } - return 0; + /* return everything we got */ + luaL_pushresult(&b); + return 0; + } + } + return 0; } /* ** Get another row of the given cursor. */ -static int cur_fetch (lua_State *L) { - cur_data *cur = (cur_data *) getcursor (L); - SQLHSTMT hstmt = cur->hstmt; - int ret; - SQLRETURN rc = SQLFetch(cur->hstmt); - if (rc == SQL_NO_DATA) { - lua_pushnil(L); - return 1; - } else if (error(rc)) return fail(L, hSTMT, hstmt); +static int cur_fetch (lua_State *L) +{ + cur_data *cur = getcursor (L, 1); + SQLHSTMT hstmt = cur->stmt->hstmt; + int ret; + SQLRETURN rc = SQLFetch(hstmt); + if (rc == SQL_NO_DATA) { + /* automatically close cursor when end of resultset is reached */ + if((ret = cur_shut(L, cur)) != 0) { + return ret; + } + + lua_pushnil(L); + return 1; + } else { + if (error(rc)) { + return fail(L, hSTMT, hstmt); + } + } if (lua_istable (L, 2)) { SQLUSMALLINT i; @@ -298,8 +467,9 @@ static int cur_fetch (lua_State *L) { int alpha = strchr (opts, 'a') != NULL; for (i = 1; i <= cur->numcols; i++) { ret = push_column (L, cur->coltypes, hstmt, i); - if (ret) + if (ret) { return ret; + } if (alpha) { lua_rawgeti (L, LUA_REGISTRYINDEX, cur->colnames); lua_rawgeti (L, -1, i); /* gets column name */ @@ -307,21 +477,22 @@ static int cur_fetch (lua_State *L) { lua_rawset (L, 2); /* table[name] = value */ lua_pop (L, 1); /* pops colnames table */ } - if (num) + if (num) { lua_rawseti (L, 2, i); - else + } else { lua_pop (L, 1); /* pops value */ + } } lua_pushvalue (L, 2); return 1; /* return table */ - } - else { + } else { SQLUSMALLINT i; luaL_checkstack (L, cur->numcols, LUASQL_PREFIX"too many columns"); for (i = 1; i <= cur->numcols; i++) { ret = push_column (L, cur->coltypes, hstmt, i); - if (ret) + if (ret) { return ret; + } } return cur->numcols; } @@ -330,40 +501,30 @@ static int cur_fetch (lua_State *L) { /* ** Closes a cursor. */ -static int cur_close (lua_State *L) { - conn_data *conn; +static int cur_close (lua_State *L) +{ + int res; cur_data *cur = (cur_data *) luaL_checkudata (L, 1, LUASQL_CURSOR_ODBC); - SQLRETURN ret; luaL_argcheck (L, cur != NULL, 1, LUASQL_PREFIX"cursor expected"); + if (cur->closed) { lua_pushboolean (L, 0); return 1; } - /* Nullify structure fields. */ - cur->closed = 1; - ret = SQLCloseCursor(cur->hstmt); - if (error(ret)) - return fail(L, hSTMT, cur->hstmt); - ret = SQLFreeHandle(hSTMT, cur->hstmt); - if (error(ret)) - return fail(L, hSTMT, cur->hstmt); - /* Decrement cursor counter on connection object */ - lua_rawgeti (L, LUA_REGISTRYINDEX, cur->conn); - conn = lua_touserdata (L, -1); - conn->cur_counter--; - luaL_unref (L, LUA_REGISTRYINDEX, cur->conn); - luaL_unref (L, LUA_REGISTRYINDEX, cur->colnames); - luaL_unref (L, LUA_REGISTRYINDEX, cur->coltypes); - return pass(L); -} + if((res = cur_shut(L, cur)) != 0) { + return res; + } + return pass(L); +} /* ** Returns the table with column names. */ -static int cur_colnames (lua_State *L) { - cur_data *cur = (cur_data *) getcursor (L); +static int cur_colnames (lua_State *L) +{ + cur_data *cur = getcursor (L, 1); lua_rawgeti (L, LUA_REGISTRYINDEX, cur->colnames); return 1; } @@ -372,8 +533,9 @@ static int cur_colnames (lua_State *L) { /* ** Returns the table with column types. */ -static int cur_coltypes (lua_State *L) { - cur_data *cur = (cur_data *) getcursor (L); +static int cur_coltypes (lua_State *L) +{ + cur_data *cur = getcursor (L, 1); lua_rawgeti (L, LUA_REGISTRYINDEX, cur->coltypes); return 1; } @@ -382,7 +544,8 @@ static int cur_coltypes (lua_State *L) { /* ** Creates two tables with the names and the types of the columns. */ -static int create_colinfo (lua_State *L, cur_data *cur) { +static int create_colinfo (lua_State *L, cur_data *cur) +{ SQLCHAR buffer[256]; SQLSMALLINT namelen, datatype, i; SQLRETURN ret; @@ -393,14 +556,14 @@ static int create_colinfo (lua_State *L, cur_data *cur) { lua_newtable(L); names = lua_gettop (L); for (i = 1; i <= cur->numcols; i++) { - ret = SQLDescribeCol(cur->hstmt, i, buffer, sizeof(buffer), - &namelen, &datatype, NULL, NULL, NULL); + ret = SQLDescribeCol(cur->stmt->hstmt, i, buffer, sizeof(buffer), + &namelen, &datatype, NULL, NULL, NULL); if (ret == SQL_ERROR) { lua_pop(L, 2); return -1; } - lua_pushstring (L, (char*)buffer); + lua_pushstring (L, (char *)buffer); lua_rawseti (L, names, i); lua_pushstring(L, sqltypetolua(datatype)); lua_rawseti (L, types, i); @@ -415,82 +578,292 @@ static int create_colinfo (lua_State *L, cur_data *cur) { /* ** Creates a cursor table and leave it on the top of the stack. */ -static int create_cursor (lua_State *L, int o, conn_data *conn, - const SQLHSTMT hstmt, const SQLSMALLINT numcols) +static int create_cursor (lua_State *L, int stmt_i, stmt_data *stmt, + const SQLSMALLINT numcols) { - cur_data *cur = (cur_data *) lua_newuserdata(L, sizeof(cur_data)); - luasql_setmeta (L, LUASQL_CURSOR_ODBC); + cur_data *cur; + + lock_obj(L, stmt_i, stmt); - conn->cur_counter++; + cur = (cur_data *) lua_newuserdata(L, sizeof(cur_data)); + luasql_setmeta (L, LUASQL_CURSOR_ODBC); /* fill in structure */ cur->closed = 0; - cur->conn = LUA_NOREF; + cur->stmt = stmt; cur->numcols = numcols; cur->colnames = LUA_NOREF; cur->coltypes = LUA_NOREF; - cur->hstmt = hstmt; - lua_pushvalue (L, o); - cur->conn = luaL_ref (L, LUA_REGISTRYINDEX); /* make and store column information table */ if(create_colinfo (L, cur) < 0) { lua_pop(L, 1); - return fail(L, hSTMT, cur->hstmt); + return fail(L, hSTMT, cur->stmt->hstmt); } return 1; } +/* +** Returns the table with statement params. +*/ +static int stmt_paramtypes (lua_State *L) +{ + stmt_data *stmt = getstatement(L, 1); + lua_rawgeti (L, LUA_REGISTRYINDEX, stmt->paramtypes); + return 1; +} + +static int stmt_close(lua_State *L) +{ + stmt_data *stmt = (stmt_data *) luaL_checkudata (L, 1, LUASQL_STATEMENT_ODBC); + luaL_argcheck (L, stmt != NULL, 1, LUASQL_PREFIX"statement expected"); + luaL_argcheck (L, stmt->lock == 0, 1, + LUASQL_PREFIX"there are still open cursors"); + + if (stmt->closed) { + lua_pushboolean (L, 0); + return 1; + } + + if(stmt_shut(L, stmt)) { + return fail(L, hSTMT, stmt->hstmt); + } + + lua_pushboolean(L, 1); + return 1; +} /* ** Closes a connection. */ -static int conn_close (lua_State *L) { +static int conn_close (lua_State *L) +{ SQLRETURN ret; - env_data *env; - conn_data *conn = (conn_data *)luaL_checkudata(L,1,LUASQL_CONNECTION_ODBC); + conn_data *conn = (conn_data *)luaL_checkudata(L,1,LUASQL_CONNECTION_ODBC); luaL_argcheck (L, conn != NULL, 1, LUASQL_PREFIX"connection expected"); if (conn->closed) { lua_pushboolean (L, 0); return 1; } - if (conn->cur_counter > 0) - return luaL_error (L, LUASQL_PREFIX"there are open cursors"); + if (conn->lock > 0) { + return luaL_error (L, LUASQL_PREFIX"there are open statements/cursors"); + } /* Decrement connection counter on environment object */ - lua_rawgeti (L, LUA_REGISTRYINDEX, conn->env); - env = lua_touserdata (L, -1); - env->conn_counter--; + unlock_obj(L, conn->env); + /* Nullify structure fields. */ conn->closed = 1; - luaL_unref (L, LUA_REGISTRYINDEX, conn->env); ret = SQLDisconnect(conn->hdbc); - if (error(ret)) + if (error(ret)) { return fail(L, hDBC, conn->hdbc); + } + ret = SQLFreeHandle(hDBC, conn->hdbc); - if (error(ret)) + if (error(ret)) { return fail(L, hDBC, conn->hdbc); - return pass(L); + } + + return pass(L); } +/* +** Executes the given statement +** Takes: +** * istmt : location of the statement object on the stack +*/ +static int raw_execute(lua_State *L, int istmt) +{ + SQLSMALLINT numcols; + + stmt_data *stmt = getstatement(L, istmt); + + /* execute the statement */ + if (error(SQLExecute(stmt->hstmt))) { + return fail(L, hSTMT, stmt->hstmt); + } + + /* determine the number of result columns */ + if (error(SQLNumResultCols(stmt->hstmt, &numcols))) { + return fail(L, hSTMT, stmt->hstmt); + } + + if (numcols > 0) { + /* if there is a results table (e.g., SELECT) */ + return create_cursor(L, -1, stmt, numcols); + } else { + /* if action has no results (e.g., UPDATE) */ + SQLINTEGER numrows; + if(error(SQLRowCount(stmt->hstmt, &numrows))) { + return fail(L, hSTMT, stmt->hstmt); + } + + luasql_pushinteger(L, numrows); + return 1; + } +} /* -** Executes a SQL statement. -** Returns +** Reads a param table into a statement +*/ +static int raw_readparams(lua_State *L, stmt_data *stmt, int iparams) +{ + static SQLINTEGER cbNull = SQL_NULL_DATA; + SQLSMALLINT i; + param_data *data; + + free_stmt_params(stmt->params, stmt->numparams); + stmt->params = malloc_stmt_params(stmt->numparams); + data = stmt->params; + + for(i=1; i<=stmt->numparams; ++i, ++data) { + /* not using lua_geti for backwards compat with Lua 5.1/LuaJIT */ + lua_pushnumber(L, i); + lua_gettable(L, iparams); + + switch(lua_type(L, -1)) { + case LUA_TNIL: { + lua_pop(L, 1); + + if(error(SQLBindParameter(stmt->hstmt, i, SQL_PARAM_INPUT, SQL_C_DEFAULT, + SQL_DOUBLE, 0, 0, NULL, 0, &cbNull))) { + return fail(L, hSTMT, stmt->hstmt); + } + } + break; + + case LUA_TNUMBER: { + data->buf = malloc(sizeof(double)); + *(double *)data->buf = (double)lua_tonumber(L, -1); + data->len = sizeof(double); + data->type = 0; + + lua_pop(L, 1); + + if(error(SQLBindParameter(stmt->hstmt, i, SQL_PARAM_INPUT, SQL_C_DOUBLE, + SQL_DOUBLE, 0, 0, data->buf, data->len, + &data->type))) { + return fail(L, hSTMT, stmt->hstmt); + } + } + break; + + case LUA_TSTRING: { + const char *str = lua_tostring(L, -1); + size_t len = strlen(str); + + data->buf = malloc(len+1); + memcpy((char *)data->buf, str, len+1); + data->len = len; + data->type = SQL_NTS; + + lua_pop(L, 1); + + if(error(SQLBindParameter(stmt->hstmt, i, SQL_PARAM_INPUT, SQL_C_CHAR, + SQL_CHAR, len, 0, data->buf, data->len, + &data->type))) { + return fail(L, hSTMT, stmt->hstmt); + } + } + break; + + case LUA_TBOOLEAN: { + data->buf = malloc(sizeof(SQLCHAR)); + *(SQLCHAR *)data->buf = (SQLCHAR)lua_toboolean(L, -1); + data->len = 0; + data->type = 0; + + lua_pop(L, 1); + + if(error(SQLBindParameter(stmt->hstmt, i, SQL_PARAM_INPUT, SQL_C_BIT, + SQL_BIT, 0, 0, data->buf, data->len, + &data->type))) { + return fail(L, hSTMT, stmt->hstmt); + } + } + break; + + default: + lua_pop(L, 1); + return luasql_faildirect(L, "unsupported parameter type"); + break; + } + } + + return 0; +} + +/* +** Executes the prepared statement +** Lua Input: [params] +** params: A table of parameters to use in the statement +** Lua Returns ** cursor object: if there are results or ** row count: number of rows affected by statement if no results */ -static int conn_execute (lua_State *L) { - conn_data *conn = (conn_data *) getconnection (L); - SQLCHAR *statement = (SQLCHAR*)luaL_checkstring(L, 2); +static int stmt_execute(lua_State *L) +{ + /* any parameters to use */ + if(lua_gettop(L) > 1) { + stmt_data *stmt = getstatement(L, 1); + int res = raw_readparams(L, stmt, 2); + if(res != 0) { + return res; + } + } + + return raw_execute(L, 1); +} + +/* +** creates a table of parameter types (maybe) +** Returns: the reference key of the table (noref if unable to build the table) +*/ +static int desc_params(lua_State *L, stmt_data *stmt) +{ + SQLSMALLINT i; + + lua_newtable(L); + for(i=1; i <= stmt->numparams; ++i) { + SQLSMALLINT type, digits, nullable; + SQLULEN len; + + /* fun fact: most ODBC drivers don't support this function (MS Access for + example), so we can't get a param type table */ + if(error(SQLDescribeParam(stmt->hstmt, i, &type, &len, &digits, &nullable))) { + lua_pop(L,1); + return LUA_NOREF; + } + + lua_pushstring(L, sqltypetolua(type)); + lua_rawseti(L, -2, i); + } + + return luaL_ref(L, LUA_REGISTRYINDEX); +} + +/* +** Prepares a statement +** Lua Input: sql +** sql: The SQL statement to prepare +** Lua Returns: +** Statement object +*/ +static int conn_prepare(lua_State *L) +{ + conn_data *conn = getconnection(L, 1); + SQLCHAR *statement = (SQLCHAR *)luaL_checkstring(L, 2); SQLHDBC hdbc = conn->hdbc; SQLHSTMT hstmt; - SQLSMALLINT numcols; SQLRETURN ret; + + stmt_data *stmt; + ret = SQLAllocHandle(hSTMT, hdbc, &hstmt); - if (error(ret)) + if (error(ret)) { return fail(L, hDBC, hdbc); + } ret = SQLPrepare(hstmt, statement, SQL_NTS); if (error(ret)) { @@ -499,134 +872,283 @@ static int conn_execute (lua_State *L) { return ret; } - /* execute the statement */ - ret = SQLExecute (hstmt); - if (error(ret)) { - ret = fail(L, hSTMT, hstmt); + stmt = (stmt_data *)lua_newuserdata(L, sizeof(stmt_data)); + memset(stmt, 0, sizeof(stmt_data)); + + stmt->closed = 0; + stmt->lock = 0; + stmt->hidden = 0; + stmt->conn = conn; + stmt->hstmt = hstmt; + if(error(SQLNumParams(hstmt, &stmt->numparams))) { + int res; + lua_pop(L, 1); + res = fail(L, hSTMT, hstmt); SQLFreeHandle(hSTMT, hstmt); - return ret; + return res; } + stmt->paramtypes = desc_params(L, stmt); + stmt->params = NULL; - /* determine the number of results */ - ret = SQLNumResultCols (hstmt, &numcols); - if (error(ret)) { - ret = fail(L, hSTMT, hstmt); - SQLFreeHandle(hSTMT, hstmt); - return ret; + /* activate statement object */ + luasql_setmeta(L, LUASQL_STATEMENT_ODBC); + lock_obj(L, 1, conn); + + return 1; +} + +/* +** Executes a SQL statement directly +** Lua Input: sql, [params] +** sql: The SQL statement to execute +** params: A table of parameters to use in the SQL +** Lua Returns +** cursor object: if there are results or +** row count: number of rows affected by statement if no results +*/ +static int conn_execute (lua_State *L) +{ + stmt_data *stmt; + int res, istmt; + int ltop = lua_gettop(L); + + /* prepare statement */ + if((res = conn_prepare(L)) != 1) { + return res; } + istmt = lua_gettop(L); + stmt = getstatement(L, istmt); - if (numcols > 0) - /* if there is a results table (e.g., SELECT) */ - return create_cursor (L, 1, conn, hstmt, numcols); - else { - /* if action has no results (e.g., UPDATE) */ - SQLLEN numrows; - ret = SQLRowCount(hstmt, &numrows); - if (error(ret)) { - ret = fail(L, hSTMT, hstmt); - SQLFreeHandle(hSTMT, hstmt); - return ret; + /* because this is a direct execute, statement is hidden from user */ + stmt->hidden = 1; + + /* do we have any parameters */ + if(ltop > 2) { + if((res = raw_readparams(L, stmt, 3)) != 0) { + return res; } - lua_pushnumber(L, numrows); - SQLFreeHandle(hSTMT, hstmt); - return 1; } + + /* do it */ + res = raw_execute(L, istmt); + + /* anything but a cursor, close the statement directly */ + if(!lua_isuserdata(L, -res)) { + stmt_shut(L, stmt); + } + + /* tidy up */ + lua_remove(L, istmt); + + return res; } /* -** Rolls back a transaction. +** Commits a transaction. */ -static int conn_commit (lua_State *L) { - conn_data *conn = (conn_data *) getconnection (L); +static int conn_commit (lua_State *L) +{ + conn_data *conn = getconnection (L, 1); SQLRETURN ret = SQLEndTran(hDBC, conn->hdbc, SQL_COMMIT); - if (error(ret)) + if (error(ret)) { return fail(L, hSTMT, conn->hdbc); - else + } else { return pass(L); + } } /* -** Rollback the current transaction. +** Rollback the current transaction. */ -static int conn_rollback (lua_State *L) { - conn_data *conn = (conn_data *) getconnection (L); +static int conn_rollback (lua_State *L) +{ + conn_data *conn = getconnection (L, 1); SQLRETURN ret = SQLEndTran(hDBC, conn->hdbc, SQL_ROLLBACK); - if (error(ret)) + if (error(ret)) { return fail(L, hSTMT, conn->hdbc); - else + } else { return pass(L); + } } /* ** Sets the auto commit mode */ -static int conn_setautocommit (lua_State *L) { - conn_data *conn = (conn_data *) getconnection (L); +static int conn_setautocommit (lua_State *L) +{ + conn_data *conn = getconnection (L, 1); SQLRETURN ret; if (lua_toboolean (L, 2)) { ret = SQLSetConnectAttr(conn->hdbc, SQL_ATTR_AUTOCOMMIT, - (SQLPOINTER) SQL_AUTOCOMMIT_ON, 0); + (SQLPOINTER) SQL_AUTOCOMMIT_ON, 0); } else { ret = SQLSetConnectAttr(conn->hdbc, SQL_ATTR_AUTOCOMMIT, - (SQLPOINTER) SQL_AUTOCOMMIT_OFF, 0); + (SQLPOINTER) SQL_AUTOCOMMIT_OFF, 0); } - if (error(ret)) + if (error(ret)) { return fail(L, hSTMT, conn->hdbc); - else + } else { return pass(L); + } } - /* ** Create a new Connection object and push it on top of the stack. */ -static int create_connection (lua_State *L, int o, env_data *env, SQLHDBC hdbc) { - conn_data *conn = (conn_data *) lua_newuserdata(L, sizeof(conn_data)); +static int create_connection (lua_State *L, int o, SQLHDBC hdbc) +{ + env_data *env = getenvironment(L, 1); + conn_data *conn = (conn_data *)lua_newuserdata(L, sizeof(conn_data)); + /* set auto commit mode */ - SQLRETURN ret = SQLSetConnectAttr(hdbc, SQL_ATTR_AUTOCOMMIT, - (SQLPOINTER) SQL_AUTOCOMMIT_ON, 0); - if (error(ret)) + SQLRETURN ret = SQLSetConnectAttr(hdbc, SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER) SQL_AUTOCOMMIT_ON, 0); + if (error(ret)) { return fail(L, hDBC, hdbc); + } luasql_setmeta (L, LUASQL_CONNECTION_ODBC); /* fill in structure */ conn->closed = 0; - conn->cur_counter = 0; - conn->env = LUA_NOREF; + conn->lock = 0; + conn->env = env; conn->hdbc = hdbc; - lua_pushvalue (L, o); - conn->env = luaL_ref (L, LUA_REGISTRYINDEX); - env->conn_counter++; + + lock_obj(L, 1, env); + return 1; } +/* +** Uses a DSN string to connect to a ODBC source dynamically +** Lua Input: { +** dsn = , +** } +** Lua Returns: +** connection object if successfull +** nil and error message otherwise. +*/ +static int env_table_connect_DSN (lua_State *L) +{ + env_data *env = getenvironment (L, 1); + SQLCHAR *sourcename = (SQLCHAR *)luasql_table_optstring(L, 2, "dsn", NULL); + + SQLHDBC hdbc; + SQLCHAR sqlOutBuf[4097]; + SQLSMALLINT sqlOutLen; + SQLRETURN ret; + + ret = SQLSetEnvAttr (env->henv, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0); + if (error(ret)) { + return luasql_faildirect (L, "error setting SQL version."); + } + + /* tries to allocate connection handle */ + ret = SQLAllocHandle (hDBC, env->henv, &hdbc); + if (error(ret)) { + return luasql_faildirect (L, "connection allocation error."); + } + + /* tries to connect handle */ + ret = SQLDriverConnect (hdbc, NULL, sourcename, SQL_NTS, sqlOutBuf, 4096, + &sqlOutLen, SQL_DRIVER_NOPROMPT); + if (error(ret)) { + ret = fail(L, hDBC, hdbc); + SQLFreeHandle(hDBC, hdbc); + return ret; + } + + /* success, return connection object */ + ret = create_connection (L, 1, hdbc); + if(ret == 1) { + /* Add the sqlOutBuf string to the results, for diagnostics */ + lua_pushlstring(L, (char *)sqlOutBuf, sqlOutLen); + return 2; + } + + return ret; +} /* -** Creates and returns a connection object -** Lua Input: source [, user [, pass]] +** Reforms old connection style to new one +** Lua Input: source, [user, [pass]] ** source: data source ** user, pass: data source authentication information ** Lua Returns: +** new connection details table +*/ +static void env_connect_fix_old (lua_State *L) +{ + static const char *const opt_names[] = { + "source", + "user", + "password", + NULL + }; + int i, t = lua_gettop(L)-1; + + lua_newtable(L); + lua_insert(L, 2); + + for(i=0; opt_names[i] != NULL && i, +** user = , +** password = , +** or +** dsn = , +** } +** Lua Returns: ** connection object if successfull ** nil and error message otherwise. */ -static int env_connect (lua_State *L) { - env_data *env = (env_data *) getenvironment (L); - SQLCHAR *sourcename = (SQLCHAR*)luaL_checkstring (L, 2); - SQLCHAR *username = (SQLCHAR*)luaL_optstring (L, 3, NULL); - SQLCHAR *password = (SQLCHAR*)luaL_optstring (L, 4, NULL); - SQLHDBC hdbc; - SQLRETURN ret; +static int env_connect (lua_State *L) +{ + env_data *env = getenvironment (L, 1); + SQLCHAR *sourcename = NULL; + SQLCHAR *username = NULL; + SQLCHAR *password = NULL; + SQLHDBC hdbc = NULL; + SQLRETURN ret = 0; + + if(lua_gettop(L) < 2) { + return luasql_faildirect(L, "No connection details provided"); + } + + if(!lua_istable(L, 2)) { + env_connect_fix_old(L); + } + + /* check for the custom DSN connection string */ + if(luasql_table_optstring(L, 2, "dsn", NULL) != NULL) { + return env_table_connect_DSN(L); + } + + /* get the standard connection details */ + sourcename = (SQLCHAR *)luasql_table_optstring(L, 2, "source", NULL); + username = (SQLCHAR *)luasql_table_optstring(L, 2, "user", NULL); + password = (SQLCHAR *)luasql_table_optstring(L, 2, "password", NULL); /* tries to allocate connection handle */ ret = SQLAllocHandle (hDBC, env->henv, &hdbc); - if (error(ret)) + if (error(ret)) { return luasql_faildirect (L, "connection allocation error."); + } /* tries to connect handle */ - ret = SQLConnect (hdbc, sourcename, SQL_NTS, - username, SQL_NTS, password, SQL_NTS); + ret = SQLConnect (hdbc, sourcename, SQL_NTS, username, SQL_NTS, password, + SQL_NTS); if (error(ret)) { ret = fail(L, hDBC, hdbc); SQLFreeHandle(hDBC, hdbc); @@ -634,13 +1156,102 @@ static int env_connect (lua_State *L) { } /* success, return connection object */ - return create_connection (L, 1, env, hdbc); + return create_connection (L, 1, hdbc); +} + +static int split_strnull_attr_list(lua_State *L, const char* str) +{ + const char *end = str; + const char *equ = str; + + lua_newtable(L); + + while(*str != '\0') { + while(*end != '\0') { + if(*end == '=') { + equ = end; + } + ++end; + } + + lua_pushlstring(L, str, equ-str); + lua_pushstring(L, equ + 1); + lua_settable(L, -3); + + str = ++end; + } + + return 1; +} + +/* +** Lists avilable ODBC drivers +*/ +static int env_drivers (lua_State *L) +{ + env_data *env = getenvironment(L, 1); + SQLCHAR *driver = NULL; + SQLCHAR *attr = NULL; + SQLSMALLINT attr_max = 5, driver_max = 5; + SQLSMALLINT attr_req, driver_req; + SQLRETURN ret; + int i = 1; + + /* pre-flight to get max sizes */ + if(error(ret = SQLDrivers(env->henv, SQL_FETCH_FIRST, + NULL, driver_max, &driver_req, + NULL, attr_max, &attr_req) ) ) + { + return fail(L, hENV, env->henv); + } + + do { + if(driver_req > driver_max) { + driver_max = driver_req; + } + if(attr_req > attr_max) { + attr_max = attr_req; + } + } while(SQL_SUCCEEDED(ret = SQLDrivers(env->henv, SQL_FETCH_NEXT, + NULL, driver_max, &driver_req, + NULL, attr_max, &attr_req) )); + if(error(ret)) { + return fail(L, hENV, env->henv); + } + + /* alloc the buffers and pull in the data */ + driver = (SQLCHAR *)malloc(++driver_max); + attr = (SQLCHAR *)malloc(++attr_max); + + lua_newtable(L); + while(SQL_SUCCEEDED(ret = SQLDrivers(env->henv, SQL_FETCH_NEXT, + driver, driver_max, &driver_req, + attr, attr_max, &attr_req) ) ) + { + lua_newtable(L); + lua_pushstring(L, (const char *)driver); + lua_setfield(L, -2, "driver"); + split_strnull_attr_list(L, (const char *)attr); + lua_setfield(L, -2, "attr"); + + lua_rawseti(L, -2, i++); + } + + free(driver); + free(attr); + + if(error(ret)) { + return fail(L, hENV, env->henv); + } + + return 1; } /* ** Closes an environment object */ -static int env_close (lua_State *L) { +static int env_close (lua_State *L) +{ SQLRETURN ret; env_data *env = (env_data *)luaL_checkudata(L, 1, LUASQL_ENVIRONMENT_ODBC); luaL_argcheck (L, env != NULL, 1, LUASQL_PREFIX"environment expected"); @@ -648,8 +1259,9 @@ static int env_close (lua_State *L) { lua_pushboolean (L, 0); return 1; } - if (env->conn_counter > 0) + if (env->lock > 0) { return luaL_error (L, LUASQL_PREFIX"there are open connections"); + } env->closed = 1; ret = SQLFreeHandle (hENV, env->henv); @@ -664,22 +1276,32 @@ static int env_close (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_close}, /* Should this method be changed? */ {"close", env_close}, {"connect", env_connect}, + {"drivers", env_drivers}, {NULL, NULL}, }; struct luaL_Reg connection_methods[] = { {"__gc", conn_close}, /* Should this method be changed? */ {"close", conn_close}, + {"prepare", conn_prepare}, {"execute", conn_execute}, {"commit", conn_commit}, {"rollback", conn_rollback}, {"setautocommit", conn_setautocommit}, {NULL, NULL}, }; + struct luaL_Reg statement_methods[] = { + {"__gc", stmt_close}, /* Should this method be changed? */ + {"close", stmt_close}, + {"execute", stmt_execute}, + {"getparamtypes", stmt_paramtypes}, + {NULL, NULL}, + }; struct luaL_Reg cursor_methods[] = { {"__gc", cur_close}, /* Should this method be changed? */ {"close", cur_close}, @@ -690,34 +1312,36 @@ static void create_metatables (lua_State *L) { }; luasql_createmeta (L, LUASQL_ENVIRONMENT_ODBC, environment_methods); luasql_createmeta (L, LUASQL_CONNECTION_ODBC, connection_methods); + luasql_createmeta (L, LUASQL_STATEMENT_ODBC, statement_methods); luasql_createmeta (L, LUASQL_CURSOR_ODBC, cursor_methods); - lua_pop (L, 3); + lua_pop (L, 4); } /* ** Creates an Environment and returns it. */ -static int create_environment (lua_State *L) { +static int create_environment (lua_State *L) +{ env_data *env; SQLHENV henv; SQLRETURN ret = SQLAllocHandle(hENV, SQL_NULL_HANDLE, &henv); - if (error(ret)) + if (error(ret)) { return luasql_faildirect(L, "error creating environment."); + } - ret = SQLSetEnvAttr (henv, SQL_ATTR_ODBC_VERSION, - (void*)SQL_OV_ODBC3, 0); + ret = SQLSetEnvAttr (henv, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0); if (error(ret)) { ret = luasql_faildirect (L, "error setting SQL version."); SQLFreeHandle (hENV, henv); return ret; - } + } env = (env_data *)lua_newuserdata (L, sizeof (env_data)); luasql_setmeta (L, LUASQL_ENVIRONMENT_ODBC); /* fill in structure */ env->closed = 0; - env->conn_counter = 0; + env->lock = 0; env->henv = henv; return 1; } @@ -727,14 +1351,15 @@ static int create_environment (lua_State *L) { ** Creates the metatables for the objects and registers the ** driver open method. */ -LUASQL_API int luaopen_luasql_odbc (lua_State *L) { +LUASQL_API int luaopen_luasql_odbc (lua_State *L) +{ struct luaL_Reg driver[] = { {"odbc", create_environment}, {NULL, NULL}, }; + create_metatables (L); - lua_newtable (L); - luaL_setfuncs (L, driver, 0); - luasql_set_info (L); + luasql_reg_driver(L, driver); + return 1; -} +} diff --git a/src/ls_postgres.c b/src/ls_postgres.c index 3633c8a..0d211e5 100644 --- a/src/ls_postgres.c +++ b/src/ls_postgres.c @@ -602,7 +602,7 @@ LUASQL_API int luaopen_luasql_postgres (lua_State *L) { {NULL, NULL}, }; create_metatables (L); - lua_newtable (L); + luasql_find_driver_table (L); luaL_setfuncs (L, driver, 0); luasql_set_info (L); return 1; diff --git a/src/ls_sqlite.c b/src/ls_sqlite.c index c7ef8a8..36e1824 100644 --- a/src/ls_sqlite.c +++ b/src/ls_sqlite.c @@ -21,24 +21,21 @@ #define LUASQL_CONNECTION_SQLITE "SQLite connection" #define LUASQL_CURSOR_SQLITE "SQLite cursor" -typedef struct -{ - short closed; +typedef struct { + short closed; } env_data; -typedef struct -{ +typedef struct { short closed; int env; /* reference to environment */ short auto_commit; /* 0 for manual commit */ - unsigned int cur_counter; + unsigned int cur_counter; sqlite *sql_conn; } conn_data; -typedef struct -{ +typedef struct { short closed; int conn; /* reference to connection */ int numcols; /* number of columns */ @@ -52,7 +49,8 @@ LUASQL_API int luaopen_luasql_sqlite(lua_State *L); /* ** Check for valid environment. */ -static env_data *getenvironment(lua_State *L) { +static env_data *getenvironment(lua_State *L) +{ env_data *env = (env_data *)luaL_checkudata(L, 1, LUASQL_ENVIRONMENT_SQLITE); luaL_argcheck(L, env != NULL, 1, LUASQL_PREFIX"environment expected"); luaL_argcheck(L, !env->closed, 1, LUASQL_PREFIX"environment is closed"); @@ -63,7 +61,8 @@ static env_data *getenvironment(lua_State *L) { /* ** Check for valid connection. */ -static conn_data *getconnection(lua_State *L) { +static conn_data *getconnection(lua_State *L) +{ conn_data *conn = (conn_data *)luaL_checkudata (L, 1, LUASQL_CONNECTION_SQLITE); luaL_argcheck(L, conn != NULL, 1, LUASQL_PREFIX"connection expected"); luaL_argcheck(L, !conn->closed, 1, LUASQL_PREFIX"connection is closed"); @@ -74,7 +73,8 @@ static conn_data *getconnection(lua_State *L) { /* ** Check for valid cursor. */ -static cur_data *getcursor(lua_State *L) { +static cur_data *getcursor(lua_State *L) +{ cur_data *cur = (cur_data *)luaL_checkudata (L, 1, LUASQL_CURSOR_SQLITE); luaL_argcheck(L, cur != NULL, 1, LUASQL_PREFIX"cursor expected"); luaL_argcheck(L, !cur->closed, 1, LUASQL_PREFIX"cursor is closed"); @@ -87,19 +87,19 @@ static cur_data *getcursor(lua_State *L) { */ static void cur_nullify(lua_State *L, cur_data *cur) { - conn_data *conn; - - /* Nullify structure fields. */ - cur->closed = 1; - cur->sql_vm = NULL; - /* Decrement cursor counter on connection object */ - lua_rawgeti (L, LUA_REGISTRYINDEX, cur->conn); - conn = lua_touserdata (L, -1); - conn->cur_counter--; - - luaL_unref(L, LUA_REGISTRYINDEX, cur->conn); - luaL_unref(L, LUA_REGISTRYINDEX, cur->colnames); - luaL_unref(L, LUA_REGISTRYINDEX, cur->coltypes); + conn_data *conn; + + /* Nullify structure fields. */ + cur->closed = 1; + cur->sql_vm = NULL; + /* Decrement cursor counter on connection object */ + lua_rawgeti (L, LUA_REGISTRYINDEX, cur->conn); + conn = lua_touserdata (L, -1); + conn->cur_counter--; + + luaL_unref(L, LUA_REGISTRYINDEX, cur->conn); + luaL_unref(L, LUA_REGISTRYINDEX, cur->colnames); + luaL_unref(L, LUA_REGISTRYINDEX, cur->coltypes); } @@ -107,80 +107,78 @@ static void cur_nullify(lua_State *L, cur_data *cur) ** Finalizes the vm ** Return nil + errmsg or nil in case of sucess */ -static int finalize(lua_State *L, cur_data *cur) { - char *errmsg; - if (sqlite_finalize(cur->sql_vm, &errmsg) != SQLITE_OK) - { +static int finalize(lua_State *L, cur_data *cur) +{ + char *errmsg; + if (sqlite_finalize(cur->sql_vm, &errmsg) != SQLITE_OK) { cur_nullify(L, cur); - lua_pushnil(L); - lua_pushliteral(L, LUASQL_PREFIX); - lua_pushstring(L, errmsg); - sqlite_freemem(errmsg); - lua_concat(L, 2); - return 2; - } + lua_pushnil(L); + lua_pushliteral(L, LUASQL_PREFIX); + lua_pushstring(L, errmsg); + sqlite_freemem(errmsg); + lua_concat(L, 2); + return 2; + } cur_nullify(L, cur); lua_pushnil(L); - return 1; + return 1; } /* ** Get another row of the given cursor. */ -static int cur_fetch (lua_State *L) { +static int cur_fetch (lua_State *L) +{ cur_data *cur = getcursor(L); - sqlite_vm *vm = cur->sql_vm; - const char **row = NULL; - int res; + sqlite_vm *vm = cur->sql_vm; + const char **row = NULL; + int res; - if (vm == NULL) - return 0; + if (vm == NULL) { + return 0; + } - res = sqlite_step(vm, NULL, &row, NULL); + res = sqlite_step(vm, NULL, &row, NULL); - /* no more results? */ - if (res == SQLITE_DONE) - return finalize(L, cur); + /* no more results? */ + if (res == SQLITE_DONE) { + return finalize(L, cur); + } - if (res != SQLITE_ROW) - return finalize(L, cur); + if (res != SQLITE_ROW) { + return finalize(L, cur); + } - if (lua_istable (L, 2)) - { + if (lua_istable (L, 2)) { int i; const char *opts = luaL_optstring(L, 3, "n"); - if (strchr(opts, 'n') != NULL) - { + if (strchr(opts, 'n') != NULL) { /* Copy values to numerical indices */ - for (i = 0; i < cur->numcols;) - { - lua_pushstring(L, row[i]); + for (i = 0; i < cur->numcols;) { + lua_pushstring(L, row[i]); lua_rawseti(L, 2, ++i); } - } - if (strchr(opts, 'a') != NULL) - { + } + if (strchr(opts, 'a') != NULL) { /* Copy values to alphanumerical indices */ - lua_rawgeti(L, LUA_REGISTRYINDEX, cur->colnames); + lua_rawgeti(L, LUA_REGISTRYINDEX, cur->colnames); - for (i = 0; i < cur->numcols; i++) - { + for (i = 0; i < cur->numcols; i++) { lua_rawgeti(L, -1, i+1); - lua_pushstring(L, row[i]); + lua_pushstring(L, row[i]); lua_rawset (L, 2); } - } + } lua_pushvalue(L, 2); return 1; /* return table */ - } - else - { + } else { int i; luaL_checkstack (L, cur->numcols, LUASQL_PREFIX"too many columns"); - for (i = 0; i < cur->numcols; ++i) + for (i = 0; i < cur->numcols; ++i) { lua_pushstring(L, row[i]); + } return cur->numcols; /* return #numcols values */ } } @@ -191,13 +189,12 @@ static int cur_fetch (lua_State *L) { */ static int cur_gc(lua_State *L) { - cur_data *cur = (cur_data *)luaL_checkudata(L, 1, LUASQL_CURSOR_SQLITE); - if (cur != NULL && !(cur->closed)) - { - sqlite_finalize(cur->sql_vm, NULL); - cur_nullify(L, cur); - } - return 0; + cur_data *cur = (cur_data *)luaL_checkudata(L, 1, LUASQL_CURSOR_SQLITE); + if (cur != NULL && !(cur->closed)) { + sqlite_finalize(cur->sql_vm, NULL); + cur_nullify(L, cur); + } + return 0; } @@ -225,8 +222,8 @@ static int cur_close(lua_State *L) */ static int cur_getcolnames(lua_State *L) { - cur_data *cur = getcursor(L); - lua_rawgeti(L, LUA_REGISTRYINDEX, cur->colnames); + cur_data *cur = getcursor(L); + lua_rawgeti(L, LUA_REGISTRYINDEX, cur->colnames); return 1; } @@ -236,8 +233,8 @@ static int cur_getcolnames(lua_State *L) */ static int cur_getcoltypes(lua_State *L) { - cur_data *cur = getcursor(L); - lua_rawgeti(L, LUA_REGISTRYINDEX, cur->coltypes); + cur_data *cur = getcursor(L); + lua_rawgeti(L, LUA_REGISTRYINDEX, cur->coltypes); return 1; } @@ -247,11 +244,11 @@ static int cur_getcoltypes(lua_State *L) */ /* static int create_cursor(lua_State *L, int conn, sqlite_vm *sql_vm, int numcols, const char **row, const char **col_info)*/ -static int create_cursor(lua_State *L, int o, conn_data *conn, - sqlite_vm *sql_vm, int numcols, const char **col_info) +static int create_cursor(lua_State *L, int o, conn_data *conn, + sqlite_vm *sql_vm, int numcols, const char **col_info) { - int i; - cur_data *cur = (cur_data*)lua_newuserdata(L, sizeof(cur_data)); + int i; + cur_data *cur = (cur_data *)lua_newuserdata(L, sizeof(cur_data)); luasql_setmeta (L, LUASQL_CURSOR_SQLITE); /* increment cursor count for the connection creating this cursor */ @@ -265,26 +262,24 @@ static int create_cursor(lua_State *L, int o, conn_data *conn, cur->coltypes = LUA_NOREF; cur->sql_vm = sql_vm; - lua_pushvalue(L, o); + lua_pushvalue(L, o); cur->conn = luaL_ref(L, LUA_REGISTRYINDEX); - /* create table with column names */ - lua_newtable(L); - for (i = 0; i < numcols;) - { - lua_pushstring(L, col_info[i]); - lua_rawseti(L, -2, ++i); - } - cur->colnames = luaL_ref(L, LUA_REGISTRYINDEX); - - /* create table with column types */ - lua_newtable(L); - for (i = 0; i < numcols;) - { - lua_pushstring(L, col_info[numcols+i]); - lua_rawseti(L, -2, ++i); - } - cur->coltypes = luaL_ref(L, LUA_REGISTRYINDEX); + /* create table with column names */ + lua_newtable(L); + for (i = 0; i < numcols;) { + lua_pushstring(L, col_info[i]); + lua_rawseti(L, -2, ++i); + } + cur->colnames = luaL_ref(L, LUA_REGISTRYINDEX); + + /* create table with column types */ + lua_newtable(L); + for (i = 0; i < numcols;) { + lua_pushstring(L, col_info[numcols+i]); + lua_rawseti(L, -2, ++i); + } + cur->coltypes = luaL_ref(L, LUA_REGISTRYINDEX); return 1; } @@ -295,18 +290,18 @@ static int create_cursor(lua_State *L, int o, conn_data *conn, */ static int conn_gc(lua_State *L) { - conn_data *conn = (conn_data *)luaL_checkudata(L, 1, LUASQL_CONNECTION_SQLITE); - if (conn != NULL && !(conn->closed)) - { - if (conn->cur_counter > 0) - return luaL_error (L, LUASQL_PREFIX"there are open cursors"); - - /* Nullify structure fields. */ - conn->closed = 1; - luaL_unref(L, LUA_REGISTRYINDEX, conn->env); - sqlite_close(conn->sql_conn); - } - return 0; + conn_data *conn = (conn_data *)luaL_checkudata(L, 1, LUASQL_CONNECTION_SQLITE); + if (conn != NULL && !(conn->closed)) { + if (conn->cur_counter > 0) { + return luaL_error (L, LUASQL_PREFIX"there are open cursors"); + } + + /* Nullify structure fields. */ + conn->closed = 1; + luaL_unref(L, LUA_REGISTRYINDEX, conn->env); + sqlite_close(conn->sql_conn); + } + return 0; } @@ -336,49 +331,46 @@ static int conn_execute(lua_State *L) { conn_data *conn = getconnection(L); const char *statement = luaL_checkstring(L, 2); - int res; - sqlite_vm *vm; - char *errmsg; - int numcols; - const char **col_info; - - res = sqlite_compile(conn->sql_conn, statement, NULL, &vm, &errmsg); - if (res != SQLITE_OK) - { - lua_pushnil(L); - lua_pushliteral(L, LUASQL_PREFIX); - lua_pushstring(L, errmsg); - sqlite_freemem(errmsg); - lua_concat(L, 2); - return 2; - } - - /* process first result to retrive query information and type */ - res = sqlite_step(vm, &numcols, NULL, &col_info); - - /* real query? if empty, must have numcols!=0 */ - if ((res == SQLITE_ROW) || ((res == SQLITE_DONE) && numcols)) - { + int res; + sqlite_vm *vm; + char *errmsg; + int numcols; + const char **col_info; + + res = sqlite_compile(conn->sql_conn, statement, NULL, &vm, &errmsg); + if (res != SQLITE_OK) { + lua_pushnil(L); + lua_pushliteral(L, LUASQL_PREFIX); + lua_pushstring(L, errmsg); + sqlite_freemem(errmsg); + lua_concat(L, 2); + return 2; + } + + /* process first result to retrive query information and type */ + res = sqlite_step(vm, &numcols, NULL, &col_info); + + /* real query? if empty, must have numcols!=0 */ + if ((res == SQLITE_ROW) || ((res == SQLITE_DONE) && numcols)) { sqlite_reset(vm, NULL); return create_cursor(L, 1, conn, vm, numcols, col_info); } - if (res == SQLITE_DONE) /* and numcols==0, INSERT,UPDATE,DELETE statement */ - { - sqlite_finalize(vm, NULL); - /* return number of columns changed */ - lua_pushnumber(L, sqlite_changes(conn->sql_conn)); - return 1; - } - - /* error */ - sqlite_finalize(vm, &errmsg); - lua_pushnil(L); - lua_pushliteral(L, LUASQL_PREFIX); - lua_pushstring(L, errmsg); - sqlite_freemem(errmsg); - lua_concat(L, 2); - return 2; + if (res == SQLITE_DONE) { /* and numcols==0, INSERT,UPDATE,DELETE statement */ + sqlite_finalize(vm, NULL); + /* return number of columns changed */ + lua_pushnumber(L, sqlite_changes(conn->sql_conn)); + return 1; + } + + /* error */ + sqlite_finalize(vm, &errmsg); + lua_pushnil(L); + lua_pushliteral(L, LUASQL_PREFIX); + lua_pushstring(L, errmsg); + sqlite_freemem(errmsg); + lua_concat(L, 2); + return 2; } @@ -387,24 +379,25 @@ static int conn_execute(lua_State *L) */ static int conn_commit(lua_State *L) { - char *errmsg; + char *errmsg; conn_data *conn = getconnection(L); int res; - const char *sql = "COMMIT"; + const char *sql = "COMMIT"; - if (conn->auto_commit == 0) sql = "COMMIT;BEGIN"; + if (conn->auto_commit == 0) { + sql = "COMMIT;BEGIN"; + } - res = sqlite_exec(conn->sql_conn, sql, NULL, NULL, &errmsg); - if (res != SQLITE_OK) - { + res = sqlite_exec(conn->sql_conn, sql, NULL, NULL, &errmsg); + if (res != SQLITE_OK) { lua_pushnil(L); - lua_pushliteral(L, LUASQL_PREFIX); - lua_pushstring(L, errmsg); - sqlite_freemem(errmsg); - lua_concat(L, 2); - return 2; - } - lua_pushboolean(L, 1); + lua_pushliteral(L, LUASQL_PREFIX); + lua_pushstring(L, errmsg); + sqlite_freemem(errmsg); + lua_concat(L, 2); + return 2; + } + lua_pushboolean(L, 1); return 1; } @@ -414,24 +407,25 @@ static int conn_commit(lua_State *L) */ static int conn_rollback(lua_State *L) { - char *errmsg; + char *errmsg; conn_data *conn = getconnection(L); int res; - const char *sql = "ROLLBACK"; + const char *sql = "ROLLBACK"; - if (conn->auto_commit == 0) sql = "ROLLBACK;BEGIN"; + if (conn->auto_commit == 0) { + sql = "ROLLBACK;BEGIN"; + } - res = sqlite_exec(conn->sql_conn, sql, NULL, NULL, &errmsg); - if (res != SQLITE_OK) - { + res = sqlite_exec(conn->sql_conn, sql, NULL, NULL, &errmsg); + if (res != SQLITE_OK) { lua_pushnil(L); - lua_pushliteral(L, LUASQL_PREFIX); - lua_pushstring(L, errmsg); - sqlite_freemem(errmsg); - lua_concat(L, 2); - return 2; - } - lua_pushboolean(L, 1); + lua_pushliteral(L, LUASQL_PREFIX); + lua_pushstring(L, errmsg); + sqlite_freemem(errmsg); + lua_concat(L, 2); + return 2; + } + lua_pushboolean(L, 1); return 1; } @@ -444,26 +438,22 @@ static int conn_rollback(lua_State *L) static int conn_setautocommit(lua_State *L) { conn_data *conn = getconnection(L); - if (lua_toboolean(L, 2)) - { + if (lua_toboolean(L, 2)) { conn->auto_commit = 1; - /* undo active transaction - ignore errors */ - sqlite_exec(conn->sql_conn, "ROLLBACK", NULL, NULL, NULL); - } - else - { - char *errmsg; - int res; + /* undo active transaction - ignore errors */ + sqlite_exec(conn->sql_conn, "ROLLBACK", NULL, NULL, NULL); + } else { + char *errmsg; + int res; conn->auto_commit = 0; - res = sqlite_exec(conn->sql_conn, "BEGIN", NULL, NULL, &errmsg); - if (res != SQLITE_OK) - { - lua_pushliteral(L, LUASQL_PREFIX); - lua_pushstring(L, errmsg); - sqlite_freemem(errmsg); - lua_concat(L, 2); - lua_error(L); - } + res = sqlite_exec(conn->sql_conn, "BEGIN", NULL, NULL, &errmsg); + if (res != SQLITE_OK) { + lua_pushliteral(L, LUASQL_PREFIX); + lua_pushstring(L, errmsg); + sqlite_freemem(errmsg); + lua_concat(L, 2); + lua_error(L); + } } lua_pushboolean(L, 1); return 1; @@ -475,7 +465,7 @@ static int conn_setautocommit(lua_State *L) */ static int create_connection(lua_State *L, int env, sqlite *sql_conn) { - conn_data *conn = (conn_data*)lua_newuserdata(L, sizeof(conn_data)); + conn_data *conn = (conn_data *)lua_newuserdata(L, sizeof(conn_data)); luasql_setmeta(L, LUASQL_CONNECTION_SQLITE); /* fill in structure */ @@ -495,22 +485,21 @@ static int create_connection(lua_State *L, int env, sqlite *sql_conn) */ static int env_connect(lua_State *L) { - const char *sourcename; + const char *sourcename; sqlite *conn; - char *errmsg; + char *errmsg; getenvironment(L); /* validate environment */ - sourcename = luaL_checkstring(L, 2); - conn = sqlite_open(sourcename, 0, &errmsg); - if (conn == NULL) - { - lua_pushnil(L); - lua_pushliteral(L, LUASQL_PREFIX); - lua_pushstring(L, errmsg); - sqlite_freemem(errmsg); - lua_concat(L, 2); - return 2; - } - return create_connection(L, 1, conn); + sourcename = luaL_checkstring(L, 2); + conn = sqlite_open(sourcename, 0, &errmsg); + if (conn == NULL) { + lua_pushnil(L); + lua_pushliteral(L, LUASQL_PREFIX); + lua_pushstring(L, errmsg); + sqlite_freemem(errmsg); + lua_concat(L, 2); + return 2; + } + return create_connection(L, 1, conn); } @@ -520,8 +509,9 @@ static int env_connect(lua_State *L) static int env_gc (lua_State *L) { env_data *env = (env_data *)luaL_checkudata(L, 1, LUASQL_ENVIRONMENT_SQLITE); - if (env != NULL && !(env->closed)) + if (env != NULL && !(env->closed)) { env->closed = 1; + } return 0; } @@ -544,18 +534,15 @@ static int env_close (lua_State *L) static int conn_escape(lua_State *L) { - const char *from = luaL_checklstring (L, 2, 0); - char *escaped = sqlite_mprintf("%q", from); - if (escaped == NULL) - { - lua_pushnil(L); - } - else - { - lua_pushstring(L, escaped); - sqlite_freemem(escaped); - } - return 1; + const char *from = luaL_checklstring (L, 2, 0); + char *escaped = sqlite_mprintf("%q", from); + if (escaped == NULL) { + lua_pushnil(L); + } else { + lua_pushstring(L, escaped); + sqlite_freemem(escaped); + } + return 1; } /* @@ -563,30 +550,30 @@ static int conn_escape(lua_State *L) */ static void create_metatables (lua_State *L) { - struct luaL_Reg environment_methods[] = { - {"__gc", env_gc}, - {"close", env_close}, - {"connect", env_connect}, + struct luaL_Reg environment_methods[] = { + {"__gc", env_gc}, + {"close", env_close}, + {"connect", env_connect}, {NULL, NULL}, }; - struct luaL_Reg connection_methods[] = { - {"__gc", conn_gc}, - {"close", conn_close}, + struct luaL_Reg connection_methods[] = { + {"__gc", conn_gc}, + {"close", conn_close}, {"escape", conn_escape}, - {"execute", conn_execute}, - {"commit", conn_commit}, - {"rollback", conn_rollback}, - {"setautocommit", conn_setautocommit}, + {"execute", conn_execute}, + {"commit", conn_commit}, + {"rollback", conn_rollback}, + {"setautocommit", conn_setautocommit}, {NULL, NULL}, - }; - struct luaL_Reg cursor_methods[] = { - {"__gc", cur_gc}, - {"close", cur_close}, - {"getcolnames", cur_getcolnames}, - {"getcoltypes", cur_getcoltypes}, - {"fetch", cur_fetch}, + }; + struct luaL_Reg cursor_methods[] = { + {"__gc", cur_gc}, + {"close", cur_close}, + {"getcolnames", cur_getcolnames}, + {"getcoltypes", cur_getcoltypes}, + {"fetch", cur_fetch}, {NULL, NULL}, - }; + }; luasql_createmeta(L, LUASQL_ENVIRONMENT_SQLITE, environment_methods); luasql_createmeta(L, LUASQL_CONNECTION_SQLITE, connection_methods); luasql_createmeta(L, LUASQL_CURSOR_SQLITE, cursor_methods); @@ -618,7 +605,7 @@ LUASQL_API int luaopen_luasql_sqlite(lua_State *L) {NULL, NULL}, }; create_metatables (L); - lua_newtable (L); + luasql_find_driver_table (L); luaL_setfuncs (L, driver, 0); luasql_set_info (L); return 1; diff --git a/src/ls_sqlite3.c b/src/ls_sqlite3.c index 150073c..e0902a6 100644 --- a/src/ls_sqlite3.c +++ b/src/ls_sqlite3.c @@ -23,30 +23,27 @@ #define LUASQL_CONNECTION_SQLITE "SQLite3 connection" #define LUASQL_CURSOR_SQLITE "SQLite3 cursor" -typedef struct -{ - short closed; +typedef struct { + short closed; } env_data; -typedef struct -{ - short closed; - int env; /* reference to environment */ - short auto_commit; /* 0 for manual commit */ - unsigned int cur_counter; - sqlite3 *sql_conn; +typedef struct { + short closed; + int env; /* reference to environment */ + short auto_commit; /* 0 for manual commit */ + unsigned int cur_counter; + sqlite3 *sql_conn; } conn_data; -typedef struct -{ - short closed; - int conn; /* reference to connection */ - int numcols; /* number of columns */ - int colnames, coltypes; /* reference to column information tables */ - conn_data *conn_data; /* reference to connection for cursor */ - sqlite3_stmt *sql_vm; +typedef struct { + short closed; + int conn; /* reference to connection */ + int numcols; /* number of columns */ + int colnames, coltypes; /* reference to column information tables */ + conn_data *conn_data; /* reference to connection for cursor */ + sqlite3_stmt *sql_vm; } cur_data; LUASQL_API int luaopen_luasql_sqlite3(lua_State *L); @@ -55,33 +52,36 @@ LUASQL_API int luaopen_luasql_sqlite3(lua_State *L); /* ** Check for valid environment. */ -static env_data *getenvironment(lua_State *L) { - env_data *env = (env_data *)luaL_checkudata(L, 1, LUASQL_ENVIRONMENT_SQLITE); - luaL_argcheck(L, env != NULL, 1, LUASQL_PREFIX"environment expected"); - luaL_argcheck(L, !env->closed, 1, LUASQL_PREFIX"environment is closed"); - return env; +static env_data *getenvironment(lua_State *L) +{ + env_data *env = (env_data *)luaL_checkudata(L, 1, LUASQL_ENVIRONMENT_SQLITE); + luaL_argcheck(L, env != NULL, 1, LUASQL_PREFIX"environment expected"); + luaL_argcheck(L, !env->closed, 1, LUASQL_PREFIX"environment is closed"); + return env; } /* ** Check for valid connection. */ -static conn_data *getconnection(lua_State *L) { - conn_data *conn = (conn_data *)luaL_checkudata (L, 1, LUASQL_CONNECTION_SQLITE); - luaL_argcheck(L, conn != NULL, 1, LUASQL_PREFIX"connection expected"); - luaL_argcheck(L, !conn->closed, 1, LUASQL_PREFIX"connection is closed"); - return conn; +static conn_data *getconnection(lua_State *L) +{ + conn_data *conn = (conn_data *)luaL_checkudata (L, 1, LUASQL_CONNECTION_SQLITE); + luaL_argcheck(L, conn != NULL, 1, LUASQL_PREFIX"connection expected"); + luaL_argcheck(L, !conn->closed, 1, LUASQL_PREFIX"connection is closed"); + return conn; } /* ** Check for valid cursor. */ -static cur_data *getcursor(lua_State *L) { - cur_data *cur = (cur_data *)luaL_checkudata (L, 1, LUASQL_CURSOR_SQLITE); - luaL_argcheck(L, cur != NULL, 1, LUASQL_PREFIX"cursor expected"); - luaL_argcheck(L, !cur->closed, 1, LUASQL_PREFIX"cursor is closed"); - return cur; +static cur_data *getcursor(lua_State *L) +{ + cur_data *cur = (cur_data *)luaL_checkudata (L, 1, LUASQL_CURSOR_SQLITE); + luaL_argcheck(L, cur != NULL, 1, LUASQL_PREFIX"cursor expected"); + luaL_argcheck(L, !cur->closed, 1, LUASQL_PREFIX"cursor is closed"); + return cur; } /* @@ -89,19 +89,19 @@ static cur_data *getcursor(lua_State *L) { */ static void cur_nullify(lua_State *L, cur_data *cur) { - conn_data *conn; - - /* Nullify structure fields. */ - cur->closed = 1; - cur->sql_vm = NULL; - /* Decrement cursor counter on connection object */ - lua_rawgeti (L, LUA_REGISTRYINDEX, cur->conn); - conn = lua_touserdata (L, -1); - conn->cur_counter--; - - luaL_unref(L, LUA_REGISTRYINDEX, cur->conn); - luaL_unref(L, LUA_REGISTRYINDEX, cur->colnames); - luaL_unref(L, LUA_REGISTRYINDEX, cur->coltypes); + conn_data *conn; + + /* Nullify structure fields. */ + cur->closed = 1; + cur->sql_vm = NULL; + /* Decrement cursor counter on connection object */ + lua_rawgeti (L, LUA_REGISTRYINDEX, cur->conn); + conn = lua_touserdata (L, -1); + conn->cur_counter--; + + luaL_unref(L, LUA_REGISTRYINDEX, cur->conn); + luaL_unref(L, LUA_REGISTRYINDEX, cur->colnames); + luaL_unref(L, LUA_REGISTRYINDEX, cur->coltypes); } @@ -109,103 +109,102 @@ static void cur_nullify(lua_State *L, cur_data *cur) ** Finalizes the vm ** Return nil + errmsg or nil in case of sucess */ -static int finalize(lua_State *L, cur_data *cur) { - const char *errmsg; - if (sqlite3_finalize(cur->sql_vm) != SQLITE_OK) - { - errmsg = sqlite3_errmsg(cur->conn_data->sql_conn); - cur_nullify(L, cur); - return luasql_faildirect(L, errmsg); - } - cur_nullify(L, cur); - lua_pushnil(L); - return 1; +static int finalize(lua_State *L, cur_data *cur) +{ + const char *errmsg; + if (sqlite3_finalize(cur->sql_vm) != SQLITE_OK) { + errmsg = sqlite3_errmsg(cur->conn_data->sql_conn); + cur_nullify(L, cur); + return luasql_faildirect(L, errmsg); + } + cur_nullify(L, cur); + lua_pushnil(L); + return 1; } -static void push_column(lua_State *L, sqlite3_stmt *vm, int column) { - switch (sqlite3_column_type(vm, column)) { - case SQLITE_INTEGER: - lua_pushinteger(L, sqlite3_column_int64(vm, column)); - break; - case SQLITE_FLOAT: - lua_pushnumber(L, sqlite3_column_double(vm, column)); - break; - case SQLITE_TEXT: - lua_pushlstring(L, (const char *)sqlite3_column_text(vm, column), - (size_t)sqlite3_column_bytes(vm, column)); - break; - case SQLITE_BLOB: - lua_pushlstring(L, sqlite3_column_blob(vm, column), - (size_t)sqlite3_column_bytes(vm, column)); - break; - case SQLITE_NULL: - lua_pushnil(L); - break; - default: - luaL_error(L, LUASQL_PREFIX"Unrecognized column type"); - break; - } +static void push_column(lua_State *L, sqlite3_stmt *vm, int column) +{ + switch (sqlite3_column_type(vm, column)) { + case SQLITE_INTEGER: + luasql_pushinteger(L, sqlite3_column_int64(vm, column)); + break; + case SQLITE_FLOAT: + lua_pushnumber(L, sqlite3_column_double(vm, column)); + break; + case SQLITE_TEXT: + lua_pushlstring(L, (const char *)sqlite3_column_text(vm, column), + (size_t)sqlite3_column_bytes(vm, column)); + break; + case SQLITE_BLOB: + lua_pushlstring(L, sqlite3_column_blob(vm, column), + (size_t)sqlite3_column_bytes(vm, column)); + break; + case SQLITE_NULL: + lua_pushnil(L); + break; + default: + luaL_error(L, LUASQL_PREFIX"Unrecognized column type"); + break; + } } /* ** Get another row of the given cursor. */ -static int cur_fetch (lua_State *L) { - cur_data *cur = getcursor(L); - sqlite3_stmt *vm = cur->sql_vm; - int res; - - if (vm == NULL) - return 0; - - res = sqlite3_step(vm); - - /* no more results? */ - if (res == SQLITE_DONE) - return finalize(L, cur); - - if (res != SQLITE_ROW) - return finalize(L, cur); - - if (lua_istable (L, 2)) - { - int i; - const char *opts = luaL_optstring(L, 3, "n"); - - if (strchr(opts, 'n') != NULL) - { - /* Copy values to numerical indices */ - for (i = 0; i < cur->numcols;) - { - push_column(L, vm, i); - lua_rawseti(L, 2, ++i); - } - } - if (strchr(opts, 'a') != NULL) - { - /* Copy values to alphanumerical indices */ - lua_rawgeti(L, LUA_REGISTRYINDEX, cur->colnames); - - for (i = 0; i < cur->numcols; i++) - { - lua_rawgeti(L, -1, i+1); - push_column(L, vm, i); - lua_rawset (L, 2); - } - } - lua_pushvalue(L, 2); - return 1; /* return table */ - } - else - { - int i; - luaL_checkstack (L, cur->numcols, LUASQL_PREFIX"too many columns"); - for (i = 0; i < cur->numcols; ++i) - push_column(L, vm, i); - return cur->numcols; /* return #numcols values */ - } +static int cur_fetch (lua_State *L) +{ + cur_data *cur = getcursor(L); + sqlite3_stmt *vm = cur->sql_vm; + int res; + + if (vm == NULL) { + return 0; + } + + res = sqlite3_step(vm); + + /* no more results? */ + if (res == SQLITE_DONE) { + return finalize(L, cur); + } + + if (res != SQLITE_ROW) { + return finalize(L, cur); + } + + if (lua_istable (L, 2)) { + int i; + const char *opts = luaL_optstring(L, 3, "n"); + + if (strchr(opts, 'n') != NULL) { + /* Copy values to numerical indices */ + for (i = 0; i < cur->numcols;) { + push_column(L, vm, i); + lua_rawseti(L, 2, ++i); + } + } + if (strchr(opts, 'a') != NULL) { + /* Copy values to alphanumerical indices */ + lua_rawgeti(L, LUA_REGISTRYINDEX, cur->colnames); + + for (i = 0; i < cur->numcols; i++) { + lua_rawgeti(L, -1, i+1); + push_column(L, vm, i); + lua_rawset (L, 2); + } + } + lua_pushvalue(L, 2); + return 1; /* return table */ + } else { + int i; + luaL_checkstack (L, cur->numcols, LUASQL_PREFIX"too many columns"); + for (i = 0; i < cur->numcols; ++i) { + push_column(L, vm, i); + } + return cur->numcols; /* return #numcols values */ + } } @@ -214,13 +213,12 @@ static int cur_fetch (lua_State *L) { */ static int cur_gc(lua_State *L) { - cur_data *cur = (cur_data *)luaL_checkudata(L, 1, LUASQL_CURSOR_SQLITE); - if (cur != NULL && !(cur->closed)) - { - sqlite3_finalize(cur->sql_vm); - cur_nullify(L, cur); - } - return 0; + cur_data *cur = (cur_data *)luaL_checkudata(L, 1, LUASQL_CURSOR_SQLITE); + if (cur != NULL && !(cur->closed)) { + sqlite3_finalize(cur->sql_vm); + cur_nullify(L, cur); + } + return 0; } @@ -230,16 +228,16 @@ static int cur_gc(lua_State *L) */ static int cur_close(lua_State *L) { - cur_data *cur = (cur_data *)luaL_checkudata(L, 1, LUASQL_CURSOR_SQLITE); - luaL_argcheck(L, cur != NULL, 1, LUASQL_PREFIX"cursor expected"); - if (cur->closed) { - lua_pushboolean(L, 0); - return 1; - } - sqlite3_finalize(cur->sql_vm); - cur_nullify(L, cur); - lua_pushboolean(L, 1); - return 1; + cur_data *cur = (cur_data *)luaL_checkudata(L, 1, LUASQL_CURSOR_SQLITE); + luaL_argcheck(L, cur != NULL, 1, LUASQL_PREFIX"cursor expected"); + if (cur->closed) { + lua_pushboolean(L, 0); + return 1; + } + sqlite3_finalize(cur->sql_vm); + cur_nullify(L, cur); + lua_pushboolean(L, 1); + return 1; } @@ -248,9 +246,9 @@ static int cur_close(lua_State *L) */ static int cur_getcolnames(lua_State *L) { - cur_data *cur = getcursor(L); - lua_rawgeti(L, LUA_REGISTRYINDEX, cur->colnames); - return 1; + cur_data *cur = getcursor(L); + lua_rawgeti(L, LUA_REGISTRYINDEX, cur->colnames); + return 1; } @@ -259,9 +257,9 @@ static int cur_getcolnames(lua_State *L) */ static int cur_getcoltypes(lua_State *L) { - cur_data *cur = getcursor(L); - lua_rawgeti(L, LUA_REGISTRYINDEX, cur->coltypes); - return 1; + cur_data *cur = getcursor(L); + lua_rawgeti(L, LUA_REGISTRYINDEX, cur->coltypes); + return 1; } @@ -271,46 +269,44 @@ static int cur_getcoltypes(lua_State *L) /* static int create_cursor(lua_State *L, int conn, sqlite3_stmt *sql_vm, int numcols, const char **row, const char **col_info)*/ static int create_cursor(lua_State *L, int o, conn_data *conn, - sqlite3_stmt *sql_vm, int numcols) + sqlite3_stmt *sql_vm, int numcols) { - int i; - cur_data *cur = (cur_data*)lua_newuserdata(L, sizeof(cur_data)); - luasql_setmeta (L, LUASQL_CURSOR_SQLITE); - - /* increment cursor count for the connection creating this cursor */ - conn->cur_counter++; - - /* fill in structure */ - cur->closed = 0; - cur->conn = LUA_NOREF; - cur->numcols = numcols; - cur->colnames = LUA_NOREF; - cur->coltypes = LUA_NOREF; - cur->sql_vm = sql_vm; - cur->conn_data = conn; - - lua_pushvalue(L, o); - cur->conn = luaL_ref(L, LUA_REGISTRYINDEX); - - /* create table with column names */ - lua_newtable(L); - for (i = 0; i < numcols;) - { - lua_pushstring(L, sqlite3_column_name(sql_vm, i)); - lua_rawseti(L, -2, ++i); - } - cur->colnames = luaL_ref(L, LUA_REGISTRYINDEX); - - /* create table with column types */ - lua_newtable(L); - for (i = 0; i < numcols;) - { - lua_pushstring(L, sqlite3_column_decltype(sql_vm, i)); - lua_rawseti(L, -2, ++i); - } - cur->coltypes = luaL_ref(L, LUA_REGISTRYINDEX); - - return 1; + int i; + cur_data *cur = (cur_data *)lua_newuserdata(L, sizeof(cur_data)); + luasql_setmeta (L, LUASQL_CURSOR_SQLITE); + + /* increment cursor count for the connection creating this cursor */ + conn->cur_counter++; + + /* fill in structure */ + cur->closed = 0; + cur->conn = LUA_NOREF; + cur->numcols = numcols; + cur->colnames = LUA_NOREF; + cur->coltypes = LUA_NOREF; + cur->sql_vm = sql_vm; + cur->conn_data = conn; + + lua_pushvalue(L, o); + cur->conn = luaL_ref(L, LUA_REGISTRYINDEX); + + /* create table with column names */ + lua_newtable(L); + for (i = 0; i < numcols;) { + lua_pushstring(L, sqlite3_column_name(sql_vm, i)); + lua_rawseti(L, -2, ++i); + } + cur->colnames = luaL_ref(L, LUA_REGISTRYINDEX); + + /* create table with column types */ + lua_newtable(L); + for (i = 0; i < numcols;) { + lua_pushstring(L, sqlite3_column_decltype(sql_vm, i)); + lua_rawseti(L, -2, ++i); + } + cur->coltypes = luaL_ref(L, LUA_REGISTRYINDEX); + + return 1; } @@ -319,18 +315,18 @@ static int create_cursor(lua_State *L, int o, conn_data *conn, */ static int conn_gc(lua_State *L) { - conn_data *conn = (conn_data *)luaL_checkudata(L, 1, LUASQL_CONNECTION_SQLITE); - if (conn != NULL && !(conn->closed)) - { - if (conn->cur_counter > 0) - return luaL_error (L, LUASQL_PREFIX"there are open cursors"); - - /* Nullify structure fields. */ - conn->closed = 1; - luaL_unref(L, LUA_REGISTRYINDEX, conn->env); - sqlite3_close(conn->sql_conn); - } - return 0; + conn_data *conn = (conn_data *)luaL_checkudata(L, 1, LUASQL_CONNECTION_SQLITE); + if (conn != NULL && !(conn->closed)) { + if (conn->cur_counter > 0) { + return luaL_error (L, LUASQL_PREFIX"there are open cursors"); + } + + /* Nullify structure fields. */ + conn->closed = 1; + luaL_unref(L, LUA_REGISTRYINDEX, conn->env); + sqlite3_close(conn->sql_conn); + } + return 0; } @@ -339,32 +335,28 @@ static int conn_gc(lua_State *L) */ static int conn_close(lua_State *L) { - conn_data *conn = (conn_data *)luaL_checkudata(L, 1, LUASQL_CONNECTION_SQLITE); - luaL_argcheck (L, conn != NULL, 1, LUASQL_PREFIX"connection expected"); - if (conn->closed) - { - lua_pushboolean(L, 0); - return 1; - } - conn_gc(L); - lua_pushboolean(L, 1); - return 1; + conn_data *conn = (conn_data *)luaL_checkudata(L, 1, LUASQL_CONNECTION_SQLITE); + luaL_argcheck (L, conn != NULL, 1, LUASQL_PREFIX"connection expected"); + if (conn->closed) { + lua_pushboolean(L, 0); + return 1; + } + conn_gc(L); + lua_pushboolean(L, 1); + return 1; } static int conn_escape(lua_State *L) { - const char *from = luaL_checklstring (L, 2, 0); - char *escaped = sqlite3_mprintf("%q", from); - if (escaped == NULL) - { - lua_pushnil(L); - } - else - { - lua_pushstring(L, escaped); - sqlite3_free(escaped); - } - return 1; + const char *from = luaL_checklstring (L, 2, 0); + char *escaped = sqlite3_mprintf("%q", from); + if (escaped == NULL) { + lua_pushnil(L); + } else { + lua_pushstring(L, escaped); + sqlite3_free(escaped); + } + return 1; } /* @@ -374,48 +366,45 @@ static int conn_escape(lua_State *L) */ static int conn_execute(lua_State *L) { - conn_data *conn = getconnection(L); - const char *statement = luaL_checkstring(L, 2); - int res; - sqlite3_stmt *vm; - const char *errmsg; - int numcols; - const char *tail; + conn_data *conn = getconnection(L); + const char *statement = luaL_checkstring(L, 2); + int res; + sqlite3_stmt *vm; + const char *errmsg; + int numcols; + const char *tail; #if SQLITE_VERSION_NUMBER > 3006013 - res = sqlite3_prepare_v2(conn->sql_conn, statement, -1, &vm, &tail); + res = sqlite3_prepare_v2(conn->sql_conn, statement, -1, &vm, &tail); #else - res = sqlite3_prepare(conn->sql_conn, statement, -1, &vm, &tail); + res = sqlite3_prepare(conn->sql_conn, statement, -1, &vm, &tail); #endif - if (res != SQLITE_OK) - { - errmsg = sqlite3_errmsg(conn->sql_conn); - return luasql_faildirect(L, errmsg); - } - - /* process first result to retrive query information and type */ - res = sqlite3_step(vm); - numcols = sqlite3_column_count(vm); - - /* real query? if empty, must have numcols!=0 */ - if ((res == SQLITE_ROW) || ((res == SQLITE_DONE) && numcols)) - { - sqlite3_reset(vm); - return create_cursor(L, 1, conn, vm, numcols); - } - - if (res == SQLITE_DONE) /* and numcols==0, INSERT,UPDATE,DELETE statement */ - { - sqlite3_finalize(vm); - /* return number of columns changed */ - lua_pushnumber(L, sqlite3_changes(conn->sql_conn)); - return 1; - } - - /* error */ - errmsg = sqlite3_errmsg(conn->sql_conn); - sqlite3_finalize(vm); - return luasql_faildirect(L, errmsg); + if (res != SQLITE_OK) { + errmsg = sqlite3_errmsg(conn->sql_conn); + return luasql_faildirect(L, errmsg); + } + + /* process first result to retrive query information and type */ + res = sqlite3_step(vm); + numcols = sqlite3_column_count(vm); + + /* real query? if empty, must have numcols!=0 */ + if ((res == SQLITE_ROW) || ((res == SQLITE_DONE) && numcols)) { + sqlite3_reset(vm); + return create_cursor(L, 1, conn, vm, numcols); + } + + if (res == SQLITE_DONE) { /* and numcols==0, INSERT,UPDATE,DELETE statement */ + sqlite3_finalize(vm); + /* return number of columns changed */ + lua_pushnumber(L, sqlite3_changes(conn->sql_conn)); + return 1; + } + + /* error */ + errmsg = sqlite3_errmsg(conn->sql_conn); + sqlite3_finalize(vm); + return luasql_faildirect(L, errmsg); } @@ -424,26 +413,27 @@ static int conn_execute(lua_State *L) */ static int conn_commit(lua_State *L) { - char *errmsg; - conn_data *conn = getconnection(L); - int res; - const char *sql = "COMMIT"; - - if (conn->auto_commit == 0) sql = "COMMIT;BEGIN"; - - res = sqlite3_exec(conn->sql_conn, sql, NULL, NULL, &errmsg); - - if (res != SQLITE_OK) - { - lua_pushnil(L); - lua_pushliteral(L, LUASQL_PREFIX); - lua_pushstring(L, errmsg); - sqlite3_free(errmsg); - lua_concat(L, 2); - return 2; - } - lua_pushboolean(L, 1); - return 1; + char *errmsg; + conn_data *conn = getconnection(L); + int res; + const char *sql = "COMMIT"; + + if (conn->auto_commit == 0) { + sql = "COMMIT;BEGIN"; + } + + res = sqlite3_exec(conn->sql_conn, sql, NULL, NULL, &errmsg); + + if (res != SQLITE_OK) { + lua_pushnil(L); + lua_pushliteral(L, LUASQL_PREFIX); + lua_pushstring(L, errmsg); + sqlite3_free(errmsg); + lua_concat(L, 2); + return 2; + } + lua_pushboolean(L, 1); + return 1; } @@ -452,32 +442,33 @@ static int conn_commit(lua_State *L) */ static int conn_rollback(lua_State *L) { - char *errmsg; - conn_data *conn = getconnection(L); - int res; - const char *sql = "ROLLBACK"; - - if (conn->auto_commit == 0) sql = "ROLLBACK;BEGIN"; - - res = sqlite3_exec(conn->sql_conn, sql, NULL, NULL, &errmsg); - if (res != SQLITE_OK) - { - lua_pushnil(L); - lua_pushliteral(L, LUASQL_PREFIX); - lua_pushstring(L, errmsg); - sqlite3_free(errmsg); - lua_concat(L, 2); - return 2; - } - lua_pushboolean(L, 1); - return 1; + char *errmsg; + conn_data *conn = getconnection(L); + int res; + const char *sql = "ROLLBACK"; + + if (conn->auto_commit == 0) { + sql = "ROLLBACK;BEGIN"; + } + + res = sqlite3_exec(conn->sql_conn, sql, NULL, NULL, &errmsg); + if (res != SQLITE_OK) { + lua_pushnil(L); + lua_pushliteral(L, LUASQL_PREFIX); + lua_pushstring(L, errmsg); + sqlite3_free(errmsg); + lua_concat(L, 2); + return 2; + } + lua_pushboolean(L, 1); + return 1; } static int conn_getlastautoid(lua_State *L) { - conn_data *conn = getconnection(L); - lua_pushnumber(L, sqlite3_last_insert_rowid(conn->sql_conn)); - return 1; + conn_data *conn = getconnection(L); + lua_pushnumber(L, sqlite3_last_insert_rowid(conn->sql_conn)); + return 1; } @@ -488,30 +479,26 @@ static int conn_getlastautoid(lua_State *L) */ static int conn_setautocommit(lua_State *L) { - conn_data *conn = getconnection(L); - if (lua_toboolean(L, 2)) - { - conn->auto_commit = 1; - /* undo active transaction - ignore errors */ - sqlite3_exec(conn->sql_conn, "ROLLBACK", NULL, NULL, NULL); - } - else - { - char *errmsg; - int res; - conn->auto_commit = 0; - res = sqlite3_exec(conn->sql_conn, "BEGIN", NULL, NULL, &errmsg); - if (res != SQLITE_OK) - { - lua_pushliteral(L, LUASQL_PREFIX); - lua_pushstring(L, errmsg); - sqlite3_free(errmsg); - lua_concat(L, 2); - lua_error(L); - } - } - lua_pushboolean(L, 1); - return 1; + conn_data *conn = getconnection(L); + if (lua_toboolean(L, 2)) { + conn->auto_commit = 1; + /* undo active transaction - ignore errors */ + sqlite3_exec(conn->sql_conn, "ROLLBACK", NULL, NULL, NULL); + } else { + char *errmsg; + int res; + conn->auto_commit = 0; + res = sqlite3_exec(conn->sql_conn, "BEGIN", NULL, NULL, &errmsg); + if (res != SQLITE_OK) { + lua_pushliteral(L, LUASQL_PREFIX); + lua_pushstring(L, errmsg); + sqlite3_free(errmsg); + lua_concat(L, 2); + lua_error(L); + } + } + lua_pushboolean(L, 1); + return 1; } @@ -520,18 +507,18 @@ static int conn_setautocommit(lua_State *L) */ static int create_connection(lua_State *L, int env, sqlite3 *sql_conn) { - conn_data *conn = (conn_data*)lua_newuserdata(L, sizeof(conn_data)); - luasql_setmeta(L, LUASQL_CONNECTION_SQLITE); - - /* fill in structure */ - conn->closed = 0; - conn->env = LUA_NOREF; - conn->auto_commit = 1; - conn->sql_conn = sql_conn; - conn->cur_counter = 0; - lua_pushvalue (L, env); - conn->env = luaL_ref (L, LUA_REGISTRYINDEX); - return 1; + conn_data *conn = (conn_data *)lua_newuserdata(L, sizeof(conn_data)); + luasql_setmeta(L, LUASQL_CONNECTION_SQLITE); + + /* fill in structure */ + conn->closed = 0; + conn->env = LUA_NOREF; + conn->auto_commit = 1; + conn->sql_conn = sql_conn; + conn->cur_counter = 0; + lua_pushvalue (L, env); + conn->env = luaL_ref (L, LUA_REGISTRYINDEX); + return 1; } @@ -540,32 +527,32 @@ static int create_connection(lua_State *L, int env, sqlite3 *sql_conn) */ static int env_connect(lua_State *L) { - const char *sourcename; - sqlite3 *conn; - const char *errmsg; - int res; - getenvironment(L); /* validate environment */ + const char *sourcename; + sqlite3 *conn; + const char *errmsg; + int res; + getenvironment(L); /* validate environment */ - sourcename = luaL_checkstring(L, 2); + sourcename = luaL_checkstring(L, 2); #if SQLITE_VERSION_NUMBER > 3006013 - res = sqlite3_open_v2(sourcename, &conn, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); + res = sqlite3_open_v2(sourcename, &conn, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); #else - res = sqlite3_open(sourcename, &conn); + res = sqlite3_open(sourcename, &conn); #endif - if (res != SQLITE_OK) - { - errmsg = sqlite3_errmsg(conn); - luasql_faildirect(L, errmsg); - sqlite3_close(conn); - return 2; - } - - if (lua_isnumber(L, 3)) { - sqlite3_busy_timeout(conn, lua_tonumber(L,3)); /* TODO: remove this */ - } - - return create_connection(L, 1, conn); + if (res != SQLITE_OK) { + errmsg = sqlite3_errmsg(conn); + luasql_faildirect(L, errmsg); + sqlite3_close(conn); + return 2; + } + + if (lua_isnumber(L, 3)) { + sqlite3_busy_timeout(conn, lua_tonumber(L,3)); /* TODO: remove this */ + } + + return create_connection(L, 1, conn); } @@ -574,10 +561,11 @@ static int env_connect(lua_State *L) */ static int env_gc (lua_State *L) { - env_data *env = (env_data *)luaL_checkudata(L, 1, LUASQL_ENVIRONMENT_SQLITE); - if (env != NULL && !(env->closed)) - env->closed = 1; - return 0; + env_data *env = (env_data *)luaL_checkudata(L, 1, LUASQL_ENVIRONMENT_SQLITE); + if (env != NULL && !(env->closed)) { + env->closed = 1; + } + return 0; } @@ -586,15 +574,15 @@ static int env_gc (lua_State *L) */ static int env_close (lua_State *L) { - env_data *env = (env_data *)luaL_checkudata(L, 1, LUASQL_ENVIRONMENT_SQLITE); - luaL_argcheck(L, env != NULL, 1, LUASQL_PREFIX"environment expected"); - if (env->closed) { - lua_pushboolean(L, 0); - return 1; - } - env_gc(L); - lua_pushboolean(L, 1); - return 1; + env_data *env = (env_data *)luaL_checkudata(L, 1, LUASQL_ENVIRONMENT_SQLITE); + luaL_argcheck(L, env != NULL, 1, LUASQL_PREFIX"environment expected"); + if (env->closed) { + lua_pushboolean(L, 0); + return 1; + } + env_gc(L); + lua_pushboolean(L, 1); + return 1; } @@ -614,35 +602,35 @@ static int opts_settimeout (lua_State *L) */ static void create_metatables (lua_State *L) { - struct luaL_Reg environment_methods[] = { - {"__gc", env_gc}, - {"close", env_close}, - {"connect", env_connect}, - {NULL, NULL}, - }; - struct luaL_Reg connection_methods[] = { - {"__gc", conn_gc}, - {"close", conn_close}, - {"escape", conn_escape}, - {"execute", conn_execute}, - {"commit", conn_commit}, - {"rollback", conn_rollback}, - {"setautocommit", conn_setautocommit}, - {"getlastautoid", conn_getlastautoid}, - {NULL, NULL}, - }; - struct luaL_Reg cursor_methods[] = { - {"__gc", cur_gc}, - {"close", cur_close}, - {"getcolnames", cur_getcolnames}, - {"getcoltypes", cur_getcoltypes}, - {"fetch", cur_fetch}, - {NULL, NULL}, - }; - luasql_createmeta(L, LUASQL_ENVIRONMENT_SQLITE, environment_methods); - luasql_createmeta(L, LUASQL_CONNECTION_SQLITE, connection_methods); - luasql_createmeta(L, LUASQL_CURSOR_SQLITE, cursor_methods); - lua_pop (L, 3); + struct luaL_Reg environment_methods[] = { + {"__gc", env_gc}, + {"close", env_close}, + {"connect", env_connect}, + {NULL, NULL}, + }; + struct luaL_Reg connection_methods[] = { + {"__gc", conn_gc}, + {"close", conn_close}, + {"escape", conn_escape}, + {"execute", conn_execute}, + {"commit", conn_commit}, + {"rollback", conn_rollback}, + {"setautocommit", conn_setautocommit}, + {"getlastautoid", conn_getlastautoid}, + {NULL, NULL}, + }; + struct luaL_Reg cursor_methods[] = { + {"__gc", cur_gc}, + {"close", cur_close}, + {"getcolnames", cur_getcolnames}, + {"getcoltypes", cur_getcoltypes}, + {"fetch", cur_fetch}, + {NULL, NULL}, + }; + luasql_createmeta(L, LUASQL_ENVIRONMENT_SQLITE, environment_methods); + luasql_createmeta(L, LUASQL_CONNECTION_SQLITE, connection_methods); + luasql_createmeta(L, LUASQL_CURSOR_SQLITE, cursor_methods); + lua_pop (L, 3); } /* @@ -650,12 +638,12 @@ static void create_metatables (lua_State *L) */ static int create_environment (lua_State *L) { - env_data *env = (env_data *)lua_newuserdata(L, sizeof(env_data)); - luasql_setmeta(L, LUASQL_ENVIRONMENT_SQLITE); + env_data *env = (env_data *)lua_newuserdata(L, sizeof(env_data)); + luasql_setmeta(L, LUASQL_ENVIRONMENT_SQLITE); - /* fill in structure */ - env->closed = 0; - return 1; + /* fill in structure */ + env->closed = 0; + return 1; } @@ -665,13 +653,13 @@ static int create_environment (lua_State *L) */ LUASQL_API int luaopen_luasql_sqlite3(lua_State *L) { - struct luaL_Reg driver[] = { - {"sqlite3", create_environment}, - {NULL, NULL}, - }; - create_metatables (L); - lua_newtable (L); - luaL_setfuncs (L, driver, 0); - luasql_set_info (L); - return 1; + struct luaL_Reg driver[] = { + {"sqlite3", create_environment}, + {NULL, NULL}, + }; + create_metatables (L); + luasql_find_driver_table (L); + luaL_setfuncs (L, driver, 0); + luasql_set_info (L); + return 1; } diff --git a/src/luasql.c b/src/luasql.c index caf9721..7a0d926 100644 --- a/src/luasql.c +++ b/src/luasql.c @@ -51,13 +51,14 @@ typedef struct { short closed; } pseudo_data; ** This function is used by `tostring'. */ static int luasql_tostring (lua_State *L) { - char buff[100]; pseudo_data *obj = (pseudo_data *)lua_touserdata (L, 1); - if (obj->closed) - strcpy (buff, "closed"); - else - sprintf (buff, "%p", (void *)obj); - lua_pushfstring (L, "%s (%s)", lua_tostring(L,lua_upvalueindex(1)), buff); + + if (obj->closed) { + lua_pushfstring (L, "%s (closed)", lua_tostring(L,lua_upvalueindex(1))); + } else { + lua_pushfstring (L, "%s (%p)", lua_tostring(L,lua_upvalueindex(1)), (void *)obj); + } + return 1; } @@ -122,12 +123,103 @@ LUASQL_API void luasql_setmeta (lua_State *L, const char *name) { */ LUASQL_API void luasql_set_info (lua_State *L) { lua_pushliteral (L, "_COPYRIGHT"); - lua_pushliteral (L, "Copyright (C) 2003-2012 Kepler Project"); + lua_pushliteral (L, "Copyright (C) 2003-2015 Kepler Project"); lua_settable (L, -3); lua_pushliteral (L, "_DESCRIPTION"); lua_pushliteral (L, "LuaSQL is a simple interface from Lua to a DBMS"); lua_settable (L, -3); lua_pushliteral (L, "_VERSION"); - lua_pushliteral (L, "LuaSQL 2.3.0"); + lua_pushliteral (L, "LuaSQL 3.0.0"); lua_settable (L, -3); } + +/* +** Finds a pre-existing LuaSQL table, or creates a new one. +*/ +LUASQL_API void luasql_find_driver_table (lua_State *L) { + lua_getglobal(L, "package"); + if(lua_istable(L, -1)) { + lua_getfield(L, -1, "loaded"); + lua_remove(L, -2); + + lua_pushnil(L); + while(lua_next(L, -2) != 0) { + const char *key = lua_tostring(L, -2); + if(strncmp(key, LUASQL_TABLENAME, strlen(LUASQL_TABLENAME)) == 0) { + lua_remove(L, -2); + lua_remove(L, -2); + return; + } + + lua_pop(L, 1); + } + lua_pop(L, 2); + } else { + lua_pop(L, 1); + } + + lua_newtable (L); +} + +/* +** registers a driver, taking account of the Lua version differences +** Lua Returns: +** The new/existing 'luasql' driver table on the top of the stack +*/ +LUASQL_API void luasql_reg_driver (lua_State *L, const luaL_Reg *driver) +{ +#if LUA_VERSION_NUM<=501 + luaL_register (L, LUASQL_TABLENAME, driver); +#else + luasql_find_driver_table (L); + luaL_setfuncs (L, driver, 0); +#endif + luasql_set_info (L); +} + +LUASQL_API const char* luasql_table_optstring(lua_State *L, int idx, const char* name, const char* def) { + const char* res = NULL; + + lua_pushstring(L, name); + lua_gettable(L, idx); + + res = lua_tostring(L, -1); + lua_pop(L, 1); + + return (res != NULL) ? res : def; +} + + +LUASQL_API lua_Number luasql_table_optnumber(lua_State *L, int idx, const char* name, lua_Number def) { + lua_Number res = 0; + + lua_pushstring(L, name); + lua_gettable(L, idx); + + res = lua_tonumber(L, -1); + lua_pop(L, 1); + + return lua_isnumber(L, -1) ? res : def; +} + +/* +** Registers a given C object in the registry to avoid GC +*/ +void luasql_registerobj(lua_State *L, int index, void *obj) +{ + lua_pushvalue(L, index); + lua_pushlightuserdata(L, obj); + lua_pushvalue(L, -2); + lua_settable(L, LUA_REGISTRYINDEX); + lua_pop(L, 1); +} + +/* +** Unregisters a given C object from the registry +*/ +void luasql_unregisterobj(lua_State *L, void *obj) +{ + lua_pushlightuserdata(L, obj); + lua_pushnil(L); + lua_settable(L, LUA_REGISTRYINDEX); +} diff --git a/src/luasql.h b/src/luasql.h index 345bf57..7f8631b 100644 --- a/src/luasql.h +++ b/src/luasql.h @@ -3,6 +3,9 @@ ** See Copyright Notice in license.html */ +#include +#include + #ifndef _LUASQL_ #define _LUASQL_ @@ -13,9 +16,14 @@ #if !defined LUA_VERSION_NUM /* Lua 5.0 */ #define luaL_Reg luaL_reg +#endif -#define lua_pushinteger(L, n) \ - lua_pushnumber(L, (lua_Number)n) +#if LUA_VERSION_NUM>=503 +#define luasql_pushinteger lua_pushinteger +#define luasql_isinteger lua_isinteger +#else +#define luasql_pushinteger lua_pushnumber +#define luasql_isinteger lua_isnumber #endif #define LUASQL_PREFIX "LuaSQL: " @@ -29,6 +37,15 @@ LUASQL_API int luasql_failmsg (lua_State *L, const char *err, const char *m); LUASQL_API int luasql_createmeta (lua_State *L, const char *name, const luaL_Reg *methods); LUASQL_API void luasql_setmeta (lua_State *L, const char *name); LUASQL_API void luasql_set_info (lua_State *L); +LUASQL_API void luasql_find_driver_table (lua_State *L); +LUASQL_API void luasql_reg_driver (lua_State *L, const luaL_Reg *driver); + +LUASQL_API const char* luasql_table_optstring(lua_State *L, int idx, const char* name, const char* def); +LUASQL_API lua_Number luasql_table_optnumber(lua_State *L, int idx, const char* name, lua_Number def); + + +void luasql_registerobj(lua_State *L, int index, void *obj); +void luasql_unregisterobj(lua_State *L, void *obj); #if !defined LUA_VERSION_NUM || LUA_VERSION_NUM==501 void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup); diff --git a/tests/test.lua b/tests/test.lua index 0531cc4..70387d2 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -174,6 +174,9 @@ function create_table () CONN = CONN_OK (ENV:connect (datasource, username, password)) -- Create t. local cmd = define_table(TOTAL_FIELDS) + print() + print(cmd) + print() assert2 (CREATE_TABLE_RETURN_VALUE, CONN:execute (cmd)) end