/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "mod_lua.h" #include "lua_apr.h" #include "lua_dbd.h" APLOG_USE_MODULE(lua); static APR_OPTIONAL_FN_TYPE(ap_dbd_close) *lua_ap_dbd_close = NULL; static APR_OPTIONAL_FN_TYPE(ap_dbd_open) *lua_ap_dbd_open = NULL; static request_rec *ap_lua_check_request_rec(lua_State *L, int index) { request_rec *r; luaL_checkudata(L, index, "Apache2.Request"); r = lua_unboxpointer(L, index); return r; } static lua_db_handle *lua_get_db_handle(lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); lua_rawgeti(L, 1, 0); luaL_checktype(L, -1, LUA_TUSERDATA); return (lua_db_handle *) lua_topointer(L, -1); } static lua_db_result_set *lua_get_result_set(lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); lua_rawgeti(L, 1, 0); luaL_checktype(L, -1, LUA_TUSERDATA); return (lua_db_result_set *) lua_topointer(L, -1); } /* ============================================================================= db:close(): Closes an open database connection. ============================================================================= */ int lua_db_close(lua_State *L) { /*~~~~~~~~~~~~~~~~~~~~*/ lua_db_handle *db; apr_status_t rc = 0; /*~~~~~~~~~~~~~~~~~~~~*/ db = lua_get_db_handle(L); if (db && db->alive) { if (db->type == LUA_DBTYPE_APR_DBD) { rc = apr_dbd_close(db->driver, db->handle); if (db->pool) apr_pool_destroy(db->pool); } else { lua_ap_dbd_close = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_close); if (lua_ap_dbd_close != NULL) if (db->dbdhandle) lua_ap_dbd_close(db->server, db->dbdhandle); } db->driver = NULL; db->handle = NULL; db->alive = 0; db->pool = NULL; } lua_settop(L, 0); lua_pushnumber(L, rc); return 1; } /* ============================================================================= db:__gc(): Garbage collecting function. ============================================================================= */ int lua_db_gc(lua_State *L) { /*~~~~~~~~~~~~~~~~*/ lua_db_handle *db; /*~~~~~~~~~~~~~~~~~~~~*/ db = lua_touserdata(L, 1); if (db && db->alive) { if (db->type == LUA_DBTYPE_APR_DBD) { apr_dbd_close(db->driver, db->handle); if (db->pool) apr_pool_destroy(db->pool); } else { lua_ap_dbd_close = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_close); if (lua_ap_dbd_close != NULL) if (db->dbdhandle) lua_ap_dbd_close(db->server, db->dbdhandle); } db->driver = NULL; db->handle = NULL; db->alive = 0; db->pool = NULL; } lua_settop(L, 0); return 0; } /* ============================================================================= db:active(): Returns true if the connection to the db is still active. ============================================================================= */ int lua_db_active(lua_State *L) { /*~~~~~~~~~~~~~~~~~~~~*/ lua_db_handle *db = 0; apr_status_t rc = 0; /*~~~~~~~~~~~~~~~~~~~~*/ db = lua_get_db_handle(L); if (db && db->alive) { rc = apr_dbd_check_conn(db->driver, db->pool, db->handle); if (rc == APR_SUCCESS) { lua_pushboolean(L, 1); return 1; } } lua_pushboolean(L, 0); return 1; } /* ============================================================================= db:query(statement): Executes the given database query and returns the number of rows affected. If an error is encountered, returns nil as the first parameter and the error message as the second. ============================================================================= */ int lua_db_query(lua_State *L) { /*~~~~~~~~~~~~~~~~~~~~~~~*/ lua_db_handle *db = 0; apr_status_t rc = 0; int x = 0; const char *statement; /*~~~~~~~~~~~~~~~~~~~~~~~*/ luaL_checktype(L, 3, LUA_TSTRING); statement = lua_tostring(L, 3); db = lua_get_db_handle(L); if (db && db->alive) rc = apr_dbd_query(db->driver, db->handle, &x, statement); else { rc = 0; x = -1; } if (rc == APR_SUCCESS) lua_pushnumber(L, x); else { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ const char *err = apr_dbd_error(db->driver, db->handle, rc); /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ lua_pushnil(L); if (err) { lua_pushstring(L, err); return 2; } } return 1; } /* ============================================================================= db:escape(string): Escapes a string for safe use in the given database type. ============================================================================= */ int lua_db_escape(lua_State *L) { /*~~~~~~~~~~~~~~~~~~~~~*/ lua_db_handle *db = 0; const char *statement; const char *escaped = 0; request_rec *r; /*~~~~~~~~~~~~~~~~~~~~~*/ r = ap_lua_check_request_rec(L, 2); if (r) { luaL_checktype(L, 3, LUA_TSTRING); statement = lua_tostring(L, 3); db = lua_get_db_handle(L); if (db && db->alive) { apr_dbd_init(r->pool); escaped = apr_dbd_escape(db->driver, r->pool, statement, db->handle); if (escaped) { lua_pushstring(L, escaped); return 1; } } else { lua_pushnil(L); } return (1); } return 0; } /* ============================================================================= resultset(N): Fetches one or more rows from a result set. ============================================================================= */ int lua_db_get_row(lua_State *L) { int row_no,x; const char *entry; apr_dbd_row_t *row = 0; lua_db_result_set *res = lua_get_result_set(L); row_no = luaL_optinteger(L, 2, 0); lua_settop(L,0); /* Fetch all rows at once? */ if (row_no == 0) { row_no = 1; lua_newtable(L); while (apr_dbd_get_row(res->driver, res->pool, res->results, &row, -1) != -1) { lua_pushinteger(L, row_no); lua_newtable(L); for (x = 0; x < res->cols; x++) { entry = apr_dbd_get_entry(res->driver, row, x); if (entry) { lua_pushinteger(L, x + 1); lua_pushstring(L, entry); lua_rawset(L, -3); } } lua_rawset(L, -3); row_no++; } return 1; } /* Just fetch a single row */ if (apr_dbd_get_row(res->driver, res->pool, res->results, &row, row_no) != -1) { lua_newtable(L); for (x = 0; x < res->cols; x++) { entry = apr_dbd_get_entry(res->driver, row, x); if (entry) { lua_pushinteger(L, x + 1); lua_pushstring(L, entry); lua_rawset(L, -3); } } return 1; } return 0; } /* ============================================================================= db:select(statement): Queries the database for the given statement and returns the rows/columns found as a table. If an error is encountered, returns nil as the first parameter and the error message as the second. ============================================================================= */ int lua_db_select(lua_State *L) { /*~~~~~~~~~~~~~~~~~~~~~~~*/ lua_db_handle *db = 0; apr_status_t rc = 0; const char *statement; request_rec *r; /*~~~~~~~~~~~~~~~~~~~~~~~*/ r = ap_lua_check_request_rec(L, 2); if (r) { luaL_checktype(L, 3, LUA_TSTRING); statement = lua_tostring(L, 3); db = lua_get_db_handle(L); if (db && db->alive) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ int cols; apr_dbd_results_t *results = 0; lua_db_result_set* resultset = NULL; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ rc = apr_dbd_select(db->driver, db->pool, db->handle, &results, statement, 0); if (rc == APR_SUCCESS) { cols = apr_dbd_num_cols(db->driver, results); if (cols > 0) { lua_newtable(L); resultset = lua_newuserdata(L, sizeof(lua_db_result_set)); resultset->cols = cols; resultset->driver = db->driver; resultset->pool = db->pool; resultset->rows = apr_dbd_num_tuples(db->driver, results); resultset->results = results; luaL_newmetatable(L, "lua_apr.dbselect"); lua_pushliteral(L, "__call"); lua_pushcfunction(L, lua_db_get_row); lua_rawset(L, -3); lua_setmetatable(L, -3); lua_rawseti(L, -2, 0); return 1; } return 0; } else { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ const char *err = apr_dbd_error(db->driver, db->handle, rc); /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ lua_pushnil(L); if (err) { lua_pushstring(L, err); return 2; } } } lua_pushboolean(L, 0); return 1; } return 0; } /* ============================================================================= statement:select(var1, var2, var3...): Injects variables into a prepared statement and returns the number of rows matching the query. ============================================================================= */ int lua_db_prepared_select(lua_State *L) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ lua_db_prepared_statement *st = 0; apr_status_t rc = 0; const char **vars; int x, have; /*~~~~~~~~~~~~~~~~~~~~~~~*/ /* Fetch the prepared statement and the vars passed */ luaL_checktype(L, 1, LUA_TTABLE); lua_rawgeti(L, 1, 0); luaL_checktype(L, -1, LUA_TUSERDATA); st = (lua_db_prepared_statement*) lua_topointer(L, -1); /* Check if we got enough variables passed on to us. * This, of course, only works for prepped statements made through lua. */ have = lua_gettop(L) - 2; if (st->variables != -1 && have < st->variables ) { lua_pushboolean(L, 0); lua_pushfstring(L, "Error in executing prepared statement: Expected %d arguments, got %d.", st->variables, have); return 2; } vars = apr_pcalloc(st->db->pool, have*sizeof(char *)); for (x = 0; x < have; x++) { vars[x] = lua_tostring(L, x + 2); } /* Fire off the query */ if (st->db && st->db->alive) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ int cols; apr_dbd_results_t *results = 0; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ rc = apr_dbd_pselect(st->db->driver, st->db->pool, st->db->handle, &results, st->statement, 0, have, vars); if (rc == APR_SUCCESS) { /*~~~~~~~~~~~~~~~~~~~~~*/ lua_db_result_set *resultset; /*~~~~~~~~~~~~~~~~~~~~~*/ cols = apr_dbd_num_cols(st->db->driver, results); lua_newtable(L); resultset = lua_newuserdata(L, sizeof(lua_db_result_set)); resultset->cols = cols; resultset->driver = st->db->driver; resultset->pool = st->db->pool; resultset->rows = apr_dbd_num_tuples(st->db->driver, results); resultset->results = results; luaL_newmetatable(L, "lua_apr.dbselect"); lua_pushliteral(L, "__call"); lua_pushcfunction(L, lua_db_get_row); lua_rawset(L, -3); lua_setmetatable(L, -3); lua_rawseti(L, -2, 0); return 1; } else { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ const char *err = apr_dbd_error(st->db->driver, st->db->handle, rc); /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ lua_pushnil(L); if (err) { lua_pushstring(L, err); return 2; } return 1; } } lua_pushboolean(L, 0); lua_pushliteral(L, "Database connection seems to be closed, please reacquire it."); return (2); } /* ============================================================================= statement:query(var1, var2, var3...): Injects variables into a prepared statement and returns the number of rows affected. ============================================================================= */ int lua_db_prepared_query(lua_State *L) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ lua_db_prepared_statement *st = 0; apr_status_t rc = 0; const char **vars; int x, have; /*~~~~~~~~~~~~~~~~~~~~~~~*/ /* Fetch the prepared statement and the vars passed */ luaL_checktype(L, 1, LUA_TTABLE); lua_rawgeti(L, 1, 0); luaL_checktype(L, -1, LUA_TUSERDATA); st = (lua_db_prepared_statement*) lua_topointer(L, -1); /* Check if we got enough variables passed on to us. * This, of course, only works for prepped statements made through lua. */ have = lua_gettop(L) - 2; if (st->variables != -1 && have < st->variables ) { lua_pushboolean(L, 0); lua_pushfstring(L, "Error in executing prepared statement: Expected %d arguments, got %d.", st->variables, have); return 2; } vars = apr_pcalloc(st->db->pool, have*sizeof(char *)); for (x = 0; x < have; x++) { vars[x] = lua_tostring(L, x + 2); } /* Fire off the query */ if (st->db && st->db->alive) { /*~~~~~~~~~~~~~~*/ int affected = 0; /*~~~~~~~~~~~~~~*/ rc = apr_dbd_pquery(st->db->driver, st->db->pool, st->db->handle, &affected, st->statement, have, vars); if (rc == APR_SUCCESS) { lua_pushinteger(L, affected); return 1; } else { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ const char *err = apr_dbd_error(st->db->driver, st->db->handle, rc); /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ lua_pushnil(L); if (err) { lua_pushstring(L, err); return 2; } return 1; } } lua_pushboolean(L, 0); lua_pushliteral(L, "Database connection seems to be closed, please reacquire it."); return (2); } /* ============================================================================= db:prepare(statement): Prepares a statement for later query/select. Returns a table with a :query and :select function, same as the db funcs. ============================================================================= */ int lua_db_prepare(lua_State* L) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~*/ lua_db_handle *db = 0; apr_status_t rc = 0; const char *statement, *at; request_rec *r; lua_db_prepared_statement* st; int need = 0; /*~~~~~~~~~~~~~~~~~~~~~~~~~~*/ r = ap_lua_check_request_rec(L, 2); if (r) { apr_dbd_prepared_t *pstatement = NULL; luaL_checktype(L, 3, LUA_TSTRING); statement = lua_tostring(L, 3); /* Count number of variables in statement */ at = ap_strchr_c(statement,'%'); while (at != NULL) { if (at[1] == '%') { at++; } else { need++; } at = ap_strchr_c(at+1,'%'); } db = lua_get_db_handle(L); rc = apr_dbd_prepare(db->driver, r->pool, db->handle, statement, NULL, &pstatement); if (rc != APR_SUCCESS) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ const char *err = apr_dbd_error(db->driver, db->handle, rc); /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ lua_pushnil(L); if (err) { lua_pushstring(L, err); return 2; } return 1; } /* Push the prepared statement table */ lua_newtable(L); st = lua_newuserdata(L, sizeof(lua_db_prepared_statement)); st->statement = pstatement; st->variables = need; st->db = db; lua_pushliteral(L, "select"); lua_pushcfunction(L, lua_db_prepared_select); lua_rawset(L, -4); lua_pushliteral(L, "query"); lua_pushcfunction(L, lua_db_prepared_query); lua_rawset(L, -4); lua_rawseti(L, -2, 0); return 1; } return 0; } /* ============================================================================= db:prepared(statement): Fetches a prepared statement made through DBDPrepareSQL. ============================================================================= */ int lua_db_prepared(lua_State* L) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~*/ lua_db_handle *db = 0; const char *tag; request_rec *r; lua_db_prepared_statement* st; /*~~~~~~~~~~~~~~~~~~~~~~~~~~*/ r = ap_lua_check_request_rec(L, 2); if (r) { apr_dbd_prepared_t *pstatement = NULL; db = lua_get_db_handle(L); luaL_checktype(L, 3, LUA_TSTRING); tag = lua_tostring(L, 3); /* Look for the statement */ pstatement = apr_hash_get(db->dbdhandle->prepared, tag, APR_HASH_KEY_STRING); if (pstatement == NULL) { lua_pushnil(L); lua_pushfstring(L, "Could not find any prepared statement called %s!", tag); return 2; } /* Push the prepared statement table */ lua_newtable(L); st = lua_newuserdata(L, sizeof(lua_db_prepared_statement)); st->statement = pstatement; st->variables = -1; /* we don't know :( */ st->db = db; lua_pushliteral(L, "select"); lua_pushcfunction(L, lua_db_prepared_select); lua_rawset(L, -4); lua_pushliteral(L, "query"); lua_pushcfunction(L, lua_db_prepared_query); lua_rawset(L, -4); lua_rawseti(L, -2, 0); return 1; } return 0; } /* lua_push_db_handle: Creates a database table object with database functions and a userdata at index 0, which will call lua_dbgc when garbage collected. */ static lua_db_handle* lua_push_db_handle(lua_State *L, request_rec* r, int type, apr_pool_t* pool) { lua_db_handle* db; lua_newtable(L); db = lua_newuserdata(L, sizeof(lua_db_handle)); db->alive = 1; db->pool = pool; db->type = type; db->dbdhandle = 0; db->server = r->server; luaL_newmetatable(L, "lua_apr.dbacquire"); lua_pushliteral(L, "__gc"); lua_pushcfunction(L, lua_db_gc); lua_rawset(L, -3); lua_setmetatable(L, -2); lua_rawseti(L, -2, 0); lua_pushliteral(L, "escape"); lua_pushcfunction(L, lua_db_escape); lua_rawset(L, -3); lua_pushliteral(L, "close"); lua_pushcfunction(L, lua_db_close); lua_rawset(L, -3); lua_pushliteral(L, "select"); lua_pushcfunction(L, lua_db_select); lua_rawset(L, -3); lua_pushliteral(L, "query"); lua_pushcfunction(L, lua_db_query); lua_rawset(L, -3); lua_pushliteral(L, "active"); lua_pushcfunction(L, lua_db_active); lua_rawset(L, -3); lua_pushliteral(L, "prepare"); lua_pushcfunction(L, lua_db_prepare); lua_rawset(L, -3); lua_pushliteral(L, "prepared"); lua_pushcfunction(L, lua_db_prepared); lua_rawset(L, -3); return db; } /* ============================================================================= dbacquire(dbType, dbString): Opens a new connection to a database of type _dbType_ and with the connection parameters _dbString_. If successful, returns a table with functions for using the database handle. If an error occurs, returns nil as the first parameter and the error message as the second. See the APR_DBD for a list of database types and connection strings supported. ============================================================================= */ AP_LUA_DECLARE(int) lua_db_acquire(lua_State *L) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ const char *type; const char *arguments; const char *error = 0; request_rec *r; lua_db_handle *db = 0; apr_status_t rc = 0; ap_dbd_t *dbdhandle = NULL; apr_pool_t *pool = NULL; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ r = ap_lua_check_request_rec(L, 1); if (r) { type = luaL_optstring(L, 2, "mod_dbd"); /* Defaults to mod_dbd */ if (!strcmp(type, "mod_dbd")) { lua_settop(L, 0); lua_ap_dbd_open = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_open); if (lua_ap_dbd_open) dbdhandle = (ap_dbd_t *) lua_ap_dbd_open( r->server->process->pool, r->server); if (dbdhandle) { db = lua_push_db_handle(L, r, LUA_DBTYPE_MOD_DBD, dbdhandle->pool); db->driver = dbdhandle->driver; db->handle = dbdhandle->handle; db->dbdhandle = dbdhandle; return 1; } else { lua_pushnil(L); if ( lua_ap_dbd_open == NULL ) lua_pushliteral(L, "mod_dbd doesn't seem to have been loaded."); else lua_pushliteral( L, "Could not acquire connection from mod_dbd. If your database is running, this may indicate a permission problem."); return 2; } } else { rc = apr_pool_create(&pool, NULL); if (rc != APR_SUCCESS) { lua_pushnil(L); lua_pushliteral(L, "Could not allocate memory for database!"); return 2; } apr_pool_tag(pool, "lua_dbd_pool"); apr_dbd_init(pool); dbdhandle = apr_pcalloc(pool, sizeof(ap_dbd_t)); rc = apr_dbd_get_driver(pool, type, &dbdhandle->driver); if (rc == APR_SUCCESS) { luaL_checktype(L, 3, LUA_TSTRING); arguments = lua_tostring(L, 3); lua_settop(L, 0); if (strlen(arguments)) { rc = apr_dbd_open_ex(dbdhandle->driver, pool, arguments, &dbdhandle->handle, &error); if (rc == APR_SUCCESS) { db = lua_push_db_handle(L, r, LUA_DBTYPE_APR_DBD, pool); db->driver = dbdhandle->driver; db->handle = dbdhandle->handle; db->dbdhandle = dbdhandle; return 1; } else { lua_pushnil(L); if (error) { lua_pushstring(L, error); return 2; } return 1; } } lua_pushnil(L); lua_pushliteral(L, "No database connection string was specified."); apr_pool_destroy(pool); return (2); } else { lua_pushnil(L); if (APR_STATUS_IS_ENOTIMPL(rc)) { lua_pushfstring(L, "driver for %s not available", type); } else if (APR_STATUS_IS_EDSOOPEN(rc)) { lua_pushfstring(L, "can't find driver for %s", type); } else if (APR_STATUS_IS_ESYMNOTFOUND(rc)) { lua_pushfstring(L, "driver for %s is invalid or corrupted", type); } else { lua_pushliteral(L, "mod_lua not compatible with APR in get_driver"); } lua_pushinteger(L, rc); apr_pool_destroy(pool); return 3; } } lua_pushnil(L); return 1; } return 0; }