/* ** LuaSQL, PostgreSQL driver ** Authors: Pedro Rabinovitch, Roberto Ierusalimschy, Carlos Cassino ** Tomas Guisasola, Eduardo Quintao ** See Copyright Notice in license.html ** $Id: ls_postgres.c,v 1.9 2007/06/18 01:22:45 tomas Exp $ */ #include #include #include #include #include #include "libpq-fe.h" #include "lua.h" #include "lauxlib.h" #if ! defined (LUA_VERSION_NUM) || LUA_VERSION_NUM < 501 #include "compat-5.1.h" #endif #include "luasql.h" #define LUASQL_ENVIRONMENT_PG "PostgreSQL environment" #define LUASQL_CONNECTION_PG "PostgreSQL connection" #define LUASQL_CURSOR_PG "PostgreSQL cursor" typedef struct { short closed; } env_data; typedef struct { short closed; int env; /* reference to environment */ int auto_commit; /* 0 for manual commit */ PGconn *pg_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 */ int curr_tuple; /* next tuple to be read */ PGresult *pg_res; } cur_data; typedef void (*creator) (lua_State *L, cur_data *cur); LUASQL_API int luaopen_luasql_postgres(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_PG); 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_PG); 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_PG); luaL_argcheck (L, cur != NULL, 1, LUASQL_PREFIX"cursor expected"); luaL_argcheck (L, !cur->closed, 1, LUASQL_PREFIX"cursor is closed"); return cur; } /* ** Push the value of #i field of #tuple row. */ static void pushvalue (lua_State *L, PGresult *res, int tuple, int i) { if (PQgetisnull (res, tuple, i-1)) lua_pushnil (L); else lua_pushstring (L, PQgetvalue (res, tuple, i-1)); } /* ** Get another row of the given cursor. */ static int cur_fetch (lua_State *L) { cur_data *cur = getcursor (L); PGresult *res = cur->pg_res; int tuple = cur->curr_tuple; if (tuple >= PQntuples(cur->pg_res)) { lua_pushnil(L); /* no more results */ return 1; } cur->curr_tuple++; 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 = 1; i <= cur->numcols; i++) { pushvalue (L, res, tuple, i); lua_rawseti (L, 2, i); } if (strchr (opts, 'a') != NULL) /* Copy values to alphanumerical indices */ for (i = 1; i <= cur->numcols; i++) { lua_pushstring (L, PQfname (res, i-1)); pushvalue (L, res, tuple, 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 = 1; i <= cur->numcols; i++) pushvalue (L, res, tuple, i); return cur->numcols; /* return #numcols values */ } } /* ** Close the cursor on top of the stack. ** Return 1 */ static int cur_close (lua_State *L) { cur_data *cur = (cur_data *)luaL_checkudata (L, 1, LUASQL_CURSOR_PG); 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; PQclear(cur->pg_res); luaL_unref (L, LUA_REGISTRYINDEX, cur->conn); luaL_unref (L, LUA_REGISTRYINDEX, cur->colnames); luaL_unref (L, LUA_REGISTRYINDEX, cur->coltypes); lua_pushboolean (L, 1); return 1; } /* ** Get the internal database type of the given column. */ static char *getcolumntype (PGconn *conn, PGresult *result, int i, char *buff) { Oid codigo = PQftype (result, i); char stmt[100]; PGresult *res; sprintf (stmt, "select typname from pg_type where oid = %d", codigo); res = PQexec(conn, stmt); strcpy (buff, "undefined"); if (PQresultStatus (res) == PGRES_TUPLES_OK) { if (PQntuples(res) > 0) { char *name = PQgetvalue(res, 0, 0); if (strcmp (name, "bpchar")==0 || strcmp (name, "varchar")==0) { int modifier = PQfmod (result, i) - 4; sprintf (buff, "%.20s (%d)", name, modifier); } else strncpy (buff, name, 20); } } PQclear(res); return buff; } /* ** Creates the list of fields names and pushes it on top of the stack. */ static void create_colnames (lua_State *L, cur_data *cur) { PGresult *result = cur->pg_res; int i; lua_newtable (L); for (i = 1; i <= cur->numcols; i++) { lua_pushstring (L, PQfname (result, i-1)); lua_rawseti (L, -2, i); } } /* ** Creates the list of fields types and pushes it on top of the stack. */ static void create_coltypes (lua_State *L, cur_data *cur) { PGresult *result = cur->pg_res; conn_data *conn; char typename[100]; int i; lua_rawgeti (L, LUA_REGISTRYINDEX, cur->conn); if (!lua_isuserdata (L, -1)) luaL_error (L, LUASQL_PREFIX"invalid connection"); conn = (conn_data *)lua_touserdata (L, -1); lua_newtable (L); for (i = 1; i <= cur->numcols; i++) { lua_pushstring(L, getcolumntype (conn->pg_conn, result, i-1, typename)); lua_rawseti (L, -2, i); } } /* ** Pushes a column information table on top of the stack. ** 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, creator func) { int *ref = (int *)((char *)cur + off); if (*ref != LUA_NOREF) lua_rawgeti (L, LUA_REGISTRYINDEX, *ref); else { func (L, cur); /* Stores a reference to it on the cursor structure */ lua_pushvalue (L, -1); *ref = luaL_ref (L, LUA_REGISTRYINDEX); } } #define pushtable(L,c,m,f) (_pushtable(L,c,offsetof(cur_data,m),f)) /* ** Return the list of field names. */ static int cur_getcolnames (lua_State *L) { pushtable (L, getcursor(L), colnames, create_colnames); return 1; } /* ** Return the list of field types. */ static int cur_getcoltypes (lua_State *L) { pushtable (L, getcursor(L), coltypes, create_coltypes); return 1; } /* ** Push the number of rows. */ static int cur_numrows (lua_State *L) { lua_pushnumber (L, PQntuples (getcursor(L)->pg_res)); return 1; } /* ** Create a new Cursor object and push it on top of the stack. */ static int create_cursor (lua_State *L, int conn, PGresult *result) { cur_data *cur = (cur_data *)lua_newuserdata(L, sizeof(cur_data)); luasql_setmeta (L, LUASQL_CURSOR_PG); /* fill in structure */ cur->closed = 0; cur->conn = LUA_NOREF; cur->numcols = PQnfields(result); cur->colnames = LUA_NOREF; cur->coltypes = LUA_NOREF; cur->curr_tuple = 0; cur->pg_res = result; lua_pushvalue (L, conn); cur->conn = luaL_ref (L, LUA_REGISTRYINDEX); return 1; } static void sql_commit(conn_data *conn) { PQclear(PQexec(conn->pg_conn, "COMMIT")); } static void sql_begin(conn_data *conn) { PQclear(PQexec(conn->pg_conn, "BEGIN")); } static void sql_rollback(conn_data *conn) { PQclear(PQexec(conn->pg_conn, "ROLLBACK")); } /* ** Close a Connection object. */ static int conn_close (lua_State *L) { conn_data *conn = (conn_data *)luaL_checkudata (L, 1, LUASQL_CONNECTION_PG); luaL_argcheck (L, conn != NULL, 1, LUASQL_PREFIX"connection expected"); if (conn->closed) { lua_pushboolean (L, 0); return 1; } /* Nullify structure fields. */ conn->closed = 1; luaL_unref (L, LUA_REGISTRYINDEX, conn->env); PQfinish (conn->pg_conn); lua_pushboolean (L, 1); return 1; } /* ** Escapes a string for use within an SQL statement. ** Returns a string with the escaped string. */ static int conn_escape (lua_State *L) { conn_data *conn = getconnection (L); size_t len; const char *from = luaL_checklstring (L, 2, &len); char to[len*2+1]; int error; len = PQescapeStringConn (conn->pg_conn, to, from, len, &error); if (error == 0) { /* success ! */ lua_pushlstring (L, to, len); return 1; } else return luasql_faildirect (L, PQerrorMessage (conn->pg_conn)); } /* ** 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) { conn_data *conn = getconnection (L); const char *statement = luaL_checkstring (L, 2); PGresult *res = PQexec(conn->pg_conn, statement); if (res && PQresultStatus(res)==PGRES_COMMAND_OK) { /* no tuples returned */ lua_pushnumber(L, atof(PQcmdTuples(res))); PQclear (res); return 1; } else if (res && PQresultStatus(res)==PGRES_TUPLES_OK) /* tuples returned */ return create_cursor (L, 1, res); else { /* error */ PQclear (res); return luasql_faildirect(L, PQerrorMessage(conn->pg_conn)); } } /* ** Commit the current transaction. */ static int conn_commit (lua_State *L) { conn_data *conn = getconnection (L); sql_commit(conn); if (conn->auto_commit == 0) { sql_begin(conn); lua_pushboolean (L, 1); } else lua_pushboolean (L, 0); return 1; } /* ** Rollback the current transaction. */ static int conn_rollback (lua_State *L) { conn_data *conn = getconnection (L); sql_rollback(conn); if (conn->auto_commit == 0) { sql_begin(conn); lua_pushboolean (L, 1); } else lua_pushboolean (L, 0); return 1; } /* ** Set "auto commit" property of the connection. ** If 'true', then rollback current transaction. ** If 'false', then start a new transaction. */ static int conn_setautocommit (lua_State *L) { conn_data *conn = getconnection (L); if (lua_toboolean (L, 2)) { conn->auto_commit = 1; sql_rollback(conn); /* Undo active transaction. */ } else { conn->auto_commit = 0; sql_begin(conn); } lua_pushboolean(L, 1); return 1; } /* ** Create a new Connection object and push it on top of the stack. */ static int create_connection (lua_State *L, int env, PGconn *const pg_conn) { conn_data *conn = (conn_data *)lua_newuserdata(L, sizeof(conn_data)); luasql_setmeta (L, LUASQL_CONNECTION_PG); /* fill in structure */ conn->closed = 0; conn->env = LUA_NOREF; conn->auto_commit = 1; conn->pg_conn = pg_conn; lua_pushvalue (L, env); conn->env = luaL_ref (L, LUA_REGISTRYINDEX); return 1; } static void notice_processor (void *arg, const char *message) { (void)arg; (void)message; /* arg == NULL */ } /* ** Connects to a data source. ** This driver provides two ways to connect to a data source: ** (1) giving the connection parameters as a set of pairs separated ** by whitespaces in a string (first method parameter) ** (2) giving one string for each connection parameter, said ** datasource, username, password, host and port. */ static int env_connect (lua_State *L) { const char *sourcename = luaL_checkstring(L, 2); PGconn *conn; getenvironment (L); /* validate environment */ if ((lua_gettop (L) == 2) && (strchr (sourcename, '=') != NULL)) conn = PQconnectdb (sourcename); else { const char *username = luaL_optstring(L, 3, NULL); const char *password = luaL_optstring(L, 4, NULL); const char *pghost = luaL_optstring(L, 5, NULL); const char *pgport = luaL_optstring(L, 6, NULL); conn = PQsetdbLogin(pghost, pgport, NULL, NULL, sourcename, username, password); } if (PQstatus(conn) == CONNECTION_BAD) return luasql_faildirect(L, LUASQL_PREFIX"Error connecting to database."); PQsetNoticeProcessor(conn, notice_processor, NULL); return create_connection(L, 1, conn); } /* ** Close environment object. */ static int env_close (lua_State *L) { env_data *env = (env_data *)luaL_checkudata (L, 1, LUASQL_ENVIRONMENT_PG); luaL_argcheck (L, env != NULL, 1, LUASQL_PREFIX"environment expected"); if (env->closed) { lua_pushboolean (L, 0); return 1; } env->closed = 1; lua_pushboolean (L, 1); return 1; } /* ** Create metatables for each class of object. */ static void create_metatables (lua_State *L) { struct luaL_reg environment_methods[] = { {"close", env_close}, {"connect", env_connect}, {NULL, NULL}, }; struct luaL_reg connection_methods[] = { {"close", conn_close}, {"escape", conn_escape}, {"execute", conn_execute}, {"commit", conn_commit}, {"rollback", conn_rollback}, {"setautocommit", conn_setautocommit}, {NULL, NULL}, }; struct luaL_reg cursor_methods[] = { {"close", cur_close}, {"getcolnames", cur_getcolnames}, {"getcoltypes", cur_getcoltypes}, {"fetch", cur_fetch}, {"numrows", cur_numrows}, {NULL, NULL}, }; luasql_createmeta (L, LUASQL_ENVIRONMENT_PG, environment_methods); luasql_createmeta (L, LUASQL_CONNECTION_PG, connection_methods); luasql_createmeta (L, LUASQL_CURSOR_PG, cursor_methods); lua_pop (L, 3); } /* ** Creates an Environment and returns it. */ static int create_environment (lua_State *L) { env_data *env = (env_data *)lua_newuserdata(L, sizeof(env_data)); luasql_setmeta (L, LUASQL_ENVIRONMENT_PG); /* fill in structure */ env->closed = 0; return 1; } /* ** Creates the metatables for the objects and registers the ** driver open method. */ LUASQL_API int luaopen_luasql_postgres (lua_State *L) { struct luaL_reg driver[] = { {"postgres", create_environment}, {NULL, NULL}, }; create_metatables (L); luaL_openlib (L, LUASQL_TABLENAME, driver, 0); luasql_set_info (L); return 1; }