– Calls the Redis command: ZRANGEBYSCORE key min max – for each {key, min, max} given in the input arguments. Returns – the set intersection of the results local function intersect_range_index_sets(set, tuples)
for _, redis_key in ipairs(tuples) do local key, min, max = unpack(redis_key) local ids = redis.call('zrangebyscore', key, min, max) set = set_list_intersect(set, ids) end return set
end
– Runs a query against a sorted set, extracts ids. – Response from redis: attr_value:[attr_value …]:id – Returns a set of ids. local function get_id_set_from_custom_index(set, query)
local ids = {} local index_strings = {} local sep = ':' local id = '' local key, min, max = unpack(query) index_strings = redis.call('zrangebylex', key, min, max) for _, index_string in ipairs(index_strings) do id = string.match(index_string, '[^' .. sep .. ']+$') table.insert(ids, id) end set = to_set(ids) return set
end
– Gets the hash of all the ids given. Returns the results in a – table, as well as any ids not found in Redis as a separate table local function batch_hget(model, ids_set, …)
local res, stale_ids = {}, {} for id, _ in pairs(ids_set) do local instance = nil if #{...}> 0 then local values = redis.call('hmget', model .. ':id:' .. id, ...) -- HMGET returns the value in the order of the fields given. Map back to -- field value [field value ..] instance = {} for i, field in ipairs({...}) do if not values[i] then instance = nil break end table.insert(instance, field) table.insert(instance, values[i]) end else -- HGETALL returns the value as field value [field value ..] instance = redis.call('hgetall', model .. ':id:' .. id) end -- Only add to result if entry is not stale (if query to hgetall is not empty) if instance and #instance > 0 then -- We cannot return a Lua table to Redis as a hash. Return result as a flattened -- array instead table.insert(res, id) table.insert(res, instance) else table.insert(stale_ids, id) end end return {res, stale_ids}
end
– Returns the number of ids which exist in the given ids_set local function batch_exists(model, ids_set)
local id_keys = {} for id, _ in pairs(ids_set) do table.insert(id_keys, model .. ':id:' .. id) end if #id_keys == 0 then return 0 end return redis.call('exists', unpack(id_keys))
end
– Validate that each item in the attr_vals table is not nil local function validate_attr_vals(attr_key, attr_vals)
if not attr_vals or #attr_vals == 0 then error('Invalid value given for attribute : ' .. attr_key) end for _, val in ipairs(attr_vals) do if not val then error('Invalid value given for attribute : ' .. attr_key) end end
end
– Validate the query conditions by checking if the attributes queried for are indexed – attributes. Parse query conditions into two separate tables: – 1. index_sets formatted as the id set keys in Redis '#{Model.name}:#{attr_key}:#{attr_val}' – 2. range_index_sets formatted as a tuple {id set key, min, max} => { '#{Model.name}:#{attr_key}' min max } local function validate_and_parse_query_conditions(hash_tag, model, index_attrs, range_index_attrs, …)
-- Iterate through the arguments of the script to form the redis keys at which the -- indexed id sets are stored. local index_sets, range_index_sets = {}, {} local i = 1 while i <= #arg do local attr_key, attr_val = arg[i], arg[i+1] if index_attrs[attr_key] then validate_attr_vals(attr_key, {attr_val}) -- For normal index attributes, keys are stored at "#{Model.name}:#{attr_key}:#{attr_val}" table.insert(index_sets, model .. ':' .. attr_key .. ':' .. attr_val .. hash_tag) i = i + 2 elseif range_index_attrs[attr_key] then -- For range attributes, nil values are stored as normal sets if attr_val == "" then table.insert(index_sets, model .. ':' .. attr_key .. ':' .. attr_val .. hash_tag) i = i + 2 else local min, max = arg[i+1], arg[i+2] validate_attr_vals(attr_key, {min, max}) -- For range index attributes, they are stored at "#{Model.name}:#{attr_key}" table.insert(range_index_sets, {model .. ':' .. attr_key .. hash_tag, min, max}) i = i + 3 end else error(attr_key .. ' is not an indexed attribute') end end return {index_sets, range_index_sets}
end
– Validates that attributes in query are in correct order and range condition is applied only on the last attribute. – '~' is used as a character that is lexicographically greater than any alphanumerical. '[' makes range inclusive (exclusive are not yet supported) – Returns a table {index_key, min_string, max_string} to be used for index query. local function validate_and_parse_query_conditions_custom(hash_tag, model, index_name, custom_index_attrs, args)
if #custom_index_attrs == 0 then error('Index ' .. index_name .. ' does not exist') end local sep = ':' local i = 1 local j = 1 local min, value_string_min, query_string_min = '', '', '' local max, value_string_max, query_string_max = '', '', '' local is_prev_attr_query_range = false while i <= #args do if is_prev_attr_query_range then error('Range can be applied to the last attribute of query only') end local attr_key = args[i] if custom_index_attrs[j] == attr_key then min, max = args[i+1], args[i+2] if j > 1 then query_string_min = query_string_min .. sep query_string_max = query_string_max .. sep else query_string_min = query_string_min .. '[' query_string_max = query_string_max .. '[' end if min ~= '-inf' then value_string_min = adjust_string_length(min) query_string_min = query_string_min .. value_string_min end if max ~= '+inf' then value_string_max = adjust_string_length(max) query_string_max = query_string_max .. value_string_max else query_string_max = query_string_max .. '~' end if min ~= max then is_prev_attr_query_range = true end j = j + 1 i = i + 3 else error(attr_key .. ' in position ' .. j .. ' is not supported by index ' .. index_name) end end query_string_max = query_string_max .. sep .. '~' return {model .. sep .. 'custom_index' .. sep .. index_name .. hash_tag, query_string_min, query_string_max}
end