Describe the bug

A short description of the bug.

To reproduce

my code:

local function get_range_last(keys, args)
    local key = args[1]
    local start_time = args[2]
    local end_time = args[3]
    local result = redis.call('TS.REVRANGE', key, start_time, end_time, 'COUNT', 1, 'LATEST')

    redis.log(redis.LOG_NOTICE, string.format("get_range_last for key %s: type of result is %s", key, type(result)))
    redis.log(redis.LOG_NOTICE, string.format("get_range_last for key %s: #result is %s", key, #result))

    if result and #result > 0 then
        redis.log(redis.LOG_NOTICE, string.format("get_range_last for key %s: type of result[1] is %s", key, type(result[1])))
        redis.log(redis.LOG_NOTICE, string.format("get_range_last for key %s: #result[1] is %s", key, #result[1]))

        if type(result[1]) == "table" and #result[1] == 2 then
            local timestamp = result[1][1]
            local value = result[1][2]
            redis.log(redis.LOG_NOTICE, string.format("get_range_last for key %s: timestamp = %s, value = %s", key, tostring(timestamp), tostring(value)))
            return value
        else
            redis.log(redis.LOG_WARNING, string.format("get_range_last for key %s: Unexpected result structure", key))
            return nil
        end
    else
        return nil
    end
end

redis log:

2024-07-10 13:57:26 8:M 10 Jul 2024 05:57:26.079 * get_range_last for key aaa:1m:open: type of result is table
2024-07-10 13:57:26 8:M 10 Jul 2024 05:57:26.079 * get_range_last for key aaa:1m:open: #result is 1
2024-07-10 13:57:26 8:M 10 Jul 2024 05:57:26.079 * get_range_last for key aaa:1m:open: type of result[1] is table
2024-07-10 13:57:26 8:M 10 Jul 2024 05:57:26.079 * get_range_last for key aaa:1m:open: #result[1] is 2
2024-07-10 13:57:26 8:M 10 Jul 2024 05:57:26.079 * get_range_last for key aaa:1m:open: timestamp = 1720574700, value = table: 0x563ee2039770

in redis-cli , it is 20 , not table

127.0.0.1:6379> TS.REVRANGE aaa:1m:volume - + COUNT 1 LASTEST
1) 1) (integer) 1720574700
   2) 20

Comment From: sundb

@eromoe did you use RESP3? if so 20 should be {double: 20} in lua, you can print is by result[1][2]['double']

Comment From: eromoe

Now I use this to get the value

local function table_to_int(value)
    if type(value) ~= "table" then
        return tonumber(value)
    end

    local first_key, first_value = next(value)
    if first_value == nil then
        return nil
    end

    return table_to_int(first_value)
end

@sundb I don't know where to check, I am using docker redis-stack-server:lastest .
It's very counter-intuitive and cost me much time to figure out.

Comment From: sundb

because the second reply of TS.REVRANGE is double, please ref Array reply of (Integer reply, Simple string reply) pairs representing (timestamp, value(double)) https://redis.io/docs/latest/commands/ts.revrange/

becausedouble is represented as string in RESP2, but it is a number in RESP3, but in LUA all number type are double, so in order to distinguish between integer and double, we use table to represent double.

local function table_to_int(value)
    if type(value) ~= "table" then
        return tonumber(value)
    end

    local first_key, first_value = next(value)
    if first_value == nil then
        return nil
    end

    return table_to_int(first_value)
end

you don't need to validate it, instead, you can use value['double'] directly.

Comment From: eromoe

@sundb

result[1][2]['double'] is nil

I have tried

local function get_range_last(keys, args)
    local key = args[1]
    local start_time = args[2]
    local end_time = args[3]

    -- TS.REVRANGE qmt:000001.SZ:1m:open 0 1721612760000 1721612760000 COUNT 1 LATEST
    local result = redis.call('TS.REVRANGE', key, start_time, end_time, 'COUNT', 1, 'LATEST')

    if result and #result > 0 then
        return result[1][2]['double']
    else
        return nil
    end
end
127.0.0.1:6379> TS.REVRANGE qmt:000001.SZ:1m:open 0 1721612760000 1721612760000 COUNT 1 LATEST
1) 1) (integer) 1721612760000
   2) 10.27
127.0.0.1:6379> FCALL get_range_last 0 qmt:000001.SZ:1m:open 1721612760000 1721612760000
(nil)

Comment From: eromoe

I found the result is {'ok' : value} , this is unconsistent with redis-cli api !

local function print_table(t)
    redis.log(redis.LOG_NOTICE, cjson.encode(t))
end

            local timestamp = result[1][1]
            local value = result[1][2]
            print_table(value)

Redis [BUG] TS.REVRANGE return value is table type [LUA]

Comment From: sundb

@sundb

result[1][2]['double'] is nil

I have tried

``` local function get_range_last(keys, args) local key = args[1] local start_time = args[2] local end_time = args[3]

-- TS.REVRANGE qmt:000001.SZ:1m:open 0 1721612760000 1721612760000 COUNT 1 LATEST
local result = redis.call('TS.REVRANGE', key, start_time, end_time, 'COUNT', 1, 'LATEST')

if result and #result > 0 then
    return result[1][2]['double']
else
    return nil
end

end ```

127.0.0.1:6379> TS.REVRANGE qmt:000001.SZ:1m:open 0 1721612760000 1721612760000 COUNT 1 LATEST 1) 1) (integer) 1721612760000 2) 10.27 127.0.0.1:6379> FCALL get_range_last 0 qmt:000001.SZ:1m:open 1721612760000 1721612760000 (nil)

you confuse the difference between reading it in Lua script and replying it to the client. read it in lua

local d = result[1][2]['double']

reply the double to client

return result[1][2]

Note that the format Lua gives you and the format you give back to Lua is the same, are all RESP3 double.

Comment From: sundb

I found the result is {'ok' : value} , this is unconsistent with redis-cli api !

because you are using RESP2, the simple string format is {ok: <string>}

Comment From: eromoe

@sundb I get it, check by hello find it is proto 2 on redis 7.25 , so resp3 is not enabled by default in latest docker image .

And the display problem in below is also by resp2 ? - here ts.get display value as double, but fcall return displays as integer (double in actually ) - ts.add the fcall return value to a new key, ts.get that new key would display as double too . This behaviour is really strange ..

127.0.0.1:6379> ts.get qmtagg:000010.SZ:1m:open
1) (integer) 1721619180000
2) 1.7
127.0.0.1:6379> FCALL get_range_last 0 qmtagg:000010.SZ:1m:open 1721619180000 1721619180000
(integer) 1

Comment From: sundb

@sundb I get it, check by hello find it is proto 2 on redis 7.25 , so resp3 is not enabled by default in latest docker image .

And the display problem in below is also by resp2 ?

  • here ts.get display value as double, but fcall return displays as integer (double in actually )
  • ts.add the fcall return value to a new key, ts.get that new key would display as double too . This behaviour is really strange ..

127.0.0.1:6379> ts.get qmtagg:000010.SZ:1m:open 1) (integer) 1721619180000 2) 1.7 127.0.0.1:6379> FCALL get_range_last 0 qmtagg:000010.SZ:1m:open 1721619180000 1721619180000 (integer) 1

if you want to return double to client , you should return `{double : 1.7}', otherwise, it will be treated as an integer.

Comment From: sundb

@eromoe please also ref https://redis.io/docs/latest/develop/interact/programmability/lua-api/#lua-to-resp3-type-conversion you need to know the lua and redis communication format.

Comment From: eromoe

Thank you very much for explanation, that inconsistent in redis luaenv is really hurt for newbie to lua . I think everyone use lua in redis would enconter this and the display problem, it would be much better to have a trouble shooting chapter in lua section, https://redis.io/docs/latest/develop/interact/programmability/lua-api/ .Because not easy to reach the convertion detail, Google and Stackoverflow don't contain similar questions and the LLMs also reply wrong answers.

Comment From: sundb

@eromoe i'm had been there before, feel free to call me here.