redcord 0.0.1.alpha → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/redcord.rb +30 -2
- data/lib/redcord.rbi +0 -16
- data/lib/redcord/actions.rb +171 -40
- data/lib/redcord/attribute.rb +126 -21
- data/lib/redcord/base.rb +15 -0
- data/lib/redcord/configurations.rb +4 -0
- data/lib/redcord/logger.rb +1 -1
- data/lib/redcord/lua_script_reader.rb +16 -5
- data/lib/redcord/migration.rb +2 -0
- data/lib/redcord/migration/index.rb +57 -0
- data/lib/redcord/migration/migrator.rb +1 -1
- data/lib/redcord/migration/ttl.rb +9 -4
- data/lib/redcord/migration/version.rb +3 -0
- data/lib/redcord/railtie.rb +18 -0
- data/lib/redcord/redis.rb +200 -0
- data/lib/redcord/redis_connection.rb +38 -29
- data/lib/redcord/relation.rb +214 -38
- data/lib/redcord/serializer.rb +147 -49
- 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 +50 -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 +86 -14
- data/lib/redcord/server_scripts/update_hash.erb.lua +40 -26
- data/lib/redcord/tasks/redis.rake +15 -0
- data/lib/redcord/tracer.rb +48 -0
- data/lib/redcord/vacuum_helper.rb +90 -0
- metadata +13 -11
- data/lib/redcord/prepared_redis.rb +0 -18
- data/lib/redcord/server_scripts.rb +0 -78
- data/lib/redcord/server_scripts/create_hash_returning_id.erb.lua +0 -68
data/lib/redcord/relation.rb
CHANGED
@@ -1,95 +1,271 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# typed: strict
|
4
|
+
|
5
|
+
require 'active_support/core_ext/array'
|
2
6
|
require 'active_support/core_ext/module'
|
3
7
|
|
8
|
+
module Redcord
|
9
|
+
class InvalidQuery < StandardError; end
|
10
|
+
end
|
11
|
+
|
4
12
|
class Redcord::Relation
|
5
13
|
extend T::Sig
|
6
14
|
|
7
15
|
sig { returns(T.class_of(Redcord::Base)) }
|
8
16
|
attr_reader :model
|
9
|
-
|
10
|
-
sig { returns(T::Hash[Symbol, T.untyped]) }
|
11
|
-
attr_reader :query_conditions
|
12
17
|
|
13
18
|
sig { returns(T::Set[Symbol]) }
|
14
19
|
attr_reader :select_attrs
|
15
20
|
|
16
|
-
|
17
|
-
|
21
|
+
sig { returns(T.nilable(Symbol)) }
|
22
|
+
attr_reader :custom_index_name
|
18
23
|
|
19
|
-
sig
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
def map(&blk)
|
25
|
-
to_a.map(&blk)
|
26
|
-
end
|
24
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
25
|
+
attr_reader :regular_index_query_conditions
|
26
|
+
|
27
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
28
|
+
attr_reader :custom_index_query_conditions
|
27
29
|
|
28
30
|
sig do
|
29
31
|
params(
|
30
32
|
model: T.class_of(Redcord::Base),
|
31
|
-
|
32
|
-
|
33
|
+
regular_index_query_conditions: T::Hash[Symbol, T.untyped],
|
34
|
+
custom_index_query_conditions: T::Hash[Symbol, T.untyped],
|
35
|
+
select_attrs: T::Set[Symbol],
|
36
|
+
custom_index_name: T.nilable(Symbol)
|
33
37
|
).void
|
34
38
|
end
|
35
|
-
def initialize(
|
39
|
+
def initialize(
|
40
|
+
model,
|
41
|
+
regular_index_query_conditions = {},
|
42
|
+
custom_index_query_conditions = {},
|
43
|
+
select_attrs = Set.new,
|
44
|
+
custom_index_name: nil
|
45
|
+
)
|
36
46
|
@model = model
|
37
|
-
@
|
47
|
+
@regular_index_query_conditions = regular_index_query_conditions
|
48
|
+
@custom_index_query_conditions = custom_index_query_conditions
|
38
49
|
@select_attrs = select_attrs
|
50
|
+
@custom_index_name = custom_index_name
|
39
51
|
end
|
40
52
|
|
41
53
|
sig { params(args: T::Hash[Symbol, T.untyped]).returns(Redcord::Relation) }
|
42
54
|
def where(args)
|
43
55
|
encoded_args = args.map do |attr_key, attr_val|
|
44
|
-
encoded_val = model.
|
56
|
+
encoded_val = model.validate_types_and_encode_query(attr_key, attr_val)
|
45
57
|
[attr_key, encoded_val]
|
46
58
|
end
|
47
|
-
|
59
|
+
|
60
|
+
regular_index_query_conditions.merge!(encoded_args.to_h)
|
61
|
+
if custom_index_name
|
62
|
+
with_index(custom_index_name)
|
63
|
+
end
|
48
64
|
self
|
49
65
|
end
|
50
66
|
|
51
67
|
sig do
|
52
68
|
params(
|
53
|
-
args:
|
69
|
+
args: T.untyped,
|
54
70
|
blk: T.nilable(T.proc.params(arg0: T.untyped).void),
|
55
71
|
).returns(T.any(Redcord::Relation, T::Array[T.untyped]))
|
56
72
|
end
|
57
73
|
def select(*args, &blk)
|
58
|
-
|
59
|
-
|
74
|
+
Redcord::Base.trace(
|
75
|
+
'redcord_relation_select',
|
76
|
+
model_name: model.name,
|
77
|
+
) do
|
78
|
+
if block_given?
|
79
|
+
return execute_query.select do |*item|
|
80
|
+
blk.call(*item)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
select_attrs.merge(args)
|
85
|
+
self
|
60
86
|
end
|
61
|
-
select_attrs.merge(args)
|
62
|
-
self
|
63
87
|
end
|
64
88
|
|
65
89
|
sig { returns(Integer) }
|
66
90
|
def count
|
67
|
-
|
91
|
+
Redcord::Base.trace(
|
92
|
+
'redcord_relation_count',
|
93
|
+
model_name: model.name,
|
94
|
+
) do
|
95
|
+
model.validate_index_attributes(query_conditions.keys, custom_index_name: custom_index_name)
|
96
|
+
redis.find_by_attr_count(
|
97
|
+
model.model_key,
|
98
|
+
extract_query_conditions!,
|
99
|
+
index_attrs: model._script_arg_index_attrs,
|
100
|
+
range_index_attrs: model._script_arg_range_index_attrs,
|
101
|
+
custom_index_attrs: model._script_arg_custom_index_attrs[custom_index_name],
|
102
|
+
hash_tag: extract_hash_tag!,
|
103
|
+
custom_index_name: custom_index_name
|
104
|
+
)
|
105
|
+
end
|
68
106
|
end
|
69
107
|
|
70
|
-
sig {
|
71
|
-
def
|
72
|
-
|
108
|
+
sig { params(index_name: T.nilable(Symbol)).returns(Redcord::Relation) }
|
109
|
+
def with_index(index_name)
|
110
|
+
@custom_index_name = index_name
|
111
|
+
adjusted_query_conditions = model.validate_and_adjust_custom_index_query_conditions(regular_index_query_conditions)
|
112
|
+
custom_index_query_conditions.merge!(adjusted_query_conditions)
|
113
|
+
self
|
73
114
|
end
|
74
115
|
|
116
|
+
delegate(
|
117
|
+
:&,
|
118
|
+
:[],
|
119
|
+
:all?,
|
120
|
+
:any?,
|
121
|
+
:any?,
|
122
|
+
:at,
|
123
|
+
:collect!,
|
124
|
+
:collect,
|
125
|
+
:compact!,
|
126
|
+
:compact,
|
127
|
+
:each,
|
128
|
+
:each_index,
|
129
|
+
:empty?,
|
130
|
+
:eql?,
|
131
|
+
:exists?,
|
132
|
+
:fetch,
|
133
|
+
:fifth!,
|
134
|
+
:fifth,
|
135
|
+
:filter!,
|
136
|
+
:filter,
|
137
|
+
:first!,
|
138
|
+
:first,
|
139
|
+
:forty_two!,
|
140
|
+
:forty_two,
|
141
|
+
:fourth!,
|
142
|
+
:fourth,
|
143
|
+
:include?,
|
144
|
+
:inspect,
|
145
|
+
:last!,
|
146
|
+
:last,
|
147
|
+
:many?,
|
148
|
+
:map!,
|
149
|
+
:map,
|
150
|
+
:none?,
|
151
|
+
:one?,
|
152
|
+
:reject!,
|
153
|
+
:reject,
|
154
|
+
:reverse!,
|
155
|
+
:reverse,
|
156
|
+
:reverse_each,
|
157
|
+
:second!,
|
158
|
+
:second,
|
159
|
+
:second_to_last!,
|
160
|
+
:second_to_last,
|
161
|
+
:size,
|
162
|
+
:sort!,
|
163
|
+
:sort,
|
164
|
+
:sort_by!,
|
165
|
+
:take!,
|
166
|
+
:take,
|
167
|
+
:third!,
|
168
|
+
:third,
|
169
|
+
:third_to_last!,
|
170
|
+
:third_to_last,
|
171
|
+
:to_a,
|
172
|
+
:to_ary,
|
173
|
+
:to_h,
|
174
|
+
:to_s,
|
175
|
+
:zip,
|
176
|
+
:|,
|
177
|
+
to: :execute_query,
|
178
|
+
)
|
179
|
+
|
75
180
|
private
|
181
|
+
|
182
|
+
sig { returns(T.nilable(String)) }
|
183
|
+
def extract_hash_tag!
|
184
|
+
attr = model.shard_by_attribute
|
185
|
+
return nil if attr.nil?
|
186
|
+
|
187
|
+
if !query_conditions.keys.include?(attr)
|
188
|
+
raise(
|
189
|
+
Redcord::InvalidQuery,
|
190
|
+
"Queries must contain attribute '#{attr}' since model #{model.name} is sharded by this attribute"
|
191
|
+
)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Query conditions on custom index are always in form of range, even when query is by value condition is [value_x, value_x]
|
195
|
+
# When in fact query is by value, range is trasformed to a single value to pass the validation.
|
196
|
+
condition = query_conditions[attr]
|
197
|
+
if custom_index_name and condition.first == condition.last
|
198
|
+
condition = condition.first
|
199
|
+
end
|
200
|
+
case condition
|
201
|
+
when Integer, String
|
202
|
+
"{#{condition}}"
|
203
|
+
else
|
204
|
+
raise(
|
205
|
+
Redcord::InvalidQuery,
|
206
|
+
"Does not support query condition #{condition} on a Redis Cluster",
|
207
|
+
)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
76
211
|
sig { returns(T::Array[T.untyped]) }
|
77
212
|
def execute_query
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
213
|
+
Redcord::Base.trace(
|
214
|
+
'redcord_relation_execute_query',
|
215
|
+
model_name: model.name,
|
216
|
+
) do
|
217
|
+
model.validate_index_attributes(query_conditions.keys, custom_index_name: custom_index_name)
|
218
|
+
if !select_attrs.empty?
|
219
|
+
res_hash = redis.find_by_attr(
|
220
|
+
model.model_key,
|
221
|
+
extract_query_conditions!,
|
222
|
+
select_attrs: select_attrs,
|
223
|
+
index_attrs: model._script_arg_index_attrs,
|
224
|
+
range_index_attrs: model._script_arg_range_index_attrs,
|
225
|
+
custom_index_attrs: model._script_arg_custom_index_attrs[custom_index_name],
|
226
|
+
hash_tag: extract_hash_tag!,
|
227
|
+
custom_index_name: custom_index_name
|
228
|
+
)
|
229
|
+
|
230
|
+
res_hash.map do |id, args|
|
231
|
+
model.from_redis_hash(args).map do |k, v|
|
232
|
+
[k.to_sym, TypeCoerce[model.get_attr_type(k.to_sym)].new.from(v)]
|
233
|
+
end.to_h.merge(id: id)
|
234
|
+
end
|
235
|
+
else
|
236
|
+
res_hash = redis.find_by_attr(
|
237
|
+
model.model_key,
|
238
|
+
extract_query_conditions!,
|
239
|
+
index_attrs: model._script_arg_index_attrs,
|
240
|
+
range_index_attrs: model._script_arg_range_index_attrs,
|
241
|
+
custom_index_attrs: model._script_arg_custom_index_attrs[custom_index_name],
|
242
|
+
hash_tag: extract_hash_tag!,
|
243
|
+
custom_index_name: custom_index_name
|
244
|
+
)
|
245
|
+
|
246
|
+
res_hash.map { |id, args| model.coerce_and_set_id(args, id) }
|
84
247
|
end
|
85
|
-
else
|
86
|
-
res_hash = redis.find_by_attr(model.model_key, query_conditions)
|
87
|
-
return res_hash.map { |id, args| model.coerce_and_set_id(args, id) }
|
88
248
|
end
|
89
249
|
end
|
90
250
|
|
91
|
-
sig { returns(Redcord::
|
251
|
+
sig { returns(Redcord::Redis) }
|
92
252
|
def redis
|
93
253
|
model.redis
|
94
254
|
end
|
255
|
+
|
256
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
257
|
+
def query_conditions
|
258
|
+
custom_index_name ? custom_index_query_conditions : regular_index_query_conditions
|
259
|
+
end
|
260
|
+
|
261
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
262
|
+
def extract_query_conditions!
|
263
|
+
attr = model.shard_by_attribute
|
264
|
+
return query_conditions if attr.nil?
|
265
|
+
|
266
|
+
cond = query_conditions.reject { |key| key == attr }
|
267
|
+
raise Redcord::InvalidQuery, "Cannot query only by shard_by_attribute: #{attr}" if cond.empty?
|
268
|
+
|
269
|
+
cond
|
270
|
+
end
|
95
271
|
end
|
data/lib/redcord/serializer.rb
CHANGED
@@ -1,14 +1,19 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# typed: strict
|
3
|
-
|
4
|
-
|
5
|
-
|
4
|
+
|
5
|
+
require 'redcord/range_interval'
|
6
|
+
|
6
7
|
module Redcord
|
7
8
|
# Raised by Model.where
|
8
9
|
class AttributeNotIndexed < StandardError; end
|
9
10
|
class WrongAttributeType < TypeError; end
|
11
|
+
class CustomIndexInvalidQuery < StandardError; end
|
12
|
+
class CustomIndexInvalidDesign < StandardError; end
|
10
13
|
end
|
11
14
|
|
15
|
+
# This module defines various helper methods on Redcord for serialization
|
16
|
+
# between the Ruby client and Redis server.
|
12
17
|
module Redcord::Serializer
|
13
18
|
extend T::Sig
|
14
19
|
|
@@ -16,84 +21,160 @@ module Redcord::Serializer
|
|
16
21
|
def self.included(klass)
|
17
22
|
klass.extend(ClassMethods)
|
18
23
|
end
|
19
|
-
|
24
|
+
|
20
25
|
module ClassMethods
|
21
26
|
extend T::Sig
|
22
27
|
|
23
|
-
# Redis only allows range queries on floats. To allow range queries on the
|
24
|
-
# type, encode_attr_value and decode_attr_value will implicitly
|
25
|
-
# Time attributes to a float.
|
28
|
+
# Redis only allows range queries on floats. To allow range queries on the
|
29
|
+
# Ruby Time type, encode_attr_value and decode_attr_value will implicitly
|
30
|
+
# encode and decode Time attributes to a float.
|
26
31
|
TIME_TYPES = T.let(Set[Time, T.nilable(Time)], T::Set[T.untyped])
|
32
|
+
|
27
33
|
sig { params(attribute: Symbol, val: T.untyped).returns(T.untyped) }
|
28
34
|
def encode_attr_value(attribute, val)
|
29
|
-
if val && TIME_TYPES.include?(props[attribute][:type])
|
30
|
-
|
35
|
+
if !val.blank? && TIME_TYPES.include?(props[attribute][:type])
|
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
|
31
43
|
end
|
32
|
-
val
|
33
44
|
end
|
34
45
|
|
35
46
|
sig { params(attribute: Symbol, val: T.untyped).returns(T.untyped) }
|
36
47
|
def decode_attr_value(attribute, val)
|
37
|
-
if val && TIME_TYPES.include?(props[attribute][:type])
|
38
|
-
val =
|
48
|
+
if !val.blank? && TIME_TYPES.include?(props[attribute][:type])
|
49
|
+
val = val.to_i
|
50
|
+
nsec = val >= 0 ? val % 1_000_000_000 : -val % 1_000_000_000
|
51
|
+
|
52
|
+
Time.zone.at(val / 1_000_000_000).change(nsec: nsec)
|
53
|
+
else
|
54
|
+
val
|
39
55
|
end
|
40
|
-
val
|
41
56
|
end
|
42
57
|
|
43
58
|
sig { params(attr_key: Symbol, attr_val: T.untyped).returns(T.untyped)}
|
44
|
-
def
|
45
|
-
# Validate
|
46
|
-
if !class_variable_get(:@@index_attributes).include?(attr_key) &&
|
47
|
-
!class_variable_get(:@@range_index_attributes).include?(attr_key)
|
48
|
-
raise Redcord::AttributeNotIndexed.new(
|
49
|
-
"#{attr_key} is not an indexed attribute."
|
50
|
-
)
|
51
|
-
end
|
52
|
-
# 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
|
53
61
|
attr_type = get_attr_type(attr_key)
|
54
|
-
if class_variable_get(:@@index_attributes).include?(attr_key)
|
62
|
+
if class_variable_get(:@@index_attributes).include?(attr_key) || attr_key == shard_by_attribute
|
55
63
|
validate_attr_type(attr_val, attr_type)
|
56
64
|
else
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
validate_attr_type(attr_val, attr_type)
|
63
|
-
end
|
64
|
-
# Range index attributes need to be further encoded into a format understood by the Lua script.
|
65
|
-
if attr_val != nil
|
65
|
+
validate_range_attr_types(attr_val, attr_type)
|
66
|
+
|
67
|
+
# Range index attributes need to be further encoded into a format
|
68
|
+
# understood by the Lua script.
|
69
|
+
unless attr_val.nil?
|
66
70
|
attr_val = encode_range_index_attr_val(attr_key, attr_val)
|
67
71
|
end
|
68
72
|
end
|
69
73
|
attr_val
|
70
74
|
end
|
71
75
|
|
72
|
-
|
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
|
+
|
140
|
+
sig {
|
141
|
+
params(
|
142
|
+
attr_val: T.untyped,
|
143
|
+
attr_type: T.any(Class, T::Types::Base),
|
144
|
+
).void
|
145
|
+
}
|
73
146
|
def validate_attr_type(attr_val, attr_type)
|
74
147
|
if (attr_type.is_a?(Class) && !attr_val.is_a?(attr_type)) ||
|
75
|
-
|
76
|
-
raise
|
77
|
-
|
148
|
+
(attr_type.is_a?(T::Types::Base) && !attr_type.valid?(attr_val))
|
149
|
+
raise(
|
150
|
+
Redcord::WrongAttributeType,
|
151
|
+
"Expected type #{attr_type}, got #{attr_val.class.name}",
|
78
152
|
)
|
79
153
|
end
|
80
154
|
end
|
81
155
|
|
82
|
-
sig {
|
156
|
+
sig {
|
157
|
+
params(
|
158
|
+
attribute: Symbol,
|
159
|
+
val: T.untyped,
|
160
|
+
).returns([T.untyped, T.untyped])
|
161
|
+
}
|
83
162
|
def encode_range_index_attr_val(attribute, val)
|
84
163
|
if val.is_a?(Redcord::RangeInterval)
|
85
|
-
# nil is treated as -inf and +inf. This is supported in Redis sorted
|
86
|
-
# so clients aren't required to know the highest and lowest scores
|
164
|
+
# nil is treated as -inf and +inf. This is supported in Redis sorted
|
165
|
+
# sets so clients aren't required to know the highest and lowest scores
|
166
|
+
# in a range
|
87
167
|
min_val = !val.min ? '-inf' : encode_attr_value(attribute, val.min)
|
88
168
|
max_val = !val.max ? '+inf' : encode_attr_value(attribute, val.max)
|
89
169
|
|
90
|
-
# In Redis, by default min and max is closed. You can prefix the score
|
91
|
-
# specify an open interval.
|
170
|
+
# In Redis, by default min and max is closed. You can prefix the score
|
171
|
+
# with '(' to specify an open interval.
|
92
172
|
min_val = val.min_exclusive ? '(' + min_val.to_s : min_val.to_s
|
93
173
|
max_val = val.max_exclusive ? '(' + max_val.to_s : max_val.to_s
|
94
|
-
|
174
|
+
[min_val, max_val]
|
95
175
|
else
|
96
|
-
# Equality queries for range indices are be passed to redis as a range
|
176
|
+
# Equality queries for range indices are be passed to redis as a range
|
177
|
+
# [val, val].
|
97
178
|
encoded_val = encode_attr_value(attribute, val)
|
98
179
|
[encoded_val, encoded_val]
|
99
180
|
end
|
@@ -104,24 +185,41 @@ module Redcord::Serializer
|
|
104
185
|
props[attr_key][:type_object]
|
105
186
|
end
|
106
187
|
|
107
|
-
sig {
|
188
|
+
sig {
|
189
|
+
params(
|
190
|
+
redis_hash: T::Hash[T.untyped, T.untyped],
|
191
|
+
id: String,
|
192
|
+
).returns(T.untyped)
|
193
|
+
}
|
108
194
|
def coerce_and_set_id(redis_hash, id)
|
109
|
-
# Coerce each serialized result returned from Redis back into Model
|
195
|
+
# Coerce each serialized result returned from Redis back into Model
|
196
|
+
# instance
|
110
197
|
instance = TypeCoerce.send(:[], self).new.from(from_redis_hash(redis_hash))
|
111
198
|
instance.send(:id=, id)
|
112
199
|
instance
|
113
200
|
end
|
201
|
+
|
114
202
|
sig { returns(String) }
|
115
203
|
def model_key
|
116
204
|
"Redcord:#{name}"
|
117
205
|
end
|
118
206
|
|
119
|
-
sig {
|
207
|
+
sig {
|
208
|
+
params(
|
209
|
+
args: T::Hash[T.any(String, Symbol), T.untyped],
|
210
|
+
).returns(T::Hash[Symbol, T.untyped])
|
211
|
+
}
|
120
212
|
def to_redis_hash(args)
|
121
|
-
args.map
|
213
|
+
args.map do |key, val|
|
214
|
+
[key.to_sym, encode_attr_value(key.to_sym, val)]
|
215
|
+
end.to_h
|
122
216
|
end
|
123
217
|
|
124
|
-
sig {
|
218
|
+
sig {
|
219
|
+
params(
|
220
|
+
args: T::Hash[T.untyped, T.untyped],
|
221
|
+
).returns(T::Hash[T.untyped, T.untyped])
|
222
|
+
}
|
125
223
|
def from_redis_hash(args)
|
126
224
|
args.map { |key, val| [key, decode_attr_value(key.to_sym, val)] }.to_h
|
127
225
|
end
|