redcord 0.0.3 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/redcord.rb +1 -0
- data/lib/redcord/actions.rb +78 -6
- data/lib/redcord/attribute.rb +110 -13
- data/lib/redcord/base.rb +13 -3
- data/lib/redcord/connection_pool.rb +28 -0
- data/lib/redcord/migration.rb +2 -0
- data/lib/redcord/migration/index.rb +57 -0
- data/lib/redcord/migration/ttl.rb +9 -4
- data/lib/redcord/railtie.rb +0 -1
- data/lib/redcord/redis.rb +200 -0
- data/lib/redcord/redis_connection.rb +29 -23
- data/lib/redcord/relation.rb +112 -14
- data/lib/redcord/serializer.rb +84 -33
- data/lib/redcord/server_scripts/create_hash.erb.lua +81 -0
- data/lib/redcord/server_scripts/delete_hash.erb.lua +17 -8
- data/lib/redcord/server_scripts/find_by_attr.erb.lua +51 -16
- data/lib/redcord/server_scripts/find_by_attr_count.erb.lua +45 -14
- data/lib/redcord/server_scripts/shared/index_helper_methods.erb.lua +45 -16
- data/lib/redcord/server_scripts/shared/lua_helper_methods.erb.lua +20 -4
- data/lib/redcord/server_scripts/shared/query_helper_methods.erb.lua +81 -14
- data/lib/redcord/server_scripts/update_hash.erb.lua +40 -26
- data/lib/redcord/tasks/redis.rake +15 -0
- data/lib/redcord/vacuum_helper.rb +90 -0
- metadata +21 -5
- data/lib/redcord/prepared_redis.rb +0 -147
- data/lib/redcord/server_scripts/create_hash_returning_id.erb.lua +0 -69
data/lib/redcord/serializer.rb
CHANGED
@@ -8,6 +8,8 @@ module Redcord
|
|
8
8
|
# Raised by Model.where
|
9
9
|
class AttributeNotIndexed < StandardError; end
|
10
10
|
class WrongAttributeType < TypeError; end
|
11
|
+
class CustomIndexInvalidQuery < StandardError; end
|
12
|
+
class CustomIndexInvalidDesign < StandardError; end
|
11
13
|
end
|
12
14
|
|
13
15
|
# This module defines various helper methods on Redcord for serialization
|
@@ -31,50 +33,36 @@ module Redcord::Serializer
|
|
31
33
|
sig { params(attribute: Symbol, val: T.untyped).returns(T.untyped) }
|
32
34
|
def encode_attr_value(attribute, val)
|
33
35
|
if !val.blank? && TIME_TYPES.include?(props[attribute][:type])
|
34
|
-
|
36
|
+
time_in_nano_sec = val.to_i * 1_000_000_000
|
37
|
+
time_in_nano_sec >= 0 ? time_in_nano_sec + val.nsec : time_in_nano_sec - val.nsec
|
38
|
+
elsif val.is_a?(Float)
|
39
|
+
# Encode as round-trippable float64
|
40
|
+
'%1.16e' % [val]
|
41
|
+
else
|
42
|
+
val
|
35
43
|
end
|
36
|
-
|
37
|
-
val
|
38
44
|
end
|
39
45
|
|
40
46
|
sig { params(attribute: Symbol, val: T.untyped).returns(T.untyped) }
|
41
47
|
def decode_attr_value(attribute, val)
|
42
48
|
if !val.blank? && TIME_TYPES.include?(props[attribute][:type])
|
43
|
-
val =
|
44
|
-
|
49
|
+
val = val.to_i
|
50
|
+
nsec = val >= 0 ? val % 1_000_000_000 : -val % 1_000_000_000
|
45
51
|
|
46
|
-
|
52
|
+
Time.zone.at(val / 1_000_000_000).change(nsec: nsec)
|
53
|
+
else
|
54
|
+
val
|
55
|
+
end
|
47
56
|
end
|
48
57
|
|
49
58
|
sig { params(attr_key: Symbol, attr_val: T.untyped).returns(T.untyped)}
|
50
|
-
def
|
51
|
-
# Validate
|
52
|
-
if !class_variable_get(:@@index_attributes).include?(attr_key) &&
|
53
|
-
!class_variable_get(:@@range_index_attributes).include?(attr_key)
|
54
|
-
raise(
|
55
|
-
Redcord::AttributeNotIndexed,
|
56
|
-
"#{attr_key} is not an indexed attribute.",
|
57
|
-
)
|
58
|
-
end
|
59
|
-
|
60
|
-
# Validate attribute types for normal index attributes
|
59
|
+
def validate_types_and_encode_query(attr_key, attr_val)
|
60
|
+
# Validate attribute types for index attributes
|
61
61
|
attr_type = get_attr_type(attr_key)
|
62
|
-
if class_variable_get(:@@index_attributes).include?(attr_key)
|
62
|
+
if class_variable_get(:@@index_attributes).include?(attr_key) || attr_key == shard_by_attribute
|
63
63
|
validate_attr_type(attr_val, attr_type)
|
64
64
|
else
|
65
|
-
|
66
|
-
if attr_val.is_a?(Redcord::RangeInterval)
|
67
|
-
validate_attr_type(
|
68
|
-
attr_val.min,
|
69
|
-
T.cast(T.nilable(attr_type), T::Types::Base),
|
70
|
-
)
|
71
|
-
validate_attr_type(
|
72
|
-
attr_val.max,
|
73
|
-
T.cast(T.nilable(attr_type), T::Types::Base),
|
74
|
-
)
|
75
|
-
else
|
76
|
-
validate_attr_type(attr_val, attr_type)
|
77
|
-
end
|
65
|
+
validate_range_attr_types(attr_val, attr_type)
|
78
66
|
|
79
67
|
# Range index attributes need to be further encoded into a format
|
80
68
|
# understood by the Lua script.
|
@@ -82,10 +70,73 @@ module Redcord::Serializer
|
|
82
70
|
attr_val = encode_range_index_attr_val(attr_key, attr_val)
|
83
71
|
end
|
84
72
|
end
|
85
|
-
|
86
73
|
attr_val
|
87
74
|
end
|
88
75
|
|
76
|
+
# Validate that attributes queried for are index attributes
|
77
|
+
# For custom index: validate that attributes are present in specified index
|
78
|
+
sig { params(attr_keys: T::Array[Symbol], custom_index_name: T.nilable(Symbol)).void}
|
79
|
+
def validate_index_attributes(attr_keys, custom_index_name: nil)
|
80
|
+
custom_index_attributes = class_variable_get(:@@custom_index_attributes)[custom_index_name]
|
81
|
+
attr_keys.each do |attr_key|
|
82
|
+
next if attr_key == shard_by_attribute
|
83
|
+
|
84
|
+
if !custom_index_attributes.empty?
|
85
|
+
if !custom_index_attributes.include?(attr_key)
|
86
|
+
raise(
|
87
|
+
Redcord::AttributeNotIndexed,
|
88
|
+
"#{attr_key} is not a part of #{custom_index_name} index.",
|
89
|
+
)
|
90
|
+
end
|
91
|
+
else
|
92
|
+
if !class_variable_get(:@@index_attributes).include?(attr_key) &&
|
93
|
+
!class_variable_get(:@@range_index_attributes).include?(attr_key)
|
94
|
+
raise(
|
95
|
+
Redcord::AttributeNotIndexed,
|
96
|
+
"#{attr_key} is not an indexed attribute.",
|
97
|
+
)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Validate exclusive ranges not used; Change all query conditions to range form;
|
104
|
+
# The position of the attribute and type of query is validated on Lua side
|
105
|
+
sig { params(query_conditions: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped])}
|
106
|
+
def validate_and_adjust_custom_index_query_conditions(query_conditions)
|
107
|
+
adjusted_query_conditions = query_conditions.clone
|
108
|
+
query_conditions.each do |attr_key, condition|
|
109
|
+
if !condition.is_a?(Array)
|
110
|
+
adjusted_query_conditions[attr_key] = [condition, condition]
|
111
|
+
elsif condition[0].to_s[0] == '(' or condition[1].to_s[0] == '('
|
112
|
+
raise(Redcord::CustomIndexInvalidQuery, "Custom index doesn't support exclusive ranges")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
adjusted_query_conditions
|
116
|
+
end
|
117
|
+
|
118
|
+
sig {
|
119
|
+
params(
|
120
|
+
attr_val: T.untyped,
|
121
|
+
attr_type: T.any(Class, T::Types::Base),
|
122
|
+
).void
|
123
|
+
}
|
124
|
+
def validate_range_attr_types(attr_val, attr_type)
|
125
|
+
# Validate attribute types for range index attributes
|
126
|
+
if attr_val.is_a?(Redcord::RangeInterval)
|
127
|
+
validate_attr_type(
|
128
|
+
attr_val.min,
|
129
|
+
T.cast(T.nilable(attr_type), T::Types::Base),
|
130
|
+
)
|
131
|
+
validate_attr_type(
|
132
|
+
attr_val.max,
|
133
|
+
T.cast(T.nilable(attr_type), T::Types::Base),
|
134
|
+
)
|
135
|
+
else
|
136
|
+
validate_attr_type(attr_val, attr_type)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
89
140
|
sig {
|
90
141
|
params(
|
91
142
|
attr_val: T.untyped,
|
@@ -137,7 +188,7 @@ module Redcord::Serializer
|
|
137
188
|
sig {
|
138
189
|
params(
|
139
190
|
redis_hash: T::Hash[T.untyped, T.untyped],
|
140
|
-
id:
|
191
|
+
id: String,
|
141
192
|
).returns(T.untyped)
|
142
193
|
}
|
143
194
|
def coerce_and_set_id(redis_hash, id)
|
@@ -0,0 +1,81 @@
|
|
1
|
+
--[[
|
2
|
+
EVALSHA SHA1(__FILE__) [field value ...]
|
3
|
+
> Time complexity: O(N) where N is the number of fields being set.
|
4
|
+
|
5
|
+
Create a hash with the specified fields to their respective values stored at
|
6
|
+
key when key does not exist.
|
7
|
+
|
8
|
+
# Return value
|
9
|
+
The id of the created hash as a string.
|
10
|
+
--]]
|
11
|
+
|
12
|
+
-- The arguments can be accessed by Lua using the KEYS global variable in the
|
13
|
+
-- form of a one-based array (so KEYS[1], KEYS[2], ...).
|
14
|
+
-- All the additional arguments should not represent key names and can be
|
15
|
+
-- accessed by Lua using the ARGV global variable, very similarly to what
|
16
|
+
-- happens with keys (so ARGV[1], ARGV[2], ...).
|
17
|
+
|
18
|
+
-- KEYS = id hash_tag
|
19
|
+
-- ARGV = Model.name ttl index_attr_size range_index_attr_size custom_index_attrs_flat_size [index_attr_key ...] [range_index_attr_key ...]
|
20
|
+
-- [custom_index_name attrs_size [custom_index_attr_key ...] ...] attr_key attr_val [attr_key attr_val ..]
|
21
|
+
<%= include_lua 'shared/lua_helper_methods' %>
|
22
|
+
<%= include_lua 'shared/index_helper_methods' %>
|
23
|
+
|
24
|
+
-- Validate input to script before making Redis db calls
|
25
|
+
if #KEYS ~= 2 then
|
26
|
+
error('Expected keys to be of size 2')
|
27
|
+
end
|
28
|
+
|
29
|
+
local id, hash_tag = unpack(KEYS)
|
30
|
+
local model, ttl = unpack(ARGV)
|
31
|
+
local key = model .. ':id:' .. id
|
32
|
+
|
33
|
+
local index_attr_pos = 6
|
34
|
+
local range_attr_pos = index_attr_pos + ARGV[3]
|
35
|
+
local custom_attr_pos = range_attr_pos + ARGV[4]
|
36
|
+
-- Starting position of the attr_key-attr_val pairs
|
37
|
+
local attr_pos = custom_attr_pos + ARGV[5]
|
38
|
+
|
39
|
+
|
40
|
+
if redis.call('exists', key) ~= 0 then
|
41
|
+
error(key .. ' already exists')
|
42
|
+
end
|
43
|
+
|
44
|
+
-- Forward the script arguments to the Redis command HSET.
|
45
|
+
-- Call the Redis command: HSET "#{Model.name}:id:#{id}" field value ...
|
46
|
+
redis.call('hset', key, unpack(ARGV, attr_pos))
|
47
|
+
|
48
|
+
-- Set TTL on key
|
49
|
+
if ttl and ttl ~= '-1' then
|
50
|
+
redis.call('expire', key, ttl)
|
51
|
+
end
|
52
|
+
|
53
|
+
-- Add id value for any index and range index attributes
|
54
|
+
local attrs_hash = to_hash(unpack(ARGV, attr_pos))
|
55
|
+
local index_attr_keys = {unpack(ARGV, index_attr_pos, range_attr_pos - 1)}
|
56
|
+
if #index_attr_keys > 0 then
|
57
|
+
for _, attr_key in ipairs(index_attr_keys) do
|
58
|
+
add_id_to_index_attr(hash_tag, model, attr_key, attrs_hash[attr_key], id)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
local range_index_attr_keys = {unpack(ARGV, range_attr_pos, custom_attr_pos - 1)}
|
62
|
+
if #range_index_attr_keys > 0 then
|
63
|
+
for _, attr_key in ipairs(range_index_attr_keys) do
|
64
|
+
add_id_to_range_index_attr(hash_tag, model, attr_key, attrs_hash[attr_key], id)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
-- Add a record to every custom index
|
69
|
+
local custom_index_attr_keys = {unpack(ARGV, custom_attr_pos, attr_pos - 1)}
|
70
|
+
local i = 1
|
71
|
+
while i < #custom_index_attr_keys do
|
72
|
+
local index_name, attrs_num = custom_index_attr_keys[i], custom_index_attr_keys[i+1]
|
73
|
+
local attr_values = {}
|
74
|
+
for j, attr_key in ipairs({unpack(custom_index_attr_keys, i + 2, i + attrs_num + 1)}) do
|
75
|
+
attr_values[j] = attrs_hash[attr_key]
|
76
|
+
end
|
77
|
+
add_record_to_custom_index(hash_tag, model, index_name, attr_values, id)
|
78
|
+
i = i + 2 + attrs_num
|
79
|
+
end
|
80
|
+
|
81
|
+
return nil
|
@@ -12,8 +12,8 @@ The number of keys deleted from Redis
|
|
12
12
|
-- The arguments can be accessed by Lua using the KEYS global variable in the
|
13
13
|
-- form of a one-based array (so KEYS[1], KEYS[2], ...).
|
14
14
|
--
|
15
|
-
-- KEYS
|
16
|
-
--
|
15
|
+
-- KEYS = id, hash_tag
|
16
|
+
-- ARGV = Model.name index_attr_size range_index_attr_size [index_attr_key ...] [range_index_attr_key ...] [custom_index_name ...]
|
17
17
|
<%= include_lua 'shared/index_helper_methods' %>
|
18
18
|
|
19
19
|
-- Validate input to script before making Redis db calls
|
@@ -21,28 +21,37 @@ if #KEYS ~= 2 then
|
|
21
21
|
error('Expected keys of be of size 2')
|
22
22
|
end
|
23
23
|
|
24
|
-
local model =
|
25
|
-
local id = KEYS
|
24
|
+
local model = ARGV[1]
|
25
|
+
local id, hash_tag = unpack(KEYS)
|
26
26
|
|
27
27
|
-- key = "#{model}:id:{id}"
|
28
28
|
local key = model .. ':id:' .. id
|
29
29
|
|
30
|
+
local index_attr_pos = 4
|
31
|
+
local range_attr_pos = index_attr_pos + ARGV[2]
|
32
|
+
local custom_index_pos = range_attr_pos + ARGV[3]
|
33
|
+
|
30
34
|
-- Clean up id sets for both index and range index attributes
|
31
|
-
local index_attr_keys =
|
35
|
+
local index_attr_keys = {unpack(ARGV, index_attr_pos, range_attr_pos - 1)}
|
32
36
|
if #index_attr_keys > 0 then
|
33
37
|
-- Retrieve old index attr values so we can delete them in the attribute id sets
|
34
38
|
local attr_vals = redis.call('hmget', key, unpack(index_attr_keys))
|
35
39
|
for i=1, #index_attr_keys do
|
36
|
-
delete_id_from_index_attr(model, index_attr_keys[i], attr_vals[i], id)
|
40
|
+
delete_id_from_index_attr(hash_tag, model, index_attr_keys[i], attr_vals[i], id)
|
37
41
|
end
|
38
42
|
end
|
39
|
-
local range_index_attr_keys =
|
43
|
+
local range_index_attr_keys = {unpack(ARGV, range_attr_pos, custom_index_pos - 1)}
|
40
44
|
if #range_index_attr_keys > 0 then
|
41
45
|
local attr_vals = redis.call('hmget', key, unpack(range_index_attr_keys))
|
42
46
|
for i=1, #range_index_attr_keys do
|
43
|
-
delete_id_from_range_index_attr(model, range_index_attr_keys[i], attr_vals[i], id)
|
47
|
+
delete_id_from_range_index_attr(hash_tag, model, range_index_attr_keys[i], attr_vals[i], id)
|
44
48
|
end
|
45
49
|
end
|
50
|
+
-- Delete record from custom indexes
|
51
|
+
local custom_index_names = {unpack(ARGV, custom_index_pos)}
|
52
|
+
for _, index_name in ipairs(custom_index_names) do
|
53
|
+
delete_record_from_custom_index(hash_tag, model, index_name, id)
|
54
|
+
end
|
46
55
|
|
47
56
|
-- delete the actual key
|
48
57
|
return redis.call('del', key)
|
@@ -15,9 +15,11 @@ A hash of id:model of all the ids that match the query conditions given.
|
|
15
15
|
-- accessed by Lua using the ARGV global variable, very similarly to what
|
16
16
|
-- happens with keys (so ARGV[1], ARGV[2], ...).
|
17
17
|
--
|
18
|
-
-- KEYS[1] =
|
19
|
-
-- ARGV
|
20
|
-
--
|
18
|
+
-- KEYS[1] = hash_tag
|
19
|
+
-- ARGV = Model.name custom_index_name num_index_attr num_range_index_attr num_custom_index_attr num_query_conditions
|
20
|
+
-- [index_attrs ...] [range_index_attrs ...] [custom_index_attrs ...] [query_conidtions ...] [attr_selections ...]
|
21
|
+
-- [query_conidtions ...]: [attr_key1 attr_val1 attr_key2 attr_val2 ...]
|
22
|
+
-- [attr_selections ...]: [attr_key1 attr_key2 ...]
|
21
23
|
-- For equality query conditions, key value pairs are expected to appear in
|
22
24
|
-- the KEYS array as [attr_key, attr_val]
|
23
25
|
-- For range query conditions, key value pairs are expected to appear in the
|
@@ -29,27 +31,60 @@ A hash of id:model of all the ids that match the query conditions given.
|
|
29
31
|
<%= include_lua 'shared/lua_helper_methods' %>
|
30
32
|
<%= include_lua 'shared/query_helper_methods' %>
|
31
33
|
|
32
|
-
if #KEYS
|
33
|
-
error('Expected keys to be
|
34
|
+
if #KEYS ~=1 then
|
35
|
+
error('Expected keys to be of size 1')
|
34
36
|
end
|
35
37
|
|
36
|
-
local model =
|
37
|
-
|
38
|
+
local model = ARGV[1]
|
39
|
+
|
40
|
+
local index_name = ARGV[2]
|
41
|
+
local index_attr_pos = 7
|
42
|
+
local range_attr_pos = index_attr_pos + ARGV[3]
|
43
|
+
local custom_attr_pos = range_attr_pos + ARGV[4]
|
44
|
+
local query_cond_pos = custom_attr_pos + ARGV[5]
|
45
|
+
local attr_selection_pos = query_cond_pos + ARGV[6]
|
38
46
|
|
39
47
|
-- Get all ids which have the corresponding attribute values.
|
40
48
|
local ids_set = nil
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
+
local index_sets, range_index_sets = {}, {}
|
50
|
+
|
51
|
+
-- If custom index name is empty -> use single-attribute indices
|
52
|
+
if index_name == '' then
|
53
|
+
index_sets, range_index_sets = unpack(validate_and_parse_query_conditions(
|
54
|
+
KEYS[1],
|
55
|
+
model,
|
56
|
+
to_set({unpack(ARGV, index_attr_pos, range_attr_pos - 1)}),
|
57
|
+
to_set({unpack(ARGV, range_attr_pos, custom_attr_pos - 1)}),
|
58
|
+
unpack(ARGV, query_cond_pos, attr_selection_pos - 1)
|
59
|
+
))
|
60
|
+
|
61
|
+
-- For normal sets, Redis has SINTER built in to return the set intersection
|
62
|
+
if #index_sets > 0 then
|
63
|
+
ids_set = to_set(redis.call('sinter', unpack(index_sets)))
|
64
|
+
end
|
65
|
+
-- For sorted sets, call helper function zinter_zrangebyscore, which calls
|
66
|
+
-- ZRANGEBYSCORE for each {redis_key, min, max} tuple and returns the set intersection
|
67
|
+
if #range_index_sets > 0 then
|
68
|
+
ids_set = intersect_range_index_sets(ids_set, range_index_sets)
|
69
|
+
end
|
70
|
+
else
|
71
|
+
local custom_index_attrs = {unpack(ARGV, custom_attr_pos, query_cond_pos - 1)}
|
72
|
+
local custom_index_query = validate_and_parse_query_conditions_custom(
|
73
|
+
KEYS[1],
|
74
|
+
model,
|
75
|
+
index_name,
|
76
|
+
custom_index_attrs,
|
77
|
+
{unpack(ARGV, query_cond_pos, attr_selection_pos - 1)}
|
78
|
+
)
|
79
|
+
if #custom_index_query > 0 then
|
80
|
+
ids_set = get_id_set_from_custom_index(ids_set, custom_index_query)
|
81
|
+
else
|
82
|
+
ids_set = {}
|
83
|
+
end
|
49
84
|
end
|
50
85
|
|
51
86
|
-- Query for the hashes for all ids in the set intersection
|
52
|
-
local res, stale_ids = unpack(batch_hget(model, ids_set, ARGV))
|
87
|
+
local res, stale_ids = unpack(batch_hget(model, ids_set, unpack(ARGV, attr_selection_pos)))
|
53
88
|
|
54
89
|
-- Delete any stale ids which are no longer in redis from the id sets.
|
55
90
|
-- This can happen if an entry was auto expired due to ttl, but not removed up yet
|
@@ -15,7 +15,9 @@ An integer number of records that match the query conditions given.
|
|
15
15
|
-- accessed by Lua using the ARGV global variable, very similarly to what
|
16
16
|
-- happens with keys (so ARGV[1], ARGV[2], ...).
|
17
17
|
--
|
18
|
-
-- KEYS[1] =
|
18
|
+
-- KEYS[1] = hash_tag
|
19
|
+
-- ARGV = Model.name custom_index_name num_index_attr num_range_index_attr num_custom_index_attr
|
20
|
+
-- [index_attrs ...] [range_index_attrs ...] [custom_index_attrs ...] [query_conidtions ...]
|
19
21
|
--
|
20
22
|
-- For equality query conditions, key value pairs are expected to appear in
|
21
23
|
-- the KEYS array as [attr_key, attr_val]
|
@@ -25,25 +27,54 @@ An integer number of records that match the query conditions given.
|
|
25
27
|
<%= include_lua 'shared/lua_helper_methods' %>
|
26
28
|
<%= include_lua 'shared/query_helper_methods' %>
|
27
29
|
|
28
|
-
if #KEYS
|
29
|
-
error('Expected keys to be
|
30
|
+
if #KEYS ~=1 then
|
31
|
+
error('Expected keys to be of size 1')
|
30
32
|
end
|
31
33
|
|
32
|
-
local model =
|
33
|
-
|
34
|
+
local model = ARGV[1]
|
35
|
+
|
36
|
+
local index_name = ARGV[2]
|
37
|
+
local index_attr_pos = 6
|
38
|
+
local range_attr_pos = index_attr_pos + ARGV[3]
|
39
|
+
local custom_attr_pos = range_attr_pos + ARGV[4]
|
40
|
+
local query_cond_pos = custom_attr_pos + ARGV[5]
|
34
41
|
|
35
42
|
-- Get all ids which have the corresponding attribute values.
|
36
43
|
local ids_set = nil
|
37
|
-
-- For normal sets, Redis has SINTER built in to return the set intersection
|
38
|
-
if #index_sets > 0 then
|
39
|
-
ids_set = to_set(redis.call('sinter', unpack(index_sets)))
|
40
|
-
end
|
41
|
-
-- For sorted sets, call helper function zinter_zrangebyscore, which calls
|
42
|
-
-- ZRANGEBYSCORE for each {redis_key, min, max} tuple and returns the set intersection
|
43
|
-
if #range_index_sets > 0 then
|
44
|
-
ids_set = intersect_range_index_sets(ids_set, range_index_sets)
|
45
|
-
end
|
46
44
|
|
45
|
+
if index_name == '' then
|
46
|
+
local index_sets, range_index_sets = unpack(validate_and_parse_query_conditions(
|
47
|
+
KEYS[1],
|
48
|
+
model,
|
49
|
+
to_set({unpack(ARGV, index_attr_pos, range_attr_pos - 1)}),
|
50
|
+
to_set({unpack(ARGV, range_attr_pos, custom_attr_pos - 1)}),
|
51
|
+
unpack(ARGV, query_cond_pos)
|
52
|
+
))
|
53
|
+
|
54
|
+
-- For normal sets, Redis has SINTER built in to return the set intersection
|
55
|
+
if #index_sets > 0 then
|
56
|
+
ids_set = to_set(redis.call('sinter', unpack(index_sets)))
|
57
|
+
end
|
58
|
+
-- For sorted sets, call helper function zinter_zrangebyscore, which calls
|
59
|
+
-- ZRANGEBYSCORE for each {redis_key, min, max} tuple and returns the set intersection
|
60
|
+
if #range_index_sets > 0 then
|
61
|
+
ids_set = intersect_range_index_sets(ids_set, range_index_sets)
|
62
|
+
end
|
63
|
+
else
|
64
|
+
local custom_index_attrs = {unpack(ARGV, custom_attr_pos, query_cond_pos - 1)}
|
65
|
+
local custom_index_query = validate_and_parse_query_conditions_custom(
|
66
|
+
KEYS[1],
|
67
|
+
model,
|
68
|
+
index_name,
|
69
|
+
custom_index_attrs,
|
70
|
+
{unpack(ARGV, query_cond_pos)}
|
71
|
+
)
|
72
|
+
if #custom_index_query > 0 then
|
73
|
+
ids_set = get_id_set_from_custom_index(ids_set, custom_index_query)
|
74
|
+
else
|
75
|
+
ids_set = {}
|
76
|
+
end
|
77
|
+
end
|
47
78
|
-- Get the number of records which satisfy the query conditions.
|
48
79
|
-- We do not delete stale ids as part of this function call because we do not have
|
49
80
|
-- the list of ids which don't exist. The Redis command EXISTS key [key ...] is an O(1)
|