# HG changeset patch # User nrich@ii.net # Date 1227403749 0 # Node ID 408291a6eb3ea6816710ccd708d86fac09434772 # Parent 4ff31a4ea1fb302c71d04498e24c469e0e745b57 Initial import. diff -r 4ff31a4ea1fb -r 408291a6eb3e DBI.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DBI.lua Sun Nov 23 01:29:09 2008 +0000 @@ -0,0 +1,50 @@ +#!/usr/bin/lua + +module('DBI', package.seeall) + +local name_to_module = { + MySQL = 'dbdmysql', + PostgreSQL = 'dbdpostgresql', + SQLite3 = 'dbdsqlite3', +} + +local string = require('string') + +local function available_drivers() + local available = {} + + for driver, modulefile in pairs(name_to_module) do + local m, err = pcall(require, modulefile) + + if m then + table.insert(available, driver) + end + end + + if table.maxn(available) < 1 then + return '(None)' + end + + return table.concat(available, ',') +end + +function Connect(driver, name, username, password, host, port) + local modulefile = name_to_module[driver] + + if not modulefile then + error(string.format("Driver '%s' not found. Available drivers are: %s", driver, available_drivers())) + end + + local m, err = pcall(require, modulefile) + + if not m then + error(string.format('Cannot load driver %s. Available drivers are: %s', driver, available_drivers())) + end + + local class_str = string.format('DBD.%s.Connection', driver) + + local connection_class = package.loaded[class_str] + + return connection_class.New(name, username, password, host, port) +end + diff -r 4ff31a4ea1fb -r 408291a6eb3e Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Sun Nov 23 01:29:09 2008 +0000 @@ -0,0 +1,54 @@ +CC=gcc +CFLAGS=-g -pedantic -O2 -Wall -shared -fpic -I /usr/include/lua5.1 -I /usr/include/mysql -I /usr/include/postgresql/ -I . +AR=ar rcu +RANLIB=ranlib +RM=rm -f + +COMMON_LDFLAGS= +MYSQL_LDFLAGS=$(COMMON_LDFLAGS) -lmysqlclient +PSQL_LDFLAGS=$(COMMON_LDFLAGS) -lpq +SQLITE3_LDFLAGS=$(COMMON_LDFLAGS) -lsqlite3 + +DBDMYSQL=dbdmysql.so +DBDPSQL=dbdpostgresql.so +DBDSQLITE3=dbdsqlite3.so + +MYSQL_OBJS=build/dbd_mysql_main.o build/dbd_mysql_connection.o build/dbd_mysql_statement.o +PSQL_OBJS=build/dbd_postgresql_main.o build/dbd_postgresql_connection.o build/dbd_postgresql_statement.o +SQLITE3_OBJS=build/dbd_sqlite3_main.o build/dbd_sqlite3_connection.o build/dbd_sqlite3_statement.o + +all: dbdmysql dbdpsql dbdsqlite3 + +dbdmysql: $(MYSQL_OBJS) + $(CC) $(CFLAGS) $(MYSQL_OBJS) -o $(DBDMYSQL) $(MYSQL_LDFLAGS) + +dbdpsql: $(PSQL_OBJS) + $(CC) $(CFLAGS) $(PSQL_OBJS) -o $(DBDPSQL) $(PSQL_LDFLAGS) + +dbdsqlite3: $(SQLITE3_OBJS) + $(CC) $(CFLAGS) $(SQLITE3_OBJS) -o $(DBDSQLITE3) $(SQLITE3_LDFLAGS) + +clean: + $(RM) $(MYSQL_OBJS) $(PSQL_OBJS) $(SQLITE3_OBJS) $(DBDMYSQL) $(DBDPSQL) $(DBDSQLITE3) + +build/dbd_mysql_connection.o: dbd/mysql/connection.c dbd/mysql/dbd_mysql.h dbd/common.h + $(CC) -c -o $@ $< $(CFLAGS) +build/dbd_mysql_main.o: dbd/mysql/main.c dbd/mysql/dbd_mysql.h dbd/common.h + $(CC) -c -o $@ $< $(CFLAGS) +build/dbd_mysql_statement.o: dbd/mysql/statement.c dbd/mysql/dbd_mysql.h dbd/common.h + $(CC) -c -o $@ $< $(CFLAGS) + +build/dbd_postgresql_connection.o: dbd/postgresql/connection.c dbd/postgresql/dbd_postgresql.h dbd/common.h + $(CC) -c -o $@ $< $(CFLAGS) +build/dbd_postgresql_main.o: dbd/postgresql/main.c dbd/postgresql/dbd_postgresql.h dbd/common.h + $(CC) -c -o $@ $< $(CFLAGS) +build/dbd_postgresql_statement.o: dbd/postgresql/statement.c dbd/postgresql/dbd_postgresql.h dbd/common.h + $(CC) -c -o $@ $< $(CFLAGS) + +build/dbd_sqlite3_connection.o: dbd/sqlite3/connection.c dbd/sqlite3/dbd_sqlite3.h dbd/common.h + $(CC) -c -o $@ $< $(CFLAGS) +build/dbd_sqlite3_main.o: dbd/sqlite3/main.c dbd/sqlite3/dbd_sqlite3.h dbd/common.h + $(CC) -c -o $@ $< $(CFLAGS) +build/dbd_sqlite3_statement.o: dbd/sqlite3/statement.c dbd/sqlite3/dbd_sqlite3.h dbd/common.h + $(CC) -c -o $@ $< $(CFLAGS) + diff -r 4ff31a4ea1fb -r 408291a6eb3e dbd/common.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dbd/common.h Sun Nov 23 01:29:09 2008 +0000 @@ -0,0 +1,78 @@ +#include +#include +#include + +#include +#include +#include + +#if !defined(LUA_VERSION_NUM) || (LUA_VERSION_NUM < 501) +#include +#endif + + + +#define LUA_PUSH_ATTRIB_INT(n, v) \ + lua_pushstring(L, n); \ + lua_pushinteger(L, v); \ + lua_rawset(L, -3); + +#define LUA_PUSH_ATTRIB_FLOAT(n, v) \ + lua_pushstring(L, n); \ + lua_pushnumber(L, v); \ + lua_rawset(L, -3); + +#define LUA_PUSH_ATTRIB_STRING(n, v) \ + lua_pushstring(L, n); \ + lua_pushstring(L, v); \ + lua_rawset(L, -3); + +#define LUA_PUSH_ATTRIB_BOOL(n, v) \ + lua_pushstring(L, n); \ + lua_pushboolean(L, v); \ + lua_rawset(L, -3); + +#define LUA_PUSH_ATTRIB_NIL(n) \ + lua_pushstring(L, n); \ + lua_pushnil(L); \ + lua_rawset(L, -3); + + + +#define LUA_PUSH_ARRAY_INT(n, v) \ + lua_pushinteger(L, v); \ + lua_rawseti(L, -2, n); \ + n++; + +#define LUA_PUSH_ARRAY_FLOAT(n, v) \ + lua_pushnumber(L, v); \ + lua_rawseti(L, -2, n); \ + n++; + +#define LUA_PUSH_ARRAY_STRING(n, v) \ + lua_pushstring(L, v); \ + lua_rawseti(L, -2, n); \ + n++; + +#define LUA_PUSH_ARRAY_BOOL(n, v) \ + lua_pushboolean(L, v); \ + lua_rawseti(L, -2, n); \ + n++; + +#define LUA_PUSH_ARRAY_NIL(n) \ + lua_pushnil(L); \ + lua_rawseti(L, -2, n); \ + n++; + + + +typedef enum lua_push_type { + LUA_PUSH_NIL = 0, + LUA_PUSH_INTEGER, + LUA_PUSH_NUMBER, + LUA_PUSH_STRING, + LUA_PUSH_BOOLEAN, + + LUA_PUSH_MAX +} lua_push_type_t; + diff -r 4ff31a4ea1fb -r 408291a6eb3e dbd/mysql/connection.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dbd/mysql/connection.c Sun Nov 23 01:29:09 2008 +0000 @@ -0,0 +1,122 @@ +#include "dbd_mysql.h" + +int dbd_mysql_statement_create(lua_State *L, connection_t *conn, const char *sql_query); + +static int connection_new(lua_State *L) { + int n = lua_gettop(L); + + connection_t *conn = NULL; + + const char *host = NULL; + const char *user = NULL; + const char *password = NULL; + const char *db = NULL; + int port = 0; + + const char *unix_socket = NULL; /* TODO always NULL */ + int client_flag = 0; /* TODO always 0, set flags from options table */ + + /* db, user, password, host, port */ + switch (n) { + case 5: + if (lua_isnil(L, 5) == 0) + port = luaL_checkint(L, 5); + case 4: + if (lua_isnil(L, 4) == 0) + host = luaL_checkstring(L, 4); + case 3: + if (lua_isnil(L, 3) == 0) + password = luaL_checkstring(L, 3); + case 2: + if (lua_isnil(L, 2) == 0) + user = luaL_checkstring(L, 2); + case 1: + if (lua_isnil(L, 1) == 0) + db = luaL_checkstring(L, 1); + } + + conn = (connection_t *)lua_newuserdata(L, sizeof(connection_t)); + + conn->mysql = mysql_init(NULL); + + if (mysql_real_connect(conn->mysql, host, user, password, db, port, unix_socket, client_flag)) { + luaL_getmetatable(L, DBD_MYSQL_CONNECTION); + lua_setmetatable(L, -2); + } else { + luaL_error(L, "Failed to connect to database: %s", mysql_error(conn->mysql)); + lua_pushnil(L); + } + + return 1; +} + +static int connection_close(lua_State *L) { + connection_t *conn = (connection_t *)luaL_checkudata(L, 1, DBD_MYSQL_CONNECTION); + int disconnect = 0; + + if (conn->mysql) { + mysql_close(conn->mysql); + disconnect = 1; + } + + lua_pushboolean(L, disconnect); + return 1; +} + +static int connection_ping(lua_State *L) { + connection_t *conn = (connection_t *)luaL_checkudata(L, 1, DBD_MYSQL_CONNECTION); + int err = 1; + + if (conn->mysql) { + err = mysql_ping(conn->mysql); + } + + lua_pushboolean(L, !err); + return 1; +} + +static int connection_prepare(lua_State *L) { + connection_t *conn = (connection_t *)luaL_checkudata(L, 1, DBD_MYSQL_CONNECTION); + + if (conn->mysql) { + return dbd_mysql_statement_create(L, conn, luaL_checkstring(L, 2)); + } + + lua_pushnil(L); + return 1; +} + + +static int connection_gc(lua_State *L) { + /* always close the connection */ + connection_close(L); + + return 0; +} + +static const luaL_Reg connection_methods[] = { + {"close", connection_close}, + {"ping", connection_ping}, + {"prepare", connection_prepare}, + {NULL, NULL} +}; + +static const luaL_Reg connection_class_methods[] = { + {"New", connection_new}, + {NULL, NULL} +}; + +int dbd_mysql_connection(lua_State *L) { + luaL_newmetatable(L, DBD_MYSQL_CONNECTION); + luaL_register(L, 0, connection_methods); + lua_pushvalue(L,-1); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, connection_gc); + lua_setfield(L, -2, "__gc"); + + luaL_register(L, DBD_MYSQL_CONNECTION, connection_class_methods); + + return 1; +} + diff -r 4ff31a4ea1fb -r 408291a6eb3e dbd/mysql/dbd_mysql.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dbd/mysql/dbd_mysql.h Sun Nov 23 01:29:09 2008 +0000 @@ -0,0 +1,16 @@ +#include +#include + +#define DBD_MYSQL_CONNECTION "DBD.MySQL.Connection" +#define DBD_MYSQL_STATEMENT "DBD.MySQL.Statement" + +typedef struct _connection { + MYSQL *mysql; +} connection_t; + +typedef struct _statement { + MYSQL *mysql; + MYSQL_STMT *stmt; + MYSQL_RES *metadata; +} statement_t; + diff -r 4ff31a4ea1fb -r 408291a6eb3e dbd/mysql/main.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dbd/mysql/main.c Sun Nov 23 01:29:09 2008 +0000 @@ -0,0 +1,12 @@ +#include "dbd_mysql.h" + +int dbd_mysql_connection(lua_State *L); +int dbd_mysql_statement(lua_State *L); + +int luaopen_dbdmysql(lua_State *L) { + dbd_mysql_connection(L); + dbd_mysql_statement(L); + + return 1; +} + diff -r 4ff31a4ea1fb -r 408291a6eb3e dbd/mysql/statement.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dbd/mysql/statement.c Sun Nov 23 01:29:09 2008 +0000 @@ -0,0 +1,302 @@ +#include "dbd_mysql.h" + +static lua_push_type_t mysql_to_lua_push(unsigned int mysql_type) { + lua_push_type_t lua_type; + + switch(mysql_type) { + case MYSQL_TYPE_NULL: + lua_type = LUA_PUSH_NIL; + break; + + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + lua_type = LUA_PUSH_INTEGER; + break; + + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_LONGLONG: + lua_type = LUA_PUSH_NUMBER; + break; + + default: + lua_type = LUA_PUSH_STRING; + } + + return lua_type; +} + +static int statement_close(lua_State *L) { + statement_t *statement = (statement_t *)luaL_checkudata(L, 1, DBD_MYSQL_STATEMENT); + + if (statement->metadata) { + mysql_free_result(statement->metadata); + } + + if (statement->stmt) { + mysql_stmt_close(statement->stmt); + } + + return 1; +} + +static int statement_execute(lua_State *L) { + int n = lua_gettop(L); + statement_t *statement = (statement_t *)luaL_checkudata(L, 1, DBD_MYSQL_STATEMENT); + int num_bind_params = n - 1; + + MYSQL_BIND *bind = NULL; + MYSQL_RES *metadata = NULL; + + char *error_message = NULL; + + int p; + + bind = malloc(sizeof(MYSQL_BIND) * num_bind_params); + memset(bind, 0, sizeof(MYSQL_BIND) * num_bind_params); + + for (p = 2; p <= n; p++) { + int type = lua_type(L, p); + int i = p - 2; + + const char *str = NULL; + size_t str_len; + + double num; + + switch(type) { + case LUA_TNIL: + bind[i].buffer_type = MYSQL_TYPE_NULL; + bind[i].is_null = (my_bool*)1; + break; + + case LUA_TNUMBER: + num = luaL_checknumber(L, p); + + bind[i].buffer_type = MYSQL_TYPE_DOUBLE; + bind[i].is_null = (my_bool*)0; + bind[i].buffer = (char *)# + bind[i].length = 0; + break; + + case LUA_TSTRING: + str = luaL_checklstring(L, p, &str_len); + + bind[i].buffer_type = MYSQL_TYPE_STRING; + bind[i].is_null = (my_bool*)0; + bind[i].buffer = (char *)str; + bind[i].length = &str_len; + break; + + default: + error_message = "Binding unknown or unsupported type"; + goto cleanup; + } + } + + if (mysql_stmt_bind_param(statement->stmt, bind)) { + error_message = "Error binding statement parameters: %s"; + goto cleanup; + } + + if (mysql_stmt_execute(statement->stmt)) { + error_message = "Error executing statement: %s"; + goto cleanup; + } + + metadata = mysql_stmt_result_metadata(statement->stmt); + +cleanup: + if (bind) + free(bind); + + if (error_message) { + luaL_error(L, error_message, mysql_stmt_error(statement->stmt)); + return 0; + } + + statement->metadata = metadata; + + return 1; +} + +static int statement_fetch_impl(lua_State *L, int named_columns) { + statement_t *statement = (statement_t *)luaL_checkudata(L, 1, DBD_MYSQL_STATEMENT); + int column_count; + MYSQL_BIND *bind = NULL; + const char *error_message = NULL; + + if (!statement->stmt) { + luaL_error(L, "fetch called before execute"); + lua_pushnil(L); + return 1; + } + + if (!statement->metadata) { + lua_pushnil(L); + return 1; + } + + column_count = mysql_num_fields(statement->metadata); + + if (column_count > 0) { + int i; + MYSQL_FIELD *fields; + + bind = malloc(sizeof(MYSQL_BIND) * column_count); + memset(bind, 0, sizeof(MYSQL_BIND) * column_count); + + fields = mysql_fetch_fields(statement->metadata); + + for (i = 0; i < column_count; i++) { + unsigned int length = fields[i].length; + + char *buffer = (char *)malloc(length); + memset(buffer, 0, length); + + bind[i].buffer_type = fields[i].type; + bind[i].buffer = buffer; + bind[i].buffer_length = length; + } + + if (mysql_stmt_bind_result(statement->stmt, bind)) { + error_message = "Error binding results: %s"; + goto cleanup; + } + + if (!mysql_stmt_fetch(statement->stmt)) { + int d = 1; + + lua_newtable(L); + for (i = 0; i < column_count; i++) { + lua_push_type_t lua_push = mysql_to_lua_push(fields[i].type); + const char *name = fields[i].name; + + if (lua_push == LUA_PUSH_NIL) { + if (named_columns) { + LUA_PUSH_ATTRIB_NIL(name); + } else { + LUA_PUSH_ARRAY_NIL(d); + } + } else if (lua_push == LUA_PUSH_INTEGER) { + if (named_columns) { + LUA_PUSH_ATTRIB_INT(name, *(int *)(bind[i].buffer)); + } else { + LUA_PUSH_ARRAY_INT(d, *(int *)(bind[i].buffer)); + } + } else if (lua_push == LUA_PUSH_NUMBER) { + if (named_columns) { + LUA_PUSH_ATTRIB_FLOAT(name, *(double *)(bind[i].buffer)); + } else { + LUA_PUSH_ARRAY_FLOAT(d, *(double *)(bind[i].buffer)); + } + } else if (lua_push == LUA_PUSH_STRING) { + if (named_columns) { + LUA_PUSH_ATTRIB_STRING(name, bind[i].buffer); + } else { + LUA_PUSH_ARRAY_STRING(d, bind[i].buffer); + } + } else if (lua_push == LUA_PUSH_BOOLEAN) { + if (named_columns) { + LUA_PUSH_ATTRIB_BOOL(name, *(int *)(bind[i].buffer)); + } else { + LUA_PUSH_ARRAY_BOOL(d, *(int *)(bind[i].buffer)); + } + } else { + luaL_error(L, "Unknown push type in result set"); + } + } + } else { + lua_pushnil(L); + } + } + +cleanup: + if (bind) { + int i; + + for (i = 0; i < column_count; i++) { + free(bind[i].buffer); + } + + free(bind); + } + + if (error_message) { + luaL_error(L, error_message, mysql_stmt_error(statement->stmt)); + return 0; + } + + return 1; +} + + +static int statement_fetch(lua_State *L) { + return statement_fetch_impl(L, 0); +} + +static int statement_fetchtable(lua_State *L) { + return statement_fetch_impl(L, 1); +} + +static int statement_gc(lua_State *L) { + /* always free the handle */ + statement_close(L); + + return 0; +} + + +static const luaL_Reg statement_methods[] = { + {"close", statement_close}, + {"execute", statement_execute}, + {"fetch", statement_fetch}, + {"fetchtable", statement_fetchtable}, + {NULL, NULL} +}; + +static const luaL_Reg statement_class_methods[] = { + {NULL, NULL} +}; + +int dbd_mysql_statement_create(lua_State *L, connection_t *conn, const char *sql_query) { + unsigned long sql_len = strlen(sql_query); + + statement_t *statement = NULL; + + MYSQL_STMT *stmt = mysql_stmt_init(conn->mysql); + + if (!stmt) { + luaL_error(L, "Error allocating statement handle: %s", mysql_error(conn->mysql)); + return 0; + } + + if (mysql_stmt_prepare(stmt, sql_query, sql_len)) { + luaL_error(L, "Error preparing statement handle: %s", mysql_stmt_error(stmt)); + return 0; + } + + statement = (statement_t *)lua_newuserdata(L, sizeof(statement_t)); + statement->mysql = conn->mysql; + statement->stmt = stmt; + statement->metadata = NULL; + + luaL_getmetatable(L, DBD_MYSQL_STATEMENT); + lua_setmetatable(L, -2); + + return 1; +} + +int dbd_mysql_statement(lua_State *L) { + luaL_newmetatable(L, DBD_MYSQL_STATEMENT); + luaL_register(L, 0, statement_methods); + lua_pushvalue(L,-1); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, statement_gc); + lua_setfield(L, -2, "__gc"); + + luaL_register(L, DBD_MYSQL_STATEMENT, statement_class_methods); + + return 1; +} diff -r 4ff31a4ea1fb -r 408291a6eb3e dbd/postgresql/connection.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dbd/postgresql/connection.c Sun Nov 23 01:29:09 2008 +0000 @@ -0,0 +1,135 @@ +#include "dbd_postgresql.h" + +int dbd_postgresql_statement_create(lua_State *L, connection_t *conn, const char *sql_query); + +static int connection_new(lua_State *L) { + int n = lua_gettop(L); + connection_t *conn = NULL; + + const char *host = NULL; + const char *user = NULL; + const char *password = NULL; + const char *db = NULL; + const char *port = NULL; + + const char *options = NULL; /* TODO always NULL */ + const char *tty = NULL; /* TODO always NULL */ + + char portbuf[18]; + + /* db, user, password, host, port */ + switch (n) { + case 5: + if (lua_isnil(L, 5) == 0) + { + int pport = luaL_checkint(L, 5); + + if (pport >= 1 && pport <= 65535) { + snprintf(portbuf, sizeof(portbuf), "%d", pport); + port = portbuf; + } else { + luaL_error(L, "Invalid port %d", pport); + } + } + case 4: + if (lua_isnil(L, 4) == 0) + host = luaL_checkstring(L, 4); + case 3: + if (lua_isnil(L, 3) == 0) + password = luaL_checkstring(L, 3); + case 2: + if (lua_isnil(L, 2) == 0) + user = luaL_checkstring(L, 2); + case 1: + if (lua_isnil(L, 1) == 0) + db = luaL_checkstring(L, 1); + } + + conn = (connection_t *)lua_newuserdata(L, sizeof(connection_t)); + + conn->postgresql = PQsetdbLogin(host, port, options, tty, db, user, password); + conn->statement_id = 0; + + if (PQstatus(conn->postgresql) == CONNECTION_OK) { + luaL_getmetatable(L, DBD_POSTGRESQL_CONNECTION); + lua_setmetatable(L, -2); + } else { + luaL_error(L, "Failed to connect to database: %s", PQerrorMessage(conn->postgresql)); + lua_pushnil(L); + } + + return 1; +} + +static int connection_close(lua_State *L) { + connection_t *conn = (connection_t *)luaL_checkudata(L, 1, DBD_POSTGRESQL_CONNECTION); + int disconnect = 0; + + if (conn->postgresql) { + PQfinish(conn->postgresql); + disconnect = 1; + } + + lua_pushboolean(L, disconnect); + return 1; +} + +static int connection_ping(lua_State *L) { + connection_t *conn = (connection_t *)luaL_checkudata(L, 1, DBD_POSTGRESQL_CONNECTION); + int ok = 0; + + if (conn->postgresql) { + ConnStatusType status = PQstatus(conn->postgresql); + + if (status == CONNECTION_OK) + ok = 1; + } + + lua_pushboolean(L, ok); + return 1; +} + +static int connection_prepare(lua_State *L) { + connection_t *conn = (connection_t *)luaL_checkudata(L, 1, DBD_POSTGRESQL_CONNECTION); + + if (conn->postgresql) { + return dbd_postgresql_statement_create(L, conn, luaL_checkstring(L, 2)); + } + + lua_pushnil(L); + return 1; +} + + +static int connection_gc(lua_State *L) { + /* always close the connection */ + connection_close(L); + + return 0; +} + +static const luaL_Reg connection_methods[] = { + {"close", connection_close}, + {"ping", connection_ping}, + {"prepare", connection_prepare}, + {NULL, NULL} +}; + +static const luaL_Reg connection_class_methods[] = { + {"New", connection_new}, + {NULL, NULL} +}; + +int dbd_postgresql_connection(lua_State *L) { + luaL_newmetatable(L, DBD_POSTGRESQL_CONNECTION); + luaL_register(L, 0, connection_methods); + lua_pushvalue(L,-1); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, connection_gc); + lua_setfield(L, -2, "__gc"); + + luaL_register(L, DBD_POSTGRESQL_CONNECTION, connection_class_methods); + + return 1; +} diff -r 4ff31a4ea1fb -r 408291a6eb3e dbd/postgresql/dbd_postgresql.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dbd/postgresql/dbd_postgresql.h Sun Nov 23 01:29:09 2008 +0000 @@ -0,0 +1,22 @@ +#include +#include +#include +#include + +#define IDLEN 18 + +#define DBD_POSTGRESQL_CONNECTION "DBD.PostgreSQL.Connection" +#define DBD_POSTGRESQL_STATEMENT "DBD.PostgreSQL.Statement" + +typedef struct _connection { + PGconn *postgresql; + unsigned int statement_id; +} connection_t; + +typedef struct _statement { + PGconn *postgresql; + PGresult *result; + char name[IDLEN]; + int tuple; +} statement_t; + diff -r 4ff31a4ea1fb -r 408291a6eb3e dbd/postgresql/main.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dbd/postgresql/main.c Sun Nov 23 01:29:09 2008 +0000 @@ -0,0 +1,12 @@ +#include "dbd_postgresql.h" + +int dbd_postgresql_connection(lua_State *L); +int dbd_postgresql_statement(lua_State *L); + +int luaopen_dbdpostgresql(lua_State *L) { + dbd_postgresql_connection(L); + dbd_postgresql_statement(L); + + return 1; +} + diff -r 4ff31a4ea1fb -r 408291a6eb3e dbd/postgresql/statement.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dbd/postgresql/statement.c Sun Nov 23 01:29:09 2008 +0000 @@ -0,0 +1,319 @@ +#include "dbd_postgresql.h" + +#define MAX_PLACEHOLDERS 9999 +#define MAX_PLACEHOLDER_SIZE (1+4) /* $\d{4} */ + +static lua_push_type_t postgresql_to_lua_push(unsigned int postgresql_type) { + lua_push_type_t lua_type; + + switch(postgresql_type) { + case INT2OID: + case INT4OID: + lua_type = LUA_PUSH_INTEGER; + break; + + case FLOAT4OID: + case FLOAT8OID: + lua_type = LUA_PUSH_NUMBER; + break; + + case BOOLOID: + lua_type = LUA_PUSH_BOOLEAN; + break; + + default: + lua_type = LUA_PUSH_STRING; + } + + return lua_type; +} + +static char *replace_placeholders(lua_State *L, const char *sql) { + size_t len = strlen(sql); + int num_placeholders = 0; + int extra_space = 0; + int i; + char *newsql; + int newpos = 1; + int ph_num = 1; + int in_quote = 0; + + for (i = 1; i < len; i++) { + if (sql[i] == '?') { + num_placeholders++; + } + } + + extra_space = num_placeholders * (MAX_PLACEHOLDER_SIZE-1); + + newsql = malloc(sizeof(char) * (len+extra_space+1)); + memset(newsql, 0, sizeof(char) * (len+extra_space+1)); + + /* copy first char */ + newsql[0] = sql[0]; + for (i = 1; i < len; i++) { + if (sql[i] == '\'' && sql[i-1] != '\\') { + in_quote = !in_quote; + } + + if (sql[i] == '?' && !in_quote) { + size_t n; + + if (ph_num > MAX_PLACEHOLDERS) { + luaL_error(L, "Sorry, you are using more than %d placeholders. Use ${num} format instead", MAX_PLACEHOLDERS); + } + + n = snprintf(&newsql[newpos], MAX_PLACEHOLDER_SIZE, "$%u", ph_num++); + + newpos += n; + } else { + newsql[newpos] = sql[i]; + newpos++; + } + } + + /* terminate string on the last position */ + newsql[newpos] = '\0'; + + /* fprintf(stderr, "[%s]\n", newsql); */ + return newsql; +} + +static int statement_close(lua_State *L) { + statement_t *statement = (statement_t *)luaL_checkudata(L, 1, DBD_POSTGRESQL_STATEMENT); + + if (statement->result) { + PQclear(statement->result); + statement->result = NULL; + } + + return 0; +} + +static int statement_execute(lua_State *L) { + int n = lua_gettop(L); + statement_t *statement = (statement_t *)luaL_checkudata(L, 1, DBD_POSTGRESQL_STATEMENT); + int num_bind_params = n - 1; + ExecStatusType status; + int p; + + char **params; + PGresult *result = NULL; + + statement->tuple = 0; + + params = malloc(num_bind_params * sizeof(params)); + + for (p = 2; p <= n; p++) { + int i = p - 2; + + if (lua_isnil(L, p)) { + params[i] = NULL; + } else { + const char *param = lua_tostring(L, p); + size_t len = strlen(param) + 1; + + params[i] = malloc(len * sizeof(char)); + memset(params[i], 0, len); + + strncpy(params[i], param, len); + params[i][len] = '\0'; + } + } + + result = PQexecPrepared( + statement->postgresql, + statement->name, + num_bind_params, + (const char **)params, + NULL, + NULL, + 0 + ); + + for (p = 0; p < num_bind_params; p++) { + if (params[p]) { + free(params[p]); + } + } + free(params); + + if (!result) { + luaL_error(L, "Unable to allocate result handle"); + lua_pushboolean(L, 0); + return 1; + } + + status = PQresultStatus(result); + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + luaL_error(L, "Unable to execute statment: %s", PQresultErrorMessage(result)); + lua_pushboolean(L, 0); + return 1; + } + + statement->result = result; + + lua_pushboolean(L, 1); + return 1; +} + +static int statement_fetch_impl(lua_State *L, int named_columns) { + statement_t *statement = (statement_t *)luaL_checkudata(L, 1, DBD_POSTGRESQL_STATEMENT); + int tuple = statement->tuple++; + int i; + int num_columns; + + if (PQresultStatus(statement->result) != PGRES_TUPLES_OK) { + lua_pushnil(L); + return 1; + } + + if (tuple >= PQntuples(statement->result)) { + lua_pushnil(L); /* no more results */ + return 1; + } + + num_columns = PQnfields(statement->result); + lua_newtable(L); + for (i = 0; i < num_columns; i++) { + int d = 1; + const char *name = PQfname(statement->result, i); + + if (PQgetisnull(statement->result, tuple, i)) { + if (named_columns) { + LUA_PUSH_ATTRIB_NIL(name); + } else { + LUA_PUSH_ARRAY_NIL(d); + } + } else { + const char *value = PQgetvalue(statement->result, tuple, i); + lua_push_type_t lua_push = postgresql_to_lua_push(PQftype(statement->result, i)); + + if (lua_push == LUA_PUSH_NIL) { + if (named_columns) { + LUA_PUSH_ATTRIB_NIL(name); + } else { + LUA_PUSH_ARRAY_NIL(d); + } + } else if (lua_push == LUA_PUSH_INTEGER) { + int val = atoi(value); + + if (named_columns) { + LUA_PUSH_ATTRIB_INT(name, val); + } else { + LUA_PUSH_ARRAY_INT(d, val); + } + } else if (lua_push == LUA_PUSH_NUMBER) { + double val = strtod(value, NULL); + + if (named_columns) { + LUA_PUSH_ATTRIB_FLOAT(name, val); + } else { + LUA_PUSH_ARRAY_FLOAT(d, val); + } + } else if (lua_push == LUA_PUSH_STRING) { + if (named_columns) { + LUA_PUSH_ATTRIB_STRING(name, value); + } else { + LUA_PUSH_ARRAY_STRING(d, value); + } + } else if (lua_push == LUA_PUSH_BOOLEAN) { + int val = value[0] == 't' ? 1 : 0; + + if (named_columns) { + LUA_PUSH_ATTRIB_BOOL(name, val); + } else { + LUA_PUSH_ARRAY_BOOL(d, val); + } + } else { + luaL_error(L, "Unknown push type in result set"); + } + } + } + + return 1; +} + + +static int statement_fetch(lua_State *L) { + return statement_fetch_impl(L, 0); +} + +static int statement_fetchtable(lua_State *L) { + return statement_fetch_impl(L, 1); +} + +static int statement_gc(lua_State *L) { + /* always free the handle */ + statement_close(L); + + return 0; +} + + +static const luaL_Reg statement_methods[] = { + {"close", statement_close}, + {"execute", statement_execute}, + {"fetch", statement_fetch}, + {"fetchtable", statement_fetchtable}, + {NULL, NULL} +}; + +static const luaL_Reg statement_class_methods[] = { + {NULL, NULL} +}; + +int dbd_postgresql_statement_create(lua_State *L, connection_t *conn, const char *sql_query) { + statement_t *statement = NULL; + ExecStatusType status; + PGresult *result = NULL; + char *new_sql; + char name[IDLEN]; + + new_sql = replace_placeholders(L, sql_query); + + snprintf(name, IDLEN, "%017u", ++conn->statement_id); + + result = PQprepare(conn->postgresql, name, new_sql, 0, NULL); + free(new_sql); + + if (!result) { + luaL_error(L, "Unable to allocate prepare result handle"); + } + + status = PQresultStatus(result); + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + const char *err_string = PQresultErrorMessage(result); + PQclear(result); + luaL_error(L, "Unable to prepare statment: %s", err_string); + return 0; + } + + PQclear(result); + + statement = (statement_t *)lua_newuserdata(L, sizeof(statement_t)); + statement->postgresql = conn->postgresql; + statement->result = NULL; + statement->tuple = 0; + strncpy(statement->name, name, IDLEN-1); + statement->name[IDLEN-1] = '\0'; + + luaL_getmetatable(L, DBD_POSTGRESQL_STATEMENT); + lua_setmetatable(L, -2); + + return 1; +} + +int dbd_postgresql_statement(lua_State *L) { + luaL_newmetatable(L, DBD_POSTGRESQL_STATEMENT); + luaL_register(L, 0, statement_methods); + lua_pushvalue(L,-1); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, statement_gc); + lua_setfield(L, -2, "__gc"); + + luaL_register(L, DBD_POSTGRESQL_STATEMENT, statement_class_methods); + + return 1; +} diff -r 4ff31a4ea1fb -r 408291a6eb3e dbd/sqlite3/connection.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dbd/sqlite3/connection.c Sun Nov 23 01:29:09 2008 +0000 @@ -0,0 +1,107 @@ +#include "dbd_sqlite3.h" + +int dbd_sqlite3_statement_create(lua_State *L, connection_t *conn, const char *sql_query); + +static int connection_new(lua_State *L) { + int n = lua_gettop(L); + + const char *db = NULL; + connection_t *conn = NULL; + + /* db */ + switch (n) { + default: + if (lua_isnil(L, 1) == 0) + db = luaL_checkstring(L, 1); + } + + conn = (connection_t *)lua_newuserdata(L, sizeof(connection_t)); + + if (sqlite3_open(db, &conn->sqlite) == SQLITE_OK) { + luaL_getmetatable(L, DBD_SQLITE_CONNECTION); + lua_setmetatable(L, -2); + } else { + luaL_error(L, "Failed to connect to database: %s", sqlite3_errmsg(conn->sqlite)); + lua_pushnil(L); + } + + return 1; +} + +static int connection_close(lua_State *L) { + connection_t *conn = (connection_t *)luaL_checkudata(L, 1, DBD_SQLITE_CONNECTION); + int disconnect = 0; + + if (conn->sqlite) { +#if 0 + sqlite3_stmt *stmt; + + while((stmt = sqlite3_next_stmt(conn->sqlite, NULL)) != 0){ + sqlite3_finalize(stmt); + } +#endif + if (sqlite3_close(conn->sqlite) == SQLITE_OK) { + disconnect = 1; + } + } + + lua_pushboolean(L, disconnect); + return 1; +} + +static int connection_ping(lua_State *L) { + connection_t *conn = (connection_t *)luaL_checkudata(L, 1, DBD_SQLITE_CONNECTION); + int ok = 0; + + if (conn->sqlite) { + ok = 1; + } + + lua_pushboolean(L, ok); + return 1; +} + +static int connection_prepare(lua_State *L) { + connection_t *conn = (connection_t *)luaL_checkudata(L, 1, DBD_SQLITE_CONNECTION); + + if (conn->sqlite) { + return dbd_sqlite3_statement_create(L, conn, luaL_checkstring(L, 2)); + } + + lua_pushnil(L); + return 1; +} + + +static int connection_gc(lua_State *L) { + /* always close the connection */ + connection_close(L); + + return 0; +} + +static const luaL_Reg connection_methods[] = { + {"close", connection_close}, + {"ping", connection_ping}, + {"prepare", connection_prepare}, + {NULL, NULL} +}; + +static const luaL_Reg connection_class_methods[] = { + {"New", connection_new}, + {NULL, NULL} +}; + +int dbd_sqlite3_connection(lua_State *L) { + luaL_newmetatable(L, DBD_SQLITE_CONNECTION); + luaL_register(L, 0, connection_methods); + lua_pushvalue(L,-1); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, connection_gc); + lua_setfield(L, -2, "__gc"); + + luaL_register(L, DBD_SQLITE_CONNECTION, connection_class_methods); + + return 1; +} diff -r 4ff31a4ea1fb -r 408291a6eb3e dbd/sqlite3/dbd_sqlite3.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dbd/sqlite3/dbd_sqlite3.h Sun Nov 23 01:29:09 2008 +0000 @@ -0,0 +1,16 @@ +#include +#include + +#define DBD_SQLITE_CONNECTION "DBD.SQLite3.Connection" +#define DBD_SQLITE_STATEMENT "DBD.SQLite3.Statement" + +typedef struct _connection { + sqlite3 *sqlite; +} connection_t; + +typedef struct _statement { + sqlite3_stmt *stmt; + sqlite3 *sqlite; + int more_data; +} statement_t; + diff -r 4ff31a4ea1fb -r 408291a6eb3e dbd/sqlite3/main.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dbd/sqlite3/main.c Sun Nov 23 01:29:09 2008 +0000 @@ -0,0 +1,12 @@ +#include "dbd_sqlite3.h" + +int dbd_sqlite3_connection(lua_State *L); +int dbd_sqlite3_statement(lua_State *L); + +int luaopen_dbdsqlite3(lua_State *L) { + dbd_sqlite3_connection(L); + dbd_sqlite3_statement(L); + + return 1; +} + diff -r 4ff31a4ea1fb -r 408291a6eb3e dbd/sqlite3/statement.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dbd/sqlite3/statement.c Sun Nov 23 01:29:09 2008 +0000 @@ -0,0 +1,222 @@ +#include "dbd_sqlite3.h" + +static lua_push_type_t sqlite_to_lua_push(unsigned int sqlite_type) { + lua_push_type_t lua_type; + + switch(sqlite_type) { + case SQLITE_NULL: + lua_type = LUA_PUSH_NIL; + break; + + case SQLITE_INTEGER: + lua_type = LUA_PUSH_INTEGER; + break; + + case SQLITE_FLOAT: + lua_type = LUA_PUSH_NUMBER; + break; + + default: + lua_type = LUA_PUSH_STRING; + } + + return lua_type; +} + +static int step(statement_t *statement) { + int res = sqlite3_step(statement->stmt); + + if (res == SQLITE_DONE) { + statement->more_data = 0; + return 1; + } else if (res == SQLITE_ROW) { + statement->more_data = 1; + return 1; + } + + return 0; +} + +static int statement_close(lua_State *L) { + statement_t *statement = (statement_t *)luaL_checkudata(L, 1, DBD_SQLITE_STATEMENT); + int ok = 0; + + if (statement->stmt) { + if (sqlite3_finalize(statement->stmt) == SQLITE_OK) { + ok = 1; + } + } + + lua_pushboolean(L, ok); + + return 1; +} + +static int statement_execute(lua_State *L) { + int n = lua_gettop(L); + statement_t *statement = (statement_t *)luaL_checkudata(L, 1, DBD_SQLITE_STATEMENT); + int p; + + if (sqlite3_reset(statement->stmt) != SQLITE_OK) { + lua_pushboolean(L, 0); + return 1; + } + + for (p = 2; p <= n; p++) { + int i = p - 1; + + if (lua_isnil(L, p)) { + if (sqlite3_bind_null(statement->stmt, i) != SQLITE_OK) { + luaL_error(L, "Failed to execute statement: %s", sqlite3_errmsg(statement->sqlite)); + } + } else if (lua_isnumber(L, p)) { + if (sqlite3_bind_double(statement->stmt, i, luaL_checknumber(L, p)) != SQLITE_OK) { + luaL_error(L, "Failed to execute statement: %s", sqlite3_errmsg(statement->sqlite)); + } + } else if (lua_isstring(L, p)) { + if (sqlite3_bind_text(statement->stmt, i, luaL_checkstring(L, p), -1, SQLITE_STATIC) != SQLITE_OK) { + luaL_error(L, "Failed to execute statement: %s", sqlite3_errmsg(statement->sqlite)); + } + } + } + + lua_pushboolean(L, step(statement)); + return 1; +} + +static int statement_fetch_impl(lua_State *L, int named_columns) { + statement_t *statement = (statement_t *)luaL_checkudata(L, 1, DBD_SQLITE_STATEMENT); + int num_columns; + + if (!statement->more_data) { + lua_pushnil(L); + return 1; + } + + num_columns = sqlite3_column_count(statement->stmt); + + if (num_columns) { + int i; + int d = 1; + + lua_newtable(L); + + for (i = 0; i < num_columns; i++) { + lua_push_type_t lua_push = sqlite_to_lua_push(sqlite3_column_type(statement->stmt, i)); + const char *name = sqlite3_column_name(statement->stmt, i); + + if (lua_push == LUA_PUSH_NIL) { + if (named_columns) { + LUA_PUSH_ATTRIB_NIL(name); + } else { + LUA_PUSH_ARRAY_NIL(d); + } + } else if (lua_push == LUA_PUSH_INTEGER) { + int val = sqlite3_column_int(statement->stmt, i); + + if (named_columns) { + LUA_PUSH_ATTRIB_INT(name, val); + } else { + LUA_PUSH_ARRAY_INT(d, val); + } + } else if (lua_push == LUA_PUSH_NUMBER) { + double val = sqlite3_column_double(statement->stmt, i); + + if (named_columns) { + LUA_PUSH_ATTRIB_FLOAT(name, val); + } else { + LUA_PUSH_ARRAY_FLOAT(d, val); + } + } else if (lua_push == LUA_PUSH_STRING) { + const char *val = (const char *)sqlite3_column_text(statement->stmt, i); + + if (named_columns) { + LUA_PUSH_ATTRIB_STRING(name, val); + } else { + LUA_PUSH_ARRAY_STRING(d, val); + } + } else if (lua_push == LUA_PUSH_BOOLEAN) { + int val = sqlite3_column_int(statement->stmt, i); + + if (named_columns) { + LUA_PUSH_ATTRIB_BOOL(name, val); + } else { + LUA_PUSH_ARRAY_BOOL(d, val); + } + } else { + luaL_error(L, "Unknown push type in result set"); + } + } + } + + if (step(statement) == 0) { + if (sqlite3_reset(statement->stmt) != SQLITE_OK) { + luaL_error(L, "Failed to fetch statement: %s", sqlite3_errmsg(statement->sqlite)); + } + } + + return 1; +} + + +static int statement_fetch(lua_State *L) { + return statement_fetch_impl(L, 0); +} + +static int statement_fetchtable(lua_State *L) { + return statement_fetch_impl(L, 1); +} + +static int statement_gc(lua_State *L) { + /* always free the handle */ + statement_close(L); + + return 0; +} + + +static const luaL_Reg statement_methods[] = { + {"close", statement_close}, + {"execute", statement_execute}, + {"fetch", statement_fetch}, + {"fetchtable", statement_fetchtable}, + {NULL, NULL} +}; + +static const luaL_Reg statement_class_methods[] = { + {NULL, NULL} +}; + +int dbd_sqlite3_statement_create(lua_State *L, connection_t *conn, const char *sql_query) { + statement_t *statement = NULL; + + statement = (statement_t *)lua_newuserdata(L, sizeof(statement_t)); + statement->sqlite = conn->sqlite; + statement->stmt = NULL; + statement->more_data = 0; + + if (sqlite3_prepare_v2(statement->sqlite, sql_query, strlen(sql_query), &statement->stmt, NULL) != SQLITE_OK) { + luaL_error(L, "Failed to prepare statement: %s", sqlite3_errmsg(statement->sqlite)); + lua_pushnil(L); + return 1; + } + + luaL_getmetatable(L, DBD_SQLITE_STATEMENT); + lua_setmetatable(L, -2); + + return 1; +} + +int dbd_sqlite3_statement(lua_State *L) { + luaL_newmetatable(L, DBD_SQLITE_STATEMENT); + luaL_register(L, 0, statement_methods); + lua_pushvalue(L,-1); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, statement_gc); + lua_setfield(L, -2, "__gc"); + + luaL_register(L, DBD_SQLITE_STATEMENT, statement_class_methods); + + return 1; +}