diff --git a/README.md b/README.md index 9e1a7e0b..38662a86 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,7 @@ inside the Nginx worker process. It has two parts, a core module built into Ngin controls the life cycle of the database environment, and a FFI based Lua binding for interacting with the module to access/change data. -Table of Contents -================= +# Table of Contents * [lua-resty-lmdb](#lua-resty-lmdb) * [APIs](#apis) @@ -15,6 +14,7 @@ Table of Contents * [set](#set) * [get_env_info](#get_env_info) * [db\_drop](#db_drop) + * [prefix](#prefix) * [resty.lmdb.transaction](#restylmdbtransaction) * [reset](#reset) * [get](#get) @@ -22,7 +22,9 @@ Table of Contents * [db\_open](#db_open) * [db\_drop](#db_drop) * [commit](#commit) - * [Directives](#Directives) + * [resty.lmdb.prefix](#restylmdbprefix) + * [reset](#reset) + * [Directives](#directives) * [lmdb_environment_path](#lmdb_environment_path) * [lmdb_max_databases](#lmdb_max_databases) * [lmdb_map_size](#lmdb_map_size) @@ -99,18 +101,42 @@ In case of error, `nil` and a string describing the error will be returned inste [Back to TOC](#table-of-contents) -### resty.lmdb.transaction +### prefix -**syntax:** *local txn = transaction.begin(hint?)* +**syntax:** *for key, value in lmdb.prefix(prefix) do* **context:** *any context* -Creates a new LMDB transaction object. This does not actually starts the transaction, but only creates -a Lua table that stores the operations for execution later. If `hint` is provided then the Lua table holding -the operations will be pre-allocated to store `hint` operations. +Returns all key and their associated value for keys starting with `prefix`. +For example, if the database contains: + +``` +key1: value1 +key11: value11 +key2: value2 +``` + +Then a call of `lmdb.prefix("key")` will yield `key1`, `key11` and `key2` respectively. + +In case of errors while fetching from LMDB, `key` will be `nil` and `value` will be +a string describing the error. The caller must anticipate this happening and check each return +value carefully before consuming. + +**Warning on transaction safety:** Since the number of keys that could potentially +be returned with this method could be very large, this method does not return all +results inside a single transaction as this will be very expensive. Instead, this +method gets keys from LMDB in batches using different read transaction. Therefore, it +is possible that the database content has changed between batches. We may introduce a +mechanism for detecting this case in the future, but for now there is a small opportunity +for this to happen and you should guard your application for concurrent writes if this +is a huge concern. This function makes best effort to detect when database content +definitely changed between iterations, in this case `nil, "DB content changed while iterating"` +will be returned from the iterator. [Back to TOC](#table-of-contents) +### resty.lmdb.transaction + #### reset **syntax:** *txn:reset()* @@ -197,6 +223,26 @@ from the `txn` table when `commit()` returned an error is undefined. [Back to TOC](#table-of-contents) +### resty.lmdb.prefix + +#### page + +**syntax:** *res, err = prefix.page(start, prefix, db?)* + +**context:** *any context* + +Return all keys `>= start` and starts with `prefix`. If `db` is omitted, +it defaults to `"_default"`. + +The return value of this function is a table `res` where `res[1].key` and `res[1].value` +corresponds to the first key and value, `res[2].key` and `res[2].value` corresponds to the +second and etc. If no keys matched the provided criteria, then an empty table will be +returned. + +In case of errors, `nil` and an string describing the reason of the failure will be returned. + +[Back to TOC](#table-of-contents) + ## Directives ### lmdb_environment_path diff --git a/config b/config index da9d51ef..efe9f210 100644 --- a/config +++ b/config @@ -2,6 +2,7 @@ ngx_module_type=CORE ngx_module_name=ngx_lua_resty_lmdb_module ngx_module_srcs="$ngx_addon_dir/src/ngx_lua_resty_lmdb_module.c $ngx_addon_dir/src/ngx_lua_resty_lmdb_transaction.c + $ngx_addon_dir/src/ngx_lua_resty_lmdb_prefix.c $ngx_addon_dir/src/ngx_lua_resty_lmdb_status.c " ngx_module_incs="$ngx_addon_dir/lmdb/libraries/liblmdb $ngx_addon_dir/src" diff --git a/lib/resty/lmdb.lua b/lib/resty/lmdb.lua index f03c1cf1..1e1bd9c2 100644 --- a/lib/resty/lmdb.lua +++ b/lib/resty/lmdb.lua @@ -2,47 +2,128 @@ local _M = {} local transaction = require("resty.lmdb.transaction") +local prefix = require("resty.lmdb.prefix") local status = require("resty.lmdb.status") -do - local CACHED_TXN = transaction.begin(1) +local assert = assert +local prefix_page = prefix.page +local get_phase = ngx.get_phase +local ngx_sleep = ngx.sleep + + +local CACHED_TXN = transaction.begin(1) +local CAN_YIELD_PHASES = { + rewrite = true, + server_rewrite = true, + access = true, + content = true, + timer = true, + ssl_client_hello = true, + ssl_certificate = true, + ssl_session_fetch = true, + preread = true, +} + + +function _M.get(key, db) + CACHED_TXN:reset() + CACHED_TXN:get(key, db) + local res, err = CACHED_TXN:commit() + if not res then + return nil, err + end + + return CACHED_TXN[1].result +end - function _M.get(key, db) - CACHED_TXN:reset() - CACHED_TXN:get(key, db) - local res, err = CACHED_TXN:commit() - if not res then - return nil, err - end - return CACHED_TXN[1].result +function _M.set(key, value, db) + CACHED_TXN:reset() + CACHED_TXN:set(key, value, db) + local res, err = CACHED_TXN:commit() + if not res then + return nil, err end + return true +end - function _M.set(key, value, db) - CACHED_TXN:reset() - CACHED_TXN:set(key, value, db) - local res, err = CACHED_TXN:commit() - if not res then - return nil, err - end - return true +function _M.db_drop(delete, db) + delete = not not delete + + CACHED_TXN:reset() + CACHED_TXN:db_drop(delete, db) + local res, err = CACHED_TXN:commit() + if not res then + return nil, err end + return true +end - function _M.db_drop(delete, db) - delete = not not delete - CACHED_TXN:reset() - CACHED_TXN:db_drop(delete, db) - local res, err = CACHED_TXN:commit() +function _M.prefix(prefix, db) + local res, i, res_n, err_or_more + local last = prefix + local can_yield = CAN_YIELD_PHASES[get_phase()] + + return function() + ::more:: if not res then - return nil, err + -- need to fetch more data + res, err_or_more = prefix_page(last, prefix, db) + if not res then + return nil, err_or_more + end + + res_n = #res + if res_n == 0 or (i and res_n == 1) then + return nil + end + + if i then + -- not the first call to prefix_page + if res[1].key ~= last then + return nil, "DB content changed while iterating" + end + + -- this is not sufficient to prove DB content did not change, + -- but at least the resume point did not change. + -- skip the first key + i = 2 + + else + -- first call to prefix_page + i = 1 + end end - return true + assert(res_n > 0) + + if i > res_n then + if err_or_more then + last = res[i - 1].key + res = nil + + if can_yield then + ngx_sleep(0) + end + + goto more + end + + -- more = false + + return nil + end + + local key = res[i].key + local value = res[i].value + i = i + 1 + + return key, value end end diff --git a/lib/resty/lmdb/cdefs.lua b/lib/resty/lmdb/cdefs.lua new file mode 100644 index 00000000..32b40278 --- /dev/null +++ b/lib/resty/lmdb/cdefs.lua @@ -0,0 +1,47 @@ +local ffi = require("ffi") +local base = require("resty.core.base") + + +local DEFAULT_VALUE_BUF_SIZE = 512 * 2048 -- 1MB +base.set_string_buf_size(DEFAULT_VALUE_BUF_SIZE) + + +ffi.cdef([[ +typedef unsigned int MDB_dbi; + + +typedef enum { + NGX_LMDB_OP_GET = 0, + NGX_LMDB_OP_PREFIX, + NGX_LMDB_OP_SET, + NGX_LMDB_OP_DB_OPEN, + NGX_LMDB_OP_DB_DROP +} ngx_lua_resty_lmdb_operation_e; + + +typedef struct { + ngx_lua_resty_lmdb_operation_e opcode; + ngx_str_t key; /* GET, SET */ + ngx_str_t value; /* GET, SET */ + MDB_dbi dbi; /* ALL OPS */ + unsigned int flags; /* SET, DROP */ +} ngx_lua_resty_lmdb_operation_t; + +typedef struct { + size_t map_size; /**< Size of the data memory map */ + unsigned int page_size; /**< Size of a database page. */ + unsigned int max_readers; /**< max reader slots in the environment */ + unsigned int num_readers; /**< max reader slots used in the environment */ + unsigned int allocated_pages; /**< number of pages allocated */ + size_t in_use_pages; /**< number of pages currently in-use */ + unsigned int entries; /**< the number of entries (key/value pairs) in the environment */ +} ngx_lua_resty_lmdb_ffi_status_t; + +int ngx_lua_resty_lmdb_ffi_env_info(ngx_lua_resty_lmdb_ffi_status_t *lst, char **err); + + +int ngx_lua_resty_lmdb_ffi_execute(ngx_lua_resty_lmdb_operation_t *ops, + size_t n, int need_write, unsigned char *buf, size_t buf_len, char **err); +int ngx_lua_resty_lmdb_ffi_prefix(ngx_lua_resty_lmdb_operation_t *ops, + size_t n, unsigned char *buf, size_t buf_len, char **err); +]]) diff --git a/lib/resty/lmdb/prefix.lua b/lib/resty/lmdb/prefix.lua new file mode 100644 index 00000000..57d66302 --- /dev/null +++ b/lib/resty/lmdb/prefix.lua @@ -0,0 +1,91 @@ +local _M = {} + + +local ffi = require("ffi") +local table_new = require("table.new") +require("resty.lmdb.cdefs") +local transaction = require("resty.lmdb.transaction") +local base = require("resty.core.base") + + +local C = ffi.C +-- DEFAULT_OPS_SIZE must be >= 2, +-- see the function comment for ngx_lua_resty_lmdb_ffi_prefix +local DEFAULT_OPS_SIZE = 512 +local DEFAULT_DB = transaction.DEFAULT_DB +local NGX_ERROR = ngx.ERROR +local NGX_AGAIN = ngx.AGAIN + + +local ffi_string = ffi.string +local ffi_new = ffi.new +local get_dbi = transaction.get_dbi +local err_ptr = base.get_errmsg_ptr() +local get_string_buf = base.get_string_buf +local get_string_buf_size = base.get_string_buf_size +local assert = assert + + +function _M.page(start, prefix, db) + local value_buf_size = get_string_buf_size() + local ops = ffi_new("ngx_lua_resty_lmdb_operation_t[?]", DEFAULT_OPS_SIZE) + + ops[0].opcode = C.NGX_LMDB_OP_PREFIX + ops[0].key.data = start + ops[0].key.len = #start + + ops[1].opcode = C.NGX_LMDB_OP_PREFIX + ops[1].key.data = prefix + ops[1].key.len = #prefix + + local dbi, err = get_dbi(false, db or DEFAULT_DB) + if err then + return nil, "unable to open DB for access: " .. err + + elseif not dbi then + return nil, "DB " .. db .. " does not exist" + end + + ops[0].dbi = dbi + +::again:: + local buf = get_string_buf(value_buf_size, false) + local ret = C.ngx_lua_resty_lmdb_ffi_prefix(ops, DEFAULT_OPS_SIZE, + buf, value_buf_size, err_ptr) + if ret == NGX_ERROR then + return nil, ffi_string(err_ptr[0]) + end + + if ret == NGX_AGAIN then + value_buf_size = value_buf_size * 2 + goto again + end + + if ret == 0 then + -- unlikely case + return {}, false + end + + assert(ret > 0) + + local res = table_new(ret, 0) + + for i = 1, ret do + local cop = ops[i - 1] + + assert(cop.opcode == C.NGX_LMDB_OP_PREFIX) + + local pair = { + key = ffi_string(cop.key.data, cop.key.len), + value = ffi_string(cop.value.data, cop.value.len), + } + + res[i] = pair + end + + -- if ret == DEFAULT_OPS_SIZE, then it is possible there are more keys + return res, ret == DEFAULT_OPS_SIZE +end + + +return _M diff --git a/lib/resty/lmdb/status.lua b/lib/resty/lmdb/status.lua index ef1177f3..b6ee6108 100644 --- a/lib/resty/lmdb/status.lua +++ b/lib/resty/lmdb/status.lua @@ -1,3 +1,7 @@ +local _M = {} + + +require("resty.lmdb.cdefs") local ffi = require("ffi") local base = require("resty.core.base") @@ -9,22 +13,6 @@ local NGX_ERROR = ngx.ERROR local err_ptr = base.get_errmsg_ptr() -local _M = {} - -ffi.cdef([[ - typedef struct { - size_t map_size; /**< Size of the data memory map */ - unsigned int page_size; /**< Size of a database page. */ - unsigned int max_readers; /**< max reader slots in the environment */ - unsigned int num_readers; /**< max reader slots used in the environment */ - unsigned int allocated_pages; /**< number of pages allocated */ - size_t in_use_pages; /**< number of pages currently in-use */ - unsigned int entries; /**< the number of entries (key/value pairs) in the environment */ - } ngx_lua_resty_lmdb_ffi_status_t; - - int ngx_lua_resty_lmdb_ffi_env_info(ngx_lua_resty_lmdb_ffi_status_t *lst, char **err); -]]) - function _M.get_env_info() local env_status = ffi_new("ngx_lua_resty_lmdb_ffi_status_t[1]") diff --git a/lib/resty/lmdb/transaction.lua b/lib/resty/lmdb/transaction.lua index ebc4dbd3..6441531a 100644 --- a/lib/resty/lmdb/transaction.lua +++ b/lib/resty/lmdb/transaction.lua @@ -1,37 +1,12 @@ local _M = {} +require("resty.lmdb.cdefs") local ffi = require("ffi") local base = require("resty.core.base") local table_new = require("table.new") -ffi.cdef([[ -typedef unsigned int MDB_dbi; - - -typedef enum { - NGX_LMDB_OP_GET = 0, - NGX_LMDB_OP_SET, - NGX_LMDB_OP_DB_OPEN, - NGX_LMDB_OP_DB_DROP, -} ngx_lua_resty_lmdb_operation_e; - - -typedef struct { - ngx_lua_resty_lmdb_operation_e opcode; - ngx_str_t key; /* GET, SET */ - ngx_str_t value; /* GET, SET */ - MDB_dbi dbi; /* ALL OPS */ - unsigned int flags; /* SET, DROP */ -} ngx_lua_resty_lmdb_operation_t; - - -int ngx_lua_resty_lmdb_ffi_execute(ngx_lua_resty_lmdb_operation_t *ops, - size_t n, int need_write, unsigned char *buf, size_t buf_len, char **err); -]]) - - local err_ptr = base.get_errmsg_ptr() local get_string_buf = base.get_string_buf local get_string_buf_size = base.get_string_buf_size @@ -43,8 +18,6 @@ local C = ffi.C local ffi_string = ffi.string local ffi_new = ffi.new local MIN_OPS_N = 16 -local DEFAULT_VALUE_BUF_SIZE = 16 * 1024 -- 16KB -base.set_string_buf_size(DEFAULT_VALUE_BUF_SIZE) local NGX_ERROR = ngx.ERROR local NGX_AGAIN = ngx.AGAIN local NGX_OK = ngx.OK @@ -55,6 +28,7 @@ _TXN_MT.__index = _TXN_MT local CACHED_DBI = {} local MDB_CREATE = 0x40000 local DEFAULT_DB = "_default" +_M.DEFAULT_DB = DEFAULT_DB function _M.begin(hint) diff --git a/src/ngx_lua_resty_lmdb_module.h b/src/ngx_lua_resty_lmdb_module.h index 9cd340ae..c2839612 100644 --- a/src/ngx_lua_resty_lmdb_module.h +++ b/src/ngx_lua_resty_lmdb_module.h @@ -23,6 +23,7 @@ typedef struct ngx_lua_resty_lmdb_conf_s ngx_lua_resty_lmdb_conf_t; typedef enum { NGX_LMDB_OP_GET = 0, + NGX_LMDB_OP_PREFIX, NGX_LMDB_OP_SET, NGX_LMDB_OP_DB_OPEN, NGX_LMDB_OP_DB_DROP diff --git a/src/ngx_lua_resty_lmdb_prefix.c b/src/ngx_lua_resty_lmdb_prefix.c new file mode 100644 index 00000000..d9a221f2 --- /dev/null +++ b/src/ngx_lua_resty_lmdb_prefix.c @@ -0,0 +1,121 @@ +#include + + +/* + * This function is the FFI call used for prefix lookups. + * It is very similar to `ngx_lua_resty_lmdb_ffi_execute` inside + * ngx_lua_resty_lmdb_transaction.c, + * except we can only specify one key as the starting point at a time. + * + * The `ops[0]` will be the resume point and ops[1] will be + * the desired prefix, this function returns all keys + * >= `ops[0].key` with the same prefix as ops[1].key and up to `n` + * will be returned at a time. They are usually not the same except + * for the call to return the first page result. + * + * Returns: + * * >= 0 - number of keys found. If return < `n`, then it is the last + * key in the map (no more keys afterward the last result) + * * `NGX_ERROR` - an error occurred, *err will contain the error string + * * `NGX_AGAIN` - `buf_len` is not enough, try again with larger `buf` + */ +int ngx_lua_resty_lmdb_ffi_prefix(ngx_lua_resty_lmdb_operation_t *ops, + size_t n, u_char *buf, size_t buf_len, const char **err) +{ + ngx_lua_resty_lmdb_conf_t *lcf; + size_t i; + MDB_txn *txn; + int rc; + MDB_val key; + MDB_val value; + MDB_cursor *cur; + ngx_lua_resty_lmdb_operation_t prefix; + + ngx_lua_resty_lmdb_assert(n >= 2); + prefix = ops[1]; + ngx_lua_resty_lmdb_assert(prefix.opcode == NGX_LMDB_OP_PREFIX); + + lcf = (ngx_lua_resty_lmdb_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_lua_resty_lmdb_module); + + if (lcf == NULL || lcf->env == NULL) { + *err = "no LMDB environment defined"; + return NGX_ERROR; + } + + txn = lcf->ro_txn; + rc = mdb_txn_renew(txn); + if (rc != 0) { + *err = mdb_strerror(rc); + return NGX_ERROR; + } + + rc = mdb_cursor_open(txn, ops[0].dbi, &cur); + if (rc != 0) { + *err = mdb_strerror(rc); + mdb_txn_reset(txn); + + return NGX_ERROR; + } + + /* we always have at least one ops slot as asserted above */ + + for (i = 0; i < n; i++) { + key.mv_size = ops[i].key.len; + key.mv_data = ops[i].key.data; + + rc = mdb_cursor_get(cur, &key, &value, i == 0 ? MDB_SET_RANGE : MDB_NEXT); + if (rc == 0) { + /* is this still a prefix of saved prefix? */ + + if (key.mv_size < prefix.key.len + || ngx_memcmp(key.mv_data, prefix.key.data, prefix.key.len) != 0) + { + /* caller asked "123" but we got "13" */ + mdb_cursor_close(cur); + mdb_txn_reset(txn); + + return i; + } + + /* key found, copy result into buf */ + if (key.mv_size + value.mv_size > buf_len) { + mdb_cursor_close(cur); + mdb_txn_reset(txn); + + return NGX_AGAIN; + } + + ops[i].key.data = buf; + ops[i].key.len = key.mv_size; + buf = ngx_cpymem(buf, key.mv_data, key.mv_size); + + ops[i].value.data = buf; + ops[i].value.len = value.mv_size; + buf = ngx_cpymem(buf, value.mv_data, value.mv_size); + + ops[i].opcode = NGX_LMDB_OP_PREFIX; + + buf_len -= key.mv_size + value.mv_size; + + } else if (rc == MDB_NOTFOUND) { + mdb_cursor_close(cur); + mdb_txn_reset(txn); + + return i; + + } else { + *err = mdb_strerror(rc); + + mdb_cursor_close(cur); + mdb_txn_reset(txn); + + return NGX_ERROR; + } + } + + mdb_cursor_close(cur); + mdb_txn_reset(txn); + + return i; +} diff --git a/src/ngx_lua_resty_lmdb_transaction.c b/src/ngx_lua_resty_lmdb_transaction.c index aa26c4b0..ad892b3c 100644 --- a/src/ngx_lua_resty_lmdb_transaction.c +++ b/src/ngx_lua_resty_lmdb_transaction.c @@ -1,6 +1,28 @@ #include +/* + * This function is the FFI call used for single key operations. + * + * Params: + * * `ops` - operations array containing instructions to perform + * * `n` - number of `ops` + * * `need_write` - 1 if contains edit operations, 0 if not + * * `buf` - results buf + * * `buf_len` - size of buf + * * `err` - err message output + * + * This function executes operations specified in `ops`. If the operation + * requires passing value back to the caller, then it is written to the + * `buf` buffer, up to `buf_len`. If the `buf` passed in is not large enough, + * then `NGX_AGAIN` is returned and the caller is instructed to retry with a + * larger buffer. + * + * Returns: + * * `NGX_OK` - operations completed successfully + * * `NGX_ERROR` - an error occurred, *err will contain the error string + * * `NGX_AGAIN` - `buf_len` is not enough, try again with larger `buf` + */ int ngx_lua_resty_lmdb_ffi_execute(ngx_lua_resty_lmdb_operation_t *ops, size_t n, int need_write, u_char *buf, size_t buf_len, const char **err) { diff --git a/t/02-multiple_dbs.t b/t/02-multiple_dbs.t index db3c001d..e1209773 100644 --- a/t/02-multiple_dbs.t +++ b/t/02-multiple_dbs.t @@ -35,6 +35,7 @@ __DATA__ ngx.say(l.set("test", "value", "custom_db")) ngx.say(l.get("test", "custom_db")) + ngx.say(l.set("test", "value")) -- main DB ngx.say(l.get("not_exist")) ngx.say(l.get("test", "not_exist")) ngx.say(l.get("test", "not_exist1")) @@ -46,6 +47,7 @@ GET /t --- response_body true value +true nil nilunable to open DB for access: MDB_NOTFOUND: No matching key/data pair found nilunable to open DB for access: MDB_NOTFOUND: No matching key/data pair found diff --git a/t/10-prefix.t b/t/10-prefix.t new file mode 100644 index 00000000..fa042767 --- /dev/null +++ b/t/10-prefix.t @@ -0,0 +1,216 @@ +# vim:set ft= ts=4 sw=4 et: + +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +repeat_each(2); + +plan tests => repeat_each() * blocks() * 5; + +my $pwd = cwd(); + +our $MainConfig = qq{ + lmdb_environment_path /tmp/test.mdb; + lmdb_map_size 5m; +}; + +our $HttpConfig = qq{ + lua_package_path "$pwd/lib/?.lua;;"; +}; + +no_long_string(); +#no_diff(); + +run_tests(); + +__DATA__ + +=== TEST 1: prefix() operation +--- http_config eval: $::HttpConfig +--- main_config eval: $::MainConfig +--- config + location = /t { + content_by_lua_block { + local l = require("resty.lmdb") + + ngx.say(l.db_drop(true)) + ngx.say(l.set("test", "value")) + ngx.say(l.set("test1", "value1")) + ngx.say(l.set("test2", "value2")) + ngx.say(l.set("test3", "value3")) + ngx.say(l.set("u", "value4")) + ngx.say(l.set("u1", "value5")) + + for k, v in l.prefix("tes") do + ngx.say("key: ", k, " value: ", v) + end + } + } +--- request +GET /t +--- response_body +true +true +true +true +true +true +true +key: test value: value +key: test1 value: value1 +key: test2 value: value2 +key: test3 value: value3 +--- no_error_log +[error] +[warn] +[crit] + + + +=== TEST 2: prefix() operation not found +--- http_config eval: $::HttpConfig +--- main_config eval: $::MainConfig +--- config + location = /t { + content_by_lua_block { + local l = require("resty.lmdb") + + ngx.say(l.db_drop(true)) + ngx.say(l.set("test", "value")) + + for k, v in l.prefix("test1") do + ngx.say("key: ", k, " value: ", v) + end + } + } +--- request +GET /t +--- response_body +true +true +--- no_error_log +[error] +[warn] +[crit] + + + +=== TEST 3: prefix() operation only 1 result +--- http_config eval: $::HttpConfig +--- main_config eval: $::MainConfig +--- config + location = /t { + content_by_lua_block { + local l = require("resty.lmdb") + + ngx.say(l.db_drop(true)) + ngx.say(l.set("test", "value")) + + for k, v in l.prefix("test") do + ngx.say("key: ", k, " value: ", v) + end + } + } +--- request +GET /t +--- response_body +true +true +key: test value: value +--- no_error_log +[error] +[warn] +[crit] + + + +=== TEST 4: prefix() operation 511-513 keys (edge cases) +--- http_config eval: $::HttpConfig +--- main_config eval: $::MainConfig +--- config + location = /t { + content_by_lua_block { + local l = require("resty.lmdb") + + for i = 511, 513 do + ngx.say(l.db_drop(true)) + + for j = 1, i do + assert(l.set("test:" .. j, "value:" .. j)) + end + + local j = 0 + local found = {} + for k, v in l.prefix("test:") do + j = j + 1 + found[k] = v + end + + ngx.say("j = ", j) + + for j = 1, i do + assert(found["test:" .. j] == "value:" .. j) + end + + ngx.say("done") + end + } + } +--- request +GET /t +--- response_body +true +j = 511 +done +true +j = 512 +done +true +j = 513 +done +--- no_error_log +[error] +[warn] +[crit] + + + +=== TEST 5: large values +--- http_config eval: $::HttpConfig +--- main_config eval: $::MainConfig +--- config + location = /t { + content_by_lua_block { + local l = require("resty.lmdb") + + ngx.say(l.db_drop(true)) + for i = 1, 1024 do + assert(l.set("test:" .. i, "value:" .. string.rep("x", i))) + end + + local j = 0 + local found = {} + for k, v in l.prefix("test:") do + j = j + 1 + found[k] = v + end + + ngx.say("j = ", j) + + for i = 1, 1024 do + assert(found["test:" .. i] == "value:" .. string.rep("x", i)) + end + + ngx.say("done") + } + } +--- request +GET /t +--- response_body +true +j = 1024 +done +--- no_error_log +[error] +[warn] +[crit]