From 9b24b0f9e949edf22c367d594118f15ba7a16491 Mon Sep 17 00:00:00 2001 From: tokers Date: Thu, 21 Feb 2019 13:39:32 +0800 Subject: [PATCH] feature: implemented the lindex method for list type share dict value The `lindex` method returns the element at index `index` in the shared dict list. Just like the `LINDEX` of Redis. --- README.markdown | 19 +++++ doc/HttpLuaModule.wiki | 15 ++++ src/ngx_http_lua_shdict.c | 165 +++++++++++++++++++++++++++++++++++++- t/062-count.t | 2 +- t/145-shdict-list.t | 137 +++++++++++++++++++++++++++++++ 5 files changed, 336 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index 3df44953e0..067f2aa1b4 100644 --- a/README.markdown +++ b/README.markdown @@ -3185,6 +3185,7 @@ Nginx API for Lua * [ngx.shared.DICT.lpop](#ngxshareddictlpop) * [ngx.shared.DICT.rpop](#ngxshareddictrpop) * [ngx.shared.DICT.llen](#ngxshareddictllen) +* [ngx.shared.DICT.lindex](#ngxshareddictlindex) * [ngx.shared.DICT.ttl](#ngxshareddictttl) * [ngx.shared.DICT.expire](#ngxshareddictexpire) * [ngx.shared.DICT.flush_all](#ngxshareddictflush_all) @@ -6235,6 +6236,7 @@ The resulting object `dict` has the following methods: * [lpop](#ngxshareddictlpop) * [rpop](#ngxshareddictrpop) * [llen](#ngxshareddictllen) +* [lindex](#ngxshareddictlindex) * [ttl](#ngxshareddictttl) * [expire](#ngxshareddictexpire) * [flush_all](#ngxshareddictflush_all) @@ -6607,6 +6609,23 @@ See also [ngx.shared.DICT](#ngxshareddict). [Back to TOC](#nginx-api-for-lua) +ngx.shared.DICT.lindex +---------------------- + +**syntax:** *val, err = ngx.shared.DICT:lindex(key, index)* + +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** + +Returns the element which at the index `index` of the list named `key` in the shm-base dictionary [ngx.shared.DICT](#ngxshareddict). + +The index is zero-based, so `0` means the first element, `1` for the second element and so forth. Also, negative index can be used to designate elements starting at the tail of the list, here `-1` means the last element and `-2` means the penultimate and so on. + +If `key` does not exist, it will return `nil`. When the `key` already takes a value that is not a list, it will return `nil` and `"value not a list"`. When `index` is out of the list length bound, the method will return `nil`. + +See also [ngx.shared.DICT](#ngxshareddict). + +[Back to TOC](#nginx-api-for-lua) + ngx.shared.DICT.ttl ------------------- **syntax:** *ttl, err = ngx.shared.DICT:ttl(key)* diff --git a/doc/HttpLuaModule.wiki b/doc/HttpLuaModule.wiki index 9222811251..718d184998 100644 --- a/doc/HttpLuaModule.wiki +++ b/doc/HttpLuaModule.wiki @@ -5232,6 +5232,7 @@ The resulting object dict has the following methods: * [[#ngx.shared.DICT.lpop|lpop]] * [[#ngx.shared.DICT.rpop|rpop]] * [[#ngx.shared.DICT.llen|llen]] +* [[#ngx.shared.DICT.lindex|lindex]] * [[#ngx.shared.DICT.ttl|ttl]] * [[#ngx.shared.DICT.expire|expire]] * [[#ngx.shared.DICT.flush_all|flush_all]] @@ -5553,6 +5554,20 @@ This feature was first introduced in the v0.10.6 release. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. +== ngx.shared.DICT.lindex == + +'''syntax:''' ''val, err = ngx.shared.DICT:lindex(key, index)'' + +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' + +Returns the element which at the index index of the list named key in the shm-base dictionary [[#ngx.shared.DICT|ngx.shared.DICT]]. + +The index is zero-based, so 0 means the first element, 1 for the second element and so forth. Also, negative index can be used to designate elements starting at the tail of the list, here -1 means the last element and -2 means the penultimate and so on. + +If key does not exist, it will return nil. When the key already takes a value that is not a list, it will return nil and "value not a list". When index is out of the list length bound, the method will return `nil`. + +See also [[#ngx.shared.DICT|ngx.shared.DICT]]. + == ngx.shared.DICT.ttl == '''syntax:''' ''ttl, err = ngx.shared.DICT:ttl(key)'' diff --git a/src/ngx_http_lua_shdict.c b/src/ngx_http_lua_shdict.c index b017bea658..387880bf7f 100644 --- a/src/ngx_http_lua_shdict.c +++ b/src/ngx_http_lua_shdict.c @@ -41,6 +41,7 @@ static int ngx_http_lua_shdict_lpop(lua_State *L); static int ngx_http_lua_shdict_rpop(lua_State *L); static int ngx_http_lua_shdict_pop_helper(lua_State *L, int flags); static int ngx_http_lua_shdict_llen(lua_State *L); +static int ngx_http_lua_shdict_lindex(lua_State *L); static ngx_inline ngx_shm_zone_t *ngx_http_lua_shdict_get_zone(lua_State *L, @@ -331,7 +332,7 @@ ngx_http_lua_inject_shdict_api(ngx_http_lua_main_conf_t *lmcf, lua_State *L) lua_createtable(L, 0, lmcf->shdict_zones->nelts /* nrec */); /* ngx.shared */ - lua_createtable(L, 0 /* narr */, 18 /* nrec */); /* shared mt */ + lua_createtable(L, 0 /* narr */, 19 /* nrec */); /* shared mt */ lua_pushcfunction(L, ngx_http_lua_shdict_get); lua_setfield(L, -2, "get"); @@ -375,6 +376,9 @@ ngx_http_lua_inject_shdict_api(ngx_http_lua_main_conf_t *lmcf, lua_State *L) lua_pushcfunction(L, ngx_http_lua_shdict_llen); lua_setfield(L, -2, "llen"); + lua_pushcfunction(L, ngx_http_lua_shdict_lindex); + lua_setfield(L, -2, "lindex"); + lua_pushcfunction(L, ngx_http_lua_shdict_flush_all); lua_setfield(L, -2, "flush_all"); @@ -2176,6 +2180,165 @@ ngx_http_lua_shdict_llen(lua_State *L) } +static int +ngx_http_lua_shdict_lindex(lua_State *L) +{ + int n, i, index; + uint8_t value_type; + uint32_t hash; + double num; + ngx_int_t rc; + ngx_str_t name, key, value; + ngx_queue_t *queue; + ngx_http_lua_shdict_ctx_t *ctx; + ngx_http_lua_shdict_node_t *sd; + ngx_shm_zone_t *zone; + ngx_http_lua_shdict_list_node_t *lnode; + + + n = lua_gettop(L); + + if (n != 3) { + return luaL_error(L, "expecting 3 arguments, " + "but only seen %d", n); + } + + if (lua_type(L, 1) != LUA_TTABLE) { + return luaL_error(L, "bad \"zone\" argument"); + } + + zone = ngx_http_lua_shdict_get_zone(L, 1); + if (zone == NULL) { + return luaL_error(L, "bad \"zone\" argument"); + } + + ctx = zone->data; + name = ctx->name; + + if (lua_isnil(L, 2)) { + lua_pushnil(L); + lua_pushliteral(L, "nil key"); + return 2; + } + + key.data = (u_char *) luaL_checklstring(L, 2, &key.len); + + if (key.len == 0) { + lua_pushnil(L); + lua_pushliteral(L, "empty key"); + return 2; + } + + if (key.len > 65535) { + lua_pushnil(L); + lua_pushliteral(L, "key too long"); + return 2; + } + + index = luaL_checkint(L, 3); + + hash = ngx_crc32_short(key.data, key.len); + + ngx_shmtx_lock(&ctx->shpool->mutex); + +#if 1 + ngx_http_lua_shdict_expire(ctx, 1); +#endif + + rc = ngx_http_lua_shdict_lookup(zone, hash, key.data, key.len, &sd); + + dd("shdict lookup returned %d", (int) rc); + + if (rc == NGX_DECLINED || rc == NGX_DONE) { + ngx_shmtx_unlock(&ctx->shpool->mutex); + lua_pushnil(L); + return 1; + } + + /* rc == NGX_OK */ + + if (sd->value_type != SHDICT_TLIST) { + ngx_shmtx_unlock(&ctx->shpool->mutex); + + lua_pushnil(L); + lua_pushliteral(L, "value not a list"); + return 2; + } + + ngx_queue_remove(&sd->queue); + ngx_queue_insert_head(&ctx->sh->lru_queue, &sd->queue); + + if (index >= (int) sd->value_len || -index > (int) sd->value_len) { + ngx_shmtx_unlock(&ctx->shpool->mutex); + + lua_pushnil(L); + return 1; + } + + queue = ngx_http_lua_shdict_get_list_head(sd, key.len); + + if (index >= 0) { /* forward */ + + for (i = 0; i <= index; i++) { + queue = ngx_queue_next(queue); + } + + } else { /* backward */ + + for (i = -1; i >= index; i--) { + queue = ngx_queue_prev(queue); + } + } + + lnode = ngx_queue_data(queue, ngx_http_lua_shdict_list_node_t, queue); + + value_type = lnode->value_type; + + dd("data: %p", lnode->data); + dd("value len: %d", (int) sd->value_len); + + value.data = lnode->data; + value.len = (size_t) lnode->value_len; + + switch (value_type) { + + case SHDICT_TSTRING: + + lua_pushlstring(L, (char *) value.data, value.len); + break; + + case SHDICT_TNUMBER: + + if (value.len != sizeof(double)) { + + ngx_shmtx_unlock(&ctx->shpool->mutex); + + return luaL_error(L, "bad lua list node number value size found" + " for key %s in shared_dict %s: %lu", + key.data, name.data, + (unsigned long) value.len); + } + + ngx_memcpy(&num, value.data, sizeof(double)); + + lua_pushnumber(L, num); + break; + + default: + + ngx_shmtx_unlock(&ctx->shpool->mutex); + + return luaL_error(L, "bad list node value type found for key %s in " + "shared_dict %s: %d", key.data, name.data, + value_type); + } + + ngx_shmtx_unlock(&ctx->shpool->mutex); + + return 1; +} + + ngx_shm_zone_t * ngx_http_lua_find_zone(u_char *name_data, size_t name_len) { diff --git a/t/062-count.t b/t/062-count.t index 2e6c5419e0..f000cda6df 100644 --- a/t/062-count.t +++ b/t/062-count.t @@ -283,7 +283,7 @@ n = 5 --- request GET /test --- response_body -n = 18 +n = 19 --- no_error_log [error] diff --git a/t/145-shdict-list.t b/t/145-shdict-list.t index 9bb1592606..9fe4c30a71 100644 --- a/t/145-shdict-list.t +++ b/t/145-shdict-list.t @@ -743,3 +743,140 @@ GET /test done --- no_error_log [error] + + + +=== TEST 20: lindex with positive index +--- http_config + lua_shared_dict dogs 1m; +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + + local value = { "hello", "world", "river", "lake" } + + for i = 1, #value do + local ok, err = dogs:lpush("corgi", value[i]) + if not ok then + ngx.say("lpush() error:", err) + return + end + end + + for i = 0, 4 do + ngx.say("lindex ", i, ": ", dogs:lindex("corgi", i)) + end + + local _, err = dogs:lpop("corgi") + if err ~= nil then + ngx.say("lpop() error: ", err) + end + + ngx.say("lindex 0: ", dogs:lindex("corgi", 0)) + + local _, err = dogs:lpop("corgi") + if err ~= nil then + ngx.say("lpop() error: ", err) + end + + ngx.say("lindex 1: ", dogs:lindex("corgi", 1)) + } + } +--- request +GET /t +--- response_body +lindex 0: lake +lindex 1: river +lindex 2: world +lindex 3: hello +lindex 4: nil +lindex 0: river +lindex 1: hello +--- no_error_log +[error] + + + +=== TEST 21: lindex with negative index +--- http_config + lua_shared_dict dogs 1m; +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + + local value = { "hello", "world", "river", "lake" } + + for i = 1, #value do + local ok, err = dogs:lpush("corgi", value[i]) + if not ok then + ngx.say("lpush() error:", err) + return + end + end + + for i = 1, 5 do + ngx.say("lindex -", i, ": ", dogs:lindex("corgi", -i)) + end + + local _, err = dogs:rpop("corgi") + if err ~= nil then + ngx.say("lpop() error: ", err) + end + + ngx.say("lindex -1: ", dogs:lindex("corgi", -1)) + + local _, err = dogs:rpop("corgi") + if err ~= nil then + ngx.say("lpop() error: ", err) + end + + ngx.say("lindex -2: ", dogs:lindex("corgi", -2)) + } + } +--- request +GET /t +--- response_body +lindex -1: hello +lindex -2: world +lindex -3: river +lindex -4: lake +lindex -5: nil +lindex -1: world +lindex -2: lake +--- no_error_log +[error] + + + +=== TEST 22: lindex for non-list value +--- http_config + lua_shared_dict dogs 1m; +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + + -- simple string value + local ok, err = dogs:set("garfield", "eat") + if not ok then + ngx.print("set() error: ", err) + return + end + + local ok, err = dogs:lindex("garfield", 0) + ngx.say(ok, " ", err) + + -- nil + local ok, err = dogs:lindex("bluecat", 3) + ngx.say(ok, " ", err) + } + } +--- request +GET /t +--- response_body +nil value not a list +nil nil +--- no_error_log +[error]