From 0e04ce55292226fc9e07618dd0a77bf1ceaf648d Mon Sep 17 00:00:00 2001 From: Daniel Gruno Date: Tue, 8 Jan 2013 11:42:50 +0000 Subject: [PATCH] Add database features for mod_lua (apr_dbd + mod_dbd). See documentation update for API and examples. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1430225 13f79535-47bb-0310-9956-ffa450edef68 --- docs/manual/mod/mod_lua.html.en | 175 +++++++ docs/manual/mod/mod_lua.xml | 165 +++++++ modules/lua/NWGNUmakefile | 1 + modules/lua/config.m4 | 2 +- modules/lua/lua_dbd.c | 792 ++++++++++++++++++++++++++++++++ modules/lua/lua_dbd.h | 76 +++ modules/lua/lua_request.c | 3 + modules/lua/mod_lua.dsp | 8 + 8 files changed, 1221 insertions(+), 1 deletion(-) create mode 100644 modules/lua/lua_dbd.c create mode 100644 modules/lua/lua_dbd.h diff --git a/docs/manual/mod/mod_lua.html.en b/docs/manual/mod/mod_lua.html.en index ef8e7024b9..aa445740c2 100644 --- a/docs/manual/mod/mod_lua.html.en +++ b/docs/manual/mod/mod_lua.html.en @@ -90,6 +90,7 @@ trust, as it can be abused to change the internal workings of httpd.

  • Logging Functions
  • apache2 Package
  • Modifying contents with Lua filters
  • +
  • Database connectivity
  • top
    @@ -875,6 +876,12 @@ r:custom_response(status_code, string) -- Construct and set a custom response fo r:sleep(number_of_seconds) -- Puts the script to sleep for a given number of seconds. + +
    +r:dbacquire(dbType[, dbParams]) -- Acquires a connection to a database and returns a database class.
    +                                -- See 'Database connectivity' for details.
    +        
    + @@ -961,6 +968,174 @@ function filter(r) end +
    top
    +
    +

    Database connectivity

    + +

    + Mod_lua implements a simple database feature for querying and running commands + on the most popular database engines (mySQL, PostgreSQL, FreeTDS, ODBC, SQLite, Oracle) + as well as mod_dbd. +

    +

    Connecting and firing off queries is as easy as:

    +
    +function handler(r)
    +    local database, err = r:dbacquire("mysql", "server=localhost&user=root&database=mydb")
    +    if not err then
    +        local results, err = database:select(r, "SELECT `name`, `age` FROM `people` WHERE 1")
    +        if not err then
    +            local rows = results(0) -- fetch all rows synchronously
    +            for k, row in pairs(rows) do
    +                r:puts( string.format("Name: %s, Age: %s<br/>", row[1], row[2]) )
    +            end
    +        else
    +            r:puts("Database query error: " .. err)
    +        end
    +        database:close()
    +    else
    +        r:puts("Could not connect to the database: " .. err)
    +    end
    +end
    +    
    + +

    + To utilize mod_dbd, simply specify mod_dbd + as the database type, or leave the field blank: +

    +
    +    local database = r:dbacquire("mod_dbd")
    +    
    + +

    Database object and contained functions

    + +

    The database object returned by dbacquire has the following methods:

    +

    Normal select and query from a database:

    +
    +-- Run a statement and return the number of rows affected:
    +local affected, errmsg = database:query(r, "DELETE FROM `tbl` WHERE 1")
    +
    +-- Run a statement and return a result set that can be used synchronously or async:
    +local result, errmsg = database:select(r, "SELECT * FROM `people` WHERE 1")
    +    
    + +

    Using prepared statements (recommended):

    +
    +-- Create and run a prepared statement:
    +local statement, errmsg = database:prepare(r, "DELETE FROM `tbl` WHERE `age` > %u")
    +if not errmsg then
    +    local result, errmsg = statement:query(20) -- run the statement with age >20
    +end
    +
    +-- Fetch a prepared statement from a DBDPrepareSQL directive:
    +local statement, errmsg = database:prepared(r, "someTag")
    +if not errmsg then
    +    local result, errmsg = statement:select("John Doe", 123) -- inject the values "John Doe" and 123 into the statement
    +end
    +
    +
    + +

    Escaping values, closing databases etc:

    +
    +-- Escape a value for use in a statement:
    +local escaped = database:escape(r, [["'|blabla]])
    +
    +-- Close a database connection and free up handles:
    +database:close()
    +
    +-- Check whether a database connection is up and running:
    +local connected = database:active()
    +    
    + + +

    Working with result sets

    + +

    The result set returned by db:query or by the prepared statement functions + created through db:prepare can be used to + fetch rows synchronously or asynchronously, depending on the row number specified:
    + result(0) fetches all rows in a synchronous manner, returning a table of rows.
    + result(-1) fetches the next available row in the set, asynchronously.
    + result(N) fetches row number N, asynchronously: +

    +
    +-- fetch a result set using a regular query:
    +local result, err = db:select(r, "SELECT * FROM `tbl` WHERE 1")
    +
    +local rows = result(0) -- Fetch ALL rows synchronously
    +local row = result(-1) -- Fetch the next available row, asynchronously
    +local row = result(1234) -- Fetch row number 1234, asynchronously
    +    
    + +

    One can construct a function that returns an iterative function to iterate over all rows + in a synchronous or asynchronous way, depending on the async argument: +

    +
    +function rows(resultset, async)
    +    local a = 0
    +    local function getnext()
    +        a = a + 1
    +        local row = resultset(-1)
    +        return row and a or nil, row
    +    end
    +    if not async then
    +        return pairs(resultset(0))
    +    else
    +        return getnext, self
    +    end
    +end
    +
    +local statement, err = db:prepare(r, "SELECT * FROM `tbl` WHERE `age` > %u")
    +if not err then
    +     -- fetch rows asynchronously:
    +    local result, err = statement:select(20)
    +    if not err then
    +        for index, row in rows(result, true) do
    +            ....
    +        end
    +    end
    +
    +     -- fetch rows synchronously:
    +    local result, err = statement:select(20)
    +    if not err then
    +        for index, row in rows(result, false) do
    +            ....
    +        end
    +    end
    +end
    +    
    + + +

    Closing a database connection

    + + +

    Database handles should be closed using database:close() when they are no longer + needed. If you do not close them manually, they will eventually be garbage collected and + closed by mod_lua, but you may end up having too many unused connections to the database + if you leave the closing up to mod_lua. Essentially, the following two measures are + the same: +

    +
    +-- Method 1: Manually close a handle
    +local database = r:dbacquire("mod_dbd")
    +database:close() -- All done
    +
    +-- Method 2: Letting the garbage collector close it
    +local database = r:dbacquire("mod_dbd")
    +database = nil -- throw away the reference
    +collectgarbage() -- close the handle via GC
    +
    + + +

    Precautions when working with databases

    + +

    Although the standard query and run functions are freely + available, it is recommended that you use prepared statements whenever possible, to + both optimize performance (if your db handle lives on for a long time) and to minimize + the risk of SQL injection attacks. run and query should only + be used when there are no variables inserted into a statement (a static statement). + When using dynamic statements, use db:prepare or db:prepared. +

    + +
    top

    LuaAuthzProvider Directive

    diff --git a/docs/manual/mod/mod_lua.xml b/docs/manual/mod/mod_lua.xml index 2475aa0c87..d18d1d1dcd 100644 --- a/docs/manual/mod/mod_lua.xml +++ b/docs/manual/mod/mod_lua.xml @@ -794,6 +794,11 @@ r:custom_response(status_code, string) -- Construct and set a custom response fo r:sleep(number_of_seconds) -- Puts the script to sleep for a given number of seconds. + + +r:dbacquire(dbType[, dbParams]) -- Acquires a connection to a database and returns a database class. + -- See 'Database connectivity' for details. + @@ -880,6 +885,166 @@ end +
    + Database connectivity +

    + Mod_lua implements a simple database feature for querying and running commands + on the most popular database engines (mySQL, PostgreSQL, FreeTDS, ODBC, SQLite, Oracle) + as well as mod_dbd. +

    +

    Connecting and firing off queries is as easy as:

    + +function handler(r) + local database, err = r:dbacquire("mysql", "server=localhost&user=root&database=mydb") + if not err then + local results, err = database:select(r, "SELECT `name`, `age` FROM `people` WHERE 1") + if not err then + local rows = results(0) -- fetch all rows synchronously + for k, row in pairs(rows) do + r:puts( string.format("Name: %s, Age: %s<br/>", row[1], row[2]) ) + end + else + r:puts("Database query error: " .. err) + end + database:close() + else + r:puts("Could not connect to the database: " .. err) + end +end + +

    + To utilize mod_dbd, simply specify mod_dbd + as the database type, or leave the field blank: +

    + + local database = r:dbacquire("mod_dbd") + +
    + Database object and contained functions +

    The database object returned by dbacquire has the following methods:

    +

    Normal select and query from a database:

    + +-- Run a statement and return the number of rows affected: +local affected, errmsg = database:query(r, "DELETE FROM `tbl` WHERE 1") + +-- Run a statement and return a result set that can be used synchronously or async: +local result, errmsg = database:select(r, "SELECT * FROM `people` WHERE 1") + +

    Using prepared statements (recommended):

    + +-- Create and run a prepared statement: +local statement, errmsg = database:prepare(r, "DELETE FROM `tbl` WHERE `age` > %u") +if not errmsg then + local result, errmsg = statement:query(20) -- run the statement with age >20 +end + +-- Fetch a prepared statement from a DBDPrepareSQL directive: +local statement, errmsg = database:prepared(r, "someTag") +if not errmsg then + local result, errmsg = statement:select("John Doe", 123) -- inject the values "John Doe" and 123 into the statement +end + + +

    Escaping values, closing databases etc:

    + +-- Escape a value for use in a statement: +local escaped = database:escape(r, [["'|blabla]]) + +-- Close a database connection and free up handles: +database:close() + +-- Check whether a database connection is up and running: +local connected = database:active() + +
    +
    + Working with result sets +

    The result set returned by db:select or by the prepared statement functions + created through db:prepare can be used to + fetch rows synchronously or asynchronously, depending on the row number specified:
    + result(0) fetches all rows in a synchronous manner, returning a table of rows.
    + result(-1) fetches the next available row in the set, asynchronously.
    + result(N) fetches row number N, asynchronously: +

    + +-- fetch a result set using a regular query: +local result, err = db:select(r, "SELECT * FROM `tbl` WHERE 1") + +local rows = result(0) -- Fetch ALL rows synchronously +local row = result(-1) -- Fetch the next available row, asynchronously +local row = result(1234) -- Fetch row number 1234, asynchronously + +

    One can construct a function that returns an iterative function to iterate over all rows + in a synchronous or asynchronous way, depending on the async argument: +

    + +function rows(resultset, async) + local a = 0 + local function getnext() + a = a + 1 + local row = resultset(-1) + return row and a or nil, row + end + if not async then + return pairs(resultset(0)) + else + return getnext, self + end +end + +local statement, err = db:prepare(r, "SELECT * FROM `tbl` WHERE `age` > %u") +if not err then + -- fetch rows asynchronously: + local result, err = statement:select(20) + if not err then + for index, row in rows(result, true) do + .... + end + end + + -- fetch rows synchronously: + local result, err = statement:select(20) + if not err then + for index, row in rows(result, false) do + .... + end + end +end + +
    +
    + Closing a database connection + +

    Database handles should be closed using database:close() when they are no longer + needed. If you do not close them manually, they will eventually be garbage collected and + closed by mod_lua, but you may end up having too many unused connections to the database + if you leave the closing up to mod_lua. Essentially, the following two measures are + the same: +

    + +-- Method 1: Manually close a handle +local database = r:dbacquire("mod_dbd") +database:close() -- All done + +-- Method 2: Letting the garbage collector close it +local database = r:dbacquire("mod_dbd") +database = nil -- throw away the reference +collectgarbage() -- close the handle via GC + +
    +
    + Precautions when working with databases +

    Although the standard query and run functions are freely + available, it is recommended that you use prepared statements whenever possible, to + both optimize performance (if your db handle lives on for a long time) and to minimize + the risk of SQL injection attacks. run and query should only + be used when there are no variables inserted into a statement (a static statement). + When using dynamic statements, use db:prepare or db:prepared. +

    +
    + +
    + LuaRoot Specify the base path for resolving relative paths for mod_lua directives diff --git a/modules/lua/NWGNUmakefile b/modules/lua/NWGNUmakefile index ac962c9e93..7775ed0746 100644 --- a/modules/lua/NWGNUmakefile +++ b/modules/lua/NWGNUmakefile @@ -182,6 +182,7 @@ FILES_nlm_objs = \ $(OBJDIR)/lua_config.o \ $(OBJDIR)/lua_request.o \ $(OBJDIR)/lua_vmprep.o \ + $(OBJDIR)/lua_dbd.o \ $(EOLIST) # diff --git a/modules/lua/config.m4 b/modules/lua/config.m4 index dd0594b60c..2d1ac05253 100644 --- a/modules/lua/config.m4 +++ b/modules/lua/config.m4 @@ -136,7 +136,7 @@ else fi ]) -lua_objects="lua_apr.lo lua_config.lo mod_lua.lo lua_request.lo lua_vmprep.lo" +lua_objects="lua_apr.lo lua_config.lo mod_lua.lo lua_request.lo lua_vmprep.lo lua_dbd.lo" APACHE_MODULE(lua, Apache Lua Framework, $lua_objects, , , [ CHECK_LUA() diff --git a/modules/lua/lua_dbd.c b/modules/lua/lua_dbd.c new file mode 100644 index 0000000000..9da012e13d --- /dev/null +++ b/modules/lua/lua_dbd.c @@ -0,0 +1,792 @@ +/** + * 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. + ============================================================================= + */ +AP_LUA_DECLARE(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_MOD_DBD) { + rc = apr_dbd_close(db->driver, db->handle); + } + 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); + if (db->pool) apr_pool_destroy(db->pool); + } + + 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 collecing function. + ============================================================================= + */ +AP_LUA_DECLARE(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. + ============================================================================= + */ +AP_LUA_DECLARE(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. + ============================================================================= + */ +AP_LUA_DECLARE(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. + ============================================================================= + */ +AP_LUA_DECLARE(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. + ============================================================================= + */ +static 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. + ============================================================================= + */ +AP_LUA_DECLARE(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. + ============================================================================= + */ +static 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. + ============================================================================= + */ +static 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. + ============================================================================= + */ +AP_LUA_DECLARE(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. + ============================================================================= + */ +AP_LUA_DECLARE(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); + luaL_register(L, NULL, lua_db_methods); + 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); + 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); + + rc = apr_dbd_get_driver(r->server->process->pool, type, &db->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(db->driver, r->server->process->pool, + arguments, &db->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."); + 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); + return 3; + } + } + + lua_pushnil(L); + return 1; + } + + return 0; +} + diff --git a/modules/lua/lua_dbd.h b/modules/lua/lua_dbd.h new file mode 100644 index 0000000000..98cf914a84 --- /dev/null +++ b/modules/lua/lua_dbd.h @@ -0,0 +1,76 @@ +/** + * 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. + */ + +#ifndef _LUA_DBD_H_ +#define _LUA_DBD_H_ + +#include "mod_lua.h" +#include "apr.h" +#include "apr_dbd.h" +#include "mod_dbd.h" + +#define LUA_DBTYPE_APR_DBD 0 +#define LUA_DBTYPE_MOD_DBD 1 +typedef struct +{ + apr_dbd_t *handle; + const apr_dbd_driver_t *driver; + int alive; + apr_pool_t *pool; + char type; + ap_dbd_t * dbdhandle; + server_rec *server; +} lua_db_handle; + +typedef struct { + const apr_dbd_driver_t *driver; + int rows; + int cols; + apr_dbd_results_t *results; + apr_pool_t *pool; +} lua_db_result_set; + +typedef struct { + apr_dbd_prepared_t *statement; + int variables; + lua_db_handle *db; +} lua_db_prepared_statement; + + +AP_LUA_DECLARE(int) lua_db_close(lua_State *L); +AP_LUA_DECLARE(int) lua_db_gc(lua_State *L); +AP_LUA_DECLARE(int) lua_db_active(lua_State *L); +AP_LUA_DECLARE(int) lua_db_query(lua_State *L); +AP_LUA_DECLARE(int) lua_db_escape(lua_State *L); +AP_LUA_DECLARE(int) lua_db_select(lua_State *L); +AP_LUA_DECLARE(int) lua_db_prepare(lua_State* L); +AP_LUA_DECLARE(int) lua_db_prepared(lua_State* L); +AP_LUA_DECLARE(int) lua_db_acquire(lua_State* L); + +static const luaL_reg lua_db_methods[] = +{ + { "escape", lua_db_escape }, + { "close", lua_db_close }, + { "select", lua_db_select }, + { "query", lua_db_query }, + { "active", lua_db_active }, + { "prepare", lua_db_prepare }, + { "prepared", lua_db_prepared }, + { 0, 0 } +}; + +#endif /* !_LUA_DBD_H_ */ diff --git a/modules/lua/lua_request.c b/modules/lua/lua_request.c index a77f48e3f7..a16fbaebb5 100644 --- a/modules/lua/lua_request.c +++ b/modules/lua/lua_request.c @@ -19,6 +19,7 @@ #include "util_script.h" #include "lua_apr.h" #include "scoreboard.h" +#include "lua_dbd.h" APLOG_USE_MODULE(lua); #define POST_MAX_VARS 500 @@ -964,6 +965,8 @@ AP_LUA_DECLARE(void) ap_lua_load_request_lmodule(lua_State *L, apr_pool_t *p) makefun(&ap_auth_name, APL_REQ_FUNTYPE_STRING, p)); apr_hash_set(dispatch, "sendfile", APR_HASH_KEY_STRING, makefun(&lua_ap_sendfile, APL_REQ_FUNTYPE_LUACFUN, p)); + apr_hash_set(dispatch, "dbacquire", APR_HASH_KEY_STRING, + makefun(&lua_db_acquire, APL_REQ_FUNTYPE_LUACFUN, p)); lua_pushlightuserdata(L, dispatch); diff --git a/modules/lua/mod_lua.dsp b/modules/lua/mod_lua.dsp index e7e79f4f75..810d1c58ee 100644 --- a/modules/lua/mod_lua.dsp +++ b/modules/lua/mod_lua.dsp @@ -141,6 +141,14 @@ SOURCE=.\mod_lua.h # End Source File # Begin Source File +SOURCE=.\lua_dbd.c +# End Source File +# Begin Source File + +SOURCE=.\lua_dbd.h +# End Source File +# Begin Source File + SOURCE=..\..\build\win32\httpd.rc # End Source File # End Target -- 2.40.0