From 598ad844a20359d047fd0ec20bce1276f93b8252 Mon Sep 17 00:00:00 2001 From: Daniel Gruno Date: Sun, 27 Jan 2013 15:31:45 +0000 Subject: [PATCH] Backport lua dbd bindings to 2.4 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1439099 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 3 + STATUS | 17 +- docs/manual/mod/mod_lua.xml | 165 ++++++++ modules/lua/NWGNUmakefile | 1 + modules/lua/config.m4 | 2 +- modules/lua/lua_dbd.c | 825 ++++++++++++++++++++++++++++++++++++ modules/lua/lua_dbd.h | 66 +++ modules/lua/lua_request.c | 3 + modules/lua/mod_lua.dsp | 12 +- 9 files changed, 1075 insertions(+), 19 deletions(-) create mode 100644 modules/lua/lua_dbd.c create mode 100644 modules/lua/lua_dbd.h diff --git a/CHANGES b/CHANGES index d9a9879499..0c69683781 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,9 @@ Changes with Apache 2.4.4 + *) mod_lua: Add bindings for mod_dbd/apr_dbd database access. + [Daniel Gruno] + *) mod_proxy: Allow for persistence of local changes made via the balancer-manager between graceful/normal restarts and power cycles. [Jim Jagielski] diff --git a/STATUS b/STATUS index affe2c76db..9646f8e73e 100644 --- a/STATUS +++ b/STATUS @@ -100,22 +100,7 @@ PATCHES ACCEPTED TO BACKPORT FROM TRUNK: http://mail-archives.apache.org/mod_mbox/httpd-dev/201301.mbox/%3C20130103112044.GA19653%40redhat.com%3E kbrand: configure option added in r1429228 - * mod_lua: Add bindings for mod_dbd/apr_dbd database access. - trunk patch: http://svn.apache.org/r1430225 - http://svn.apache.org/r1430265 - http://svn.apache.org/r1430472 - http://svn.apache.org/r1430656 - http://svn.apache.org/r1431236 (CodeWarrior workaround) - http://svn.apache.org/r1431322 - http://svn.apache.org/r1431930 (Windows patch) - http://svn.apache.org/r1432418 (Debian patch) - http://svn.apache.org/r1432892 (fix for external drivers) - 2.4.x patch: http://www.humbedooh.com/apache/lua_dbd.patch - + CHANGES - +1: humbedooh, covener - +1: fuankg (compiles/links fine now for NetWare; no functional tests yet) - gsmith notes: compiles/links on Windows now. - + * mod_status: Add useful mod_status info... try to determine last time a specific vhost was accessed/used. trunk patch: https://svn.apache.org/viewvc?view=revision&revision=1417529 diff --git a/docs/manual/mod/mod_lua.xml b/docs/manual/mod/mod_lua.xml index 8820d6acd8..ed73eb0217 100644 --- a/docs/manual/mod/mod_lua.xml +++ b/docs/manual/mod/mod_lua.xml @@ -425,6 +425,11 @@ r:parsebody([sizeLimit]) -- parse the request body as a POST and return a lua t r:write("a single string") -- print to response body + + +r:dbacquire(dbType[, dbParams]) -- Acquires a connection to a database and returns a database class. + -- See 'Database connectivity' for details. + @@ -468,6 +473,166 @@ r:parsebody([sizeLimit]) -- parse the request body as a POST and return a lua t

(Other HTTP status codes are not yet implemented.)

+
+ 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 36d0a699b4..9b6b9d0f68 100644 --- a/modules/lua/NWGNUmakefile +++ b/modules/lua/NWGNUmakefile @@ -27,6 +27,7 @@ XINCDIRS += \ $(APR)/include \ $(APRUTIL)/include \ $(AP_WORK)/include \ + $(AP_WORK)/modules/database \ $(AP_WORK)/modules/http \ $(AP_WORK)/modules/ssl \ $(NWOS) \ 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..4c71a17834 --- /dev/null +++ b/modules/lua/lua_dbd.c @@ -0,0 +1,825 @@ +/** + * 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_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 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; +} + diff --git a/modules/lua/lua_dbd.h b/modules/lua/lua_dbd.h new file mode 100644 index 0000000000..6f74efd0bd --- /dev/null +++ b/modules/lua/lua_dbd.h @@ -0,0 +1,66 @@ +/** + * 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_acquire(lua_State* L); +int lua_db_escape(lua_State* L); +int lua_db_close(lua_State* L); +int lua_db_prepare(lua_State* L); +int lua_db_prepared(lua_State* L); +int lua_db_select(lua_State* L); +int lua_db_query(lua_State* L); +int lua_db_prepared_select(lua_State* L); +int lua_db_prepared_query(lua_State* L); +int lua_db_get_row(lua_State* L); +int lua_db_gc(lua_State* L); +int lua_db_active(lua_State* L); + +#endif /* !_LUA_DBD_H_ */ diff --git a/modules/lua/lua_request.c b/modules/lua/lua_request.c index 9a1cdf8cc1..63b192dcd9 100644 --- a/modules/lua/lua_request.c +++ b/modules/lua/lua_request.c @@ -18,6 +18,7 @@ #include "mod_lua.h" #include "util_script.h" #include "lua_apr.h" +#include "lua_dbd.h" APLOG_USE_MODULE(lua); @@ -751,6 +752,8 @@ AP_LUA_DECLARE(void) ap_lua_load_request_lmodule(lua_State *L, apr_pool_t *p) makefun(&req_notes, APL_REQ_FUNTYPE_TABLE, p)); apr_hash_set(dispatch, "subprocess_env", APR_HASH_KEY_STRING, makefun(&req_subprocess_env, APL_REQ_FUNTYPE_TABLE, 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..71d33b8bfb 100644 --- a/modules/lua/mod_lua.dsp +++ b/modules/lua/mod_lua.dsp @@ -43,7 +43,7 @@ RSC=rc.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c -# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/lua/src" /I "../ssl" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_LUA_DECLARE_EXPORT" /Fd"Release\mod_lua_src" /FD /c +# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/lua/src" /I "../ssl" /I "../database" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_LUA_DECLARE_EXPORT" /Fd"Release\mod_lua_src" /FD /c # ADD BASE MTL /nologo /D "NDEBUG" /win32 # ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 # ADD BASE RSC /l 0x409 /d "NDEBUG" @@ -75,7 +75,7 @@ PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).ma # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c -# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/lua/src" /I "../ssl" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_LUA_DECLARE_EXPORT" /Fd"Debug\mod_lua_src" /FD /c +# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/lua/src" /I "../ssl" /I "../database" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_LUA_DECLARE_EXPORT" /Fd"Debug\mod_lua_src" /FD /c # ADD BASE MTL /nologo /D "_DEBUG" /win32 # ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD BASE RSC /l 0x409 /d "_DEBUG" @@ -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.50.1