familia 1.2.1 → 2.0.0.pre2
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/.github/workflows/ci.yml +68 -0
- data/.github/workflows/docs.yml +64 -0
- data/.gitignore +4 -0
- data/.pre-commit-config.yaml +3 -1
- data/.rubocop.yml +16 -9
- data/.rubocop_todo.yml +177 -31
- data/.yardopts +9 -0
- data/CLAUDE.md +141 -0
- data/Gemfile +16 -2
- data/Gemfile.lock +97 -36
- data/README.md +39 -23
- data/bin/irb +3 -0
- data/docs/connection_pooling.md +192 -0
- data/familia.gemspec +10 -6
- data/lib/familia/base.rb +19 -9
- data/lib/familia/connection.rb +232 -65
- data/lib/familia/core_ext.rb +1 -1
- data/lib/familia/datatype/commands.rb +59 -0
- data/lib/familia/{redistype → datatype}/serialization.rb +9 -13
- data/lib/familia/{redistype → datatype}/types/hashkey.rb +25 -25
- data/lib/familia/{redistype → datatype}/types/list.rb +13 -13
- data/lib/familia/{redistype → datatype}/types/sorted_set.rb +20 -20
- data/lib/familia/{redistype → datatype}/types/string.rb +22 -21
- data/lib/familia/{redistype → datatype}/types/unsorted_set.rb +11 -11
- data/lib/familia/datatype.rb +243 -0
- data/lib/familia/errors.rb +5 -2
- data/lib/familia/features/expiration.rb +33 -34
- data/lib/familia/features/quantization.rb +9 -3
- data/lib/familia/features/safe_dump.rb +2 -3
- data/lib/familia/features.rb +2 -2
- data/lib/familia/horreum/class_methods.rb +97 -110
- data/lib/familia/horreum/commands.rb +46 -51
- data/lib/familia/horreum/connection.rb +82 -0
- data/lib/familia/horreum/{relations_management.rb → related_fields_management.rb} +37 -35
- data/lib/familia/horreum/serialization.rb +61 -198
- data/lib/familia/horreum/settings.rb +6 -17
- data/lib/familia/horreum/utils.rb +11 -10
- data/lib/familia/horreum.rb +69 -60
- data/lib/familia/logging.rb +12 -12
- data/lib/familia/multi_result.rb +72 -0
- data/lib/familia/refinements.rb +7 -44
- data/lib/familia/settings.rb +11 -11
- data/lib/familia/utils.rb +123 -90
- data/lib/familia/version.rb +4 -21
- data/lib/familia.rb +18 -13
- data/lib/middleware/database_middleware.rb +150 -0
- data/try/configuration/scenarios_try.rb +65 -0
- data/try/core/connection_try.rb +58 -0
- data/try/core/errors_try.rb +93 -0
- data/try/core/extensions_try.rb +26 -0
- data/try/{10_familia_try.rb → core/familia_extended_try.rb} +11 -10
- data/try/{00_familia_try.rb → core/familia_try.rb} +7 -5
- data/try/core/middleware_try.rb +68 -0
- data/try/core/refinements_try.rb +39 -0
- data/try/core/settings_try.rb +76 -0
- data/try/core/tools_try.rb +54 -0
- data/try/core/utils_try.rb +189 -0
- data/try/{26_redis_bool_try.rb → datatypes/boolean_try.rb} +4 -2
- data/try/datatypes/datatype_base_try.rb +69 -0
- data/try/{25_redis_type_hash_try.rb → datatypes/hash_try.rb} +5 -3
- data/try/{23_redis_type_list_try.rb → datatypes/list_try.rb} +5 -3
- data/try/{22_redis_type_set_try.rb → datatypes/set_try.rb} +5 -3
- data/try/{21_redis_type_zset_try.rb → datatypes/sorted_set_try.rb} +6 -4
- data/try/{24_redis_type_string_try.rb → datatypes/string_try.rb} +8 -8
- data/try/edge_cases/empty_identifiers_try.rb +48 -0
- data/try/{92_symbolize_try.rb → edge_cases/hash_symbolization_try.rb} +12 -7
- data/try/edge_cases/json_serialization_try.rb +85 -0
- data/try/edge_cases/race_conditions_try.rb +60 -0
- data/try/edge_cases/reserved_keywords_try.rb +59 -0
- data/try/{93_string_coercion_try.rb → edge_cases/string_coercion_try.rb} +60 -59
- data/try/edge_cases/ttl_side_effects_try.rb +51 -0
- data/try/features/expiration_try.rb +86 -0
- data/try/features/quantization_try.rb +90 -0
- data/try/{35_feature_safedump_try.rb → features/safe_dump_advanced_try.rb} +7 -6
- data/try/features/safe_dump_try.rb +137 -0
- data/try/{test_helpers.rb → helpers/test_helpers.rb} +25 -60
- data/try/{27_redis_horreum_try.rb → horreum/base_try.rb} +39 -14
- data/try/horreum/class_methods_try.rb +41 -0
- data/try/horreum/commands_try.rb +49 -0
- data/try/{29_redis_horreum_initialization_try.rb → horreum/initialization_try.rb} +9 -7
- data/try/horreum/relations_try.rb +146 -0
- data/try/{28_redis_horreum_serialization_try.rb → horreum/serialization_try.rb} +13 -11
- data/try/horreum/settings_try.rb +43 -0
- data/try/integration/cross_component_try.rb +46 -0
- data/try/{41_customer_safedump_try.rb → models/customer_safe_dump_try.rb} +9 -7
- data/try/{40_customer_try.rb → models/customer_try.rb} +21 -18
- data/try/models/datatype_base_try.rb +100 -0
- data/try/{30_familia_object_try.rb → models/familia_object_try.rb} +18 -16
- data/try/performance/benchmarks_try.rb +55 -0
- data/try/pooling/README.md +20 -0
- data/try/pooling/configurable_stress_test_try.rb +435 -0
- data/try/pooling/connection_pool_test_try.rb +273 -0
- data/try/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
- data/try/pooling/lib/connection_pool_metrics.rb +372 -0
- data/try/pooling/lib/connection_pool_stress_test.rb +959 -0
- data/try/pooling/lib/connection_pool_threading_models.rb +421 -0
- data/try/pooling/lib/visualize_stress_results.rb +434 -0
- data/try/pooling/pool_siege_try.rb +509 -0
- data/try/pooling/run_stress_tests_try.rb +482 -0
- data/try/prototypes/atomic_saves_v1_context_proxy.rb +121 -0
- data/try/prototypes/atomic_saves_v2_connection_switching.rb +161 -0
- data/try/prototypes/atomic_saves_v3_connection_pool.rb +189 -0
- data/try/prototypes/atomic_saves_v4.rb +105 -0
- data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +124 -0
- data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
- metadata +143 -46
- data/.github/workflows/ruby.yml +0 -71
- data/VERSION.yml +0 -4
- data/lib/familia/redistype/commands.rb +0 -59
- data/lib/familia/redistype.rb +0 -228
- data/lib/familia/tools.rb +0 -68
- data/lib/redis_middleware.rb +0 -109
- data/try/20_redis_type_try.rb +0 -70
- data/try/91_json_bug_try.rb +0 -86
@@ -1,12 +1,12 @@
|
|
1
|
-
#
|
1
|
+
# lib/familia/datatype/types/list.rb
|
2
2
|
|
3
3
|
module Familia
|
4
|
-
class List <
|
4
|
+
class List < DataType
|
5
5
|
|
6
6
|
# Returns the number of elements in the list
|
7
7
|
# @return [Integer] number of elements
|
8
8
|
def element_count
|
9
|
-
|
9
|
+
dbclient.llen dbkey
|
10
10
|
end
|
11
11
|
alias size element_count
|
12
12
|
|
@@ -16,8 +16,8 @@ module Familia
|
|
16
16
|
|
17
17
|
def push *values
|
18
18
|
echo :push, caller(1..1).first if Familia.debug
|
19
|
-
values.flatten.compact.each { |v|
|
20
|
-
|
19
|
+
values.flatten.compact.each { |v| dbclient.rpush dbkey, serialize_value(v) }
|
20
|
+
dbclient.ltrim dbkey, -@opts[:maxlength], -1 if @opts[:maxlength]
|
21
21
|
update_expiration
|
22
22
|
self
|
23
23
|
end
|
@@ -29,20 +29,20 @@ module Familia
|
|
29
29
|
alias add <<
|
30
30
|
|
31
31
|
def unshift *values
|
32
|
-
values.flatten.compact.each { |v|
|
32
|
+
values.flatten.compact.each { |v| dbclient.lpush dbkey, serialize_value(v) }
|
33
33
|
# TODO: test maxlength
|
34
|
-
|
34
|
+
dbclient.ltrim dbkey, 0, @opts[:maxlength] - 1 if @opts[:maxlength]
|
35
35
|
update_expiration
|
36
36
|
self
|
37
37
|
end
|
38
38
|
alias prepend unshift
|
39
39
|
|
40
40
|
def pop
|
41
|
-
deserialize_value
|
41
|
+
deserialize_value dbclient.rpop(dbkey)
|
42
42
|
end
|
43
43
|
|
44
44
|
def shift
|
45
|
-
deserialize_value
|
45
|
+
deserialize_value dbclient.lpop(dbkey)
|
46
46
|
end
|
47
47
|
|
48
48
|
def [](idx, count = nil)
|
@@ -65,7 +65,7 @@ module Familia
|
|
65
65
|
# @param count [Integer] Number of elements to remove (0 means all)
|
66
66
|
# @return [Integer] The number of removed elements
|
67
67
|
def remove_element(value, count = 0)
|
68
|
-
|
68
|
+
dbclient.lrem dbkey, count, serialize_value(value)
|
69
69
|
end
|
70
70
|
alias remove remove_element
|
71
71
|
|
@@ -75,7 +75,7 @@ module Familia
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def rangeraw(sidx = 0, eidx = -1)
|
78
|
-
|
78
|
+
dbclient.lrange(dbkey, sidx, eidx)
|
79
79
|
end
|
80
80
|
|
81
81
|
def members(count = -1)
|
@@ -124,7 +124,7 @@ module Familia
|
|
124
124
|
end
|
125
125
|
|
126
126
|
def at(idx)
|
127
|
-
deserialize_value
|
127
|
+
deserialize_value dbclient.lindex(dbkey, idx)
|
128
128
|
end
|
129
129
|
|
130
130
|
def first
|
@@ -157,6 +157,6 @@ module Familia
|
|
157
157
|
# end
|
158
158
|
# end
|
159
159
|
|
160
|
-
Familia::
|
160
|
+
Familia::DataType.register self, :list
|
161
161
|
end
|
162
162
|
end
|
@@ -1,11 +1,11 @@
|
|
1
|
-
#
|
1
|
+
# lib/familia/datatype/types/sorted_set.rb
|
2
2
|
|
3
3
|
module Familia
|
4
|
-
class SortedSet <
|
4
|
+
class SortedSet < DataType
|
5
5
|
# Returns the number of elements in the sorted set
|
6
6
|
# @return [Integer] number of elements
|
7
7
|
def element_count
|
8
|
-
|
8
|
+
dbclient.zcard dbkey
|
9
9
|
end
|
10
10
|
alias size element_count
|
11
11
|
|
@@ -46,32 +46,32 @@ module Familia
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def add(score, val)
|
49
|
-
ret =
|
49
|
+
ret = dbclient.zadd dbkey, score, serialize_value(val)
|
50
50
|
update_expiration
|
51
51
|
ret
|
52
52
|
end
|
53
53
|
|
54
54
|
def score(val)
|
55
|
-
ret =
|
55
|
+
ret = dbclient.zscore dbkey, serialize_value(val, strict_values: false)
|
56
56
|
ret&.to_f
|
57
57
|
end
|
58
58
|
alias [] score
|
59
59
|
|
60
60
|
def member?(val)
|
61
|
-
Familia.trace :MEMBER,
|
61
|
+
Familia.trace :MEMBER, dbclient, "#{val}<#{val.class}>", caller(1..1) if Familia.debug?
|
62
62
|
!rank(val).nil?
|
63
63
|
end
|
64
64
|
alias include? member?
|
65
65
|
|
66
66
|
# rank of member +v+ when ordered lowest to highest (starts at 0)
|
67
67
|
def rank(v)
|
68
|
-
ret =
|
68
|
+
ret = dbclient.zrank dbkey, serialize_value(v, strict_values: false)
|
69
69
|
ret&.to_i
|
70
70
|
end
|
71
71
|
|
72
72
|
# rank of member +v+ when ordered highest to lowest (starts at 0)
|
73
73
|
def revrank(v)
|
74
|
-
ret =
|
74
|
+
ret = dbclient.zrevrank dbkey, serialize_value(v, strict_values: false)
|
75
75
|
ret&.to_i
|
76
76
|
end
|
77
77
|
|
@@ -140,12 +140,12 @@ module Familia
|
|
140
140
|
def rangeraw(sidx, eidx, opts = {})
|
141
141
|
# NOTE: :withscores (no underscore) is the correct naming for the
|
142
142
|
# redis-4.x gem. We pass :withscores through explicitly b/c
|
143
|
-
#
|
143
|
+
# dbclient.zrange et al only accept that one optional argument.
|
144
144
|
# Passing `opts`` through leads to an ArgumentError:
|
145
145
|
#
|
146
146
|
# sorted_sets.rb:374:in `zrevrange': wrong number of arguments (given 4, expected 3) (ArgumentError)
|
147
147
|
#
|
148
|
-
|
148
|
+
dbclient.zrange(dbkey, sidx, eidx, **opts)
|
149
149
|
end
|
150
150
|
|
151
151
|
def revrange(sidx, eidx, opts = {})
|
@@ -155,7 +155,7 @@ module Familia
|
|
155
155
|
end
|
156
156
|
|
157
157
|
def revrangeraw(sidx, eidx, opts = {})
|
158
|
-
|
158
|
+
dbclient.zrevrange(dbkey, sidx, eidx, **opts)
|
159
159
|
end
|
160
160
|
|
161
161
|
# e.g. obj.metrics.rangebyscore (now-12.hours), now, :limit => [0, 10]
|
@@ -167,7 +167,7 @@ module Familia
|
|
167
167
|
|
168
168
|
def rangebyscoreraw(sscore, escore, opts = {})
|
169
169
|
echo :rangebyscoreraw, caller(1..1).first if Familia.debug
|
170
|
-
|
170
|
+
dbclient.zrangebyscore(dbkey, sscore, escore, **opts)
|
171
171
|
end
|
172
172
|
|
173
173
|
# e.g. obj.metrics.revrangebyscore (now-12.hours), now, :limit => [0, 10]
|
@@ -180,19 +180,19 @@ module Familia
|
|
180
180
|
def revrangebyscoreraw(sscore, escore, opts = {})
|
181
181
|
echo :revrangebyscoreraw, caller(1..1).first if Familia.debug
|
182
182
|
opts[:with_scores] = true if opts[:withscores]
|
183
|
-
|
183
|
+
dbclient.zrevrangebyscore(dbkey, sscore, escore, opts)
|
184
184
|
end
|
185
185
|
|
186
186
|
def remrangebyrank(srank, erank)
|
187
|
-
|
187
|
+
dbclient.zremrangebyrank dbkey, srank, erank
|
188
188
|
end
|
189
189
|
|
190
190
|
def remrangebyscore(sscore, escore)
|
191
|
-
|
191
|
+
dbclient.zremrangebyscore dbkey, sscore, escore
|
192
192
|
end
|
193
193
|
|
194
194
|
def increment(val, by = 1)
|
195
|
-
|
195
|
+
dbclient.zincrby(dbkey, by, val).to_i
|
196
196
|
end
|
197
197
|
alias incr increment
|
198
198
|
alias incrby increment
|
@@ -207,13 +207,13 @@ module Familia
|
|
207
207
|
# @param value The value to remove from the sorted set
|
208
208
|
# @return [Integer] The number of members that were removed (0 or 1)
|
209
209
|
def remove_element(value)
|
210
|
-
Familia.trace :REMOVE_ELEMENT,
|
210
|
+
Familia.trace :REMOVE_ELEMENT, dbclient, "#{value}<#{value.class}>", caller(1..1) if Familia.debug?
|
211
211
|
# We use `strict_values: false` here to allow for the deletion of values
|
212
212
|
# that are in the sorted set. If it's a horreum object, the value is
|
213
213
|
# the identifier and not a serialized version of the object. So either
|
214
214
|
# the value exists in the sorted set or it doesn't -- we don't need to
|
215
215
|
# raise an error if it's not found.
|
216
|
-
|
216
|
+
dbclient.zrem dbkey, serialize_value(value, strict_values: false)
|
217
217
|
end
|
218
218
|
alias remove remove_element # deprecated
|
219
219
|
|
@@ -231,7 +231,7 @@ module Familia
|
|
231
231
|
at(-1)
|
232
232
|
end
|
233
233
|
|
234
|
-
Familia::
|
235
|
-
Familia::
|
234
|
+
Familia::DataType.register self, :sorted_set
|
235
|
+
Familia::DataType.register self, :zset
|
236
236
|
end
|
237
237
|
end
|
@@ -1,7 +1,7 @@
|
|
1
|
-
#
|
1
|
+
# lib/familia/datatype/types/string.rb
|
2
2
|
|
3
3
|
module Familia
|
4
|
-
class String <
|
4
|
+
class String < DataType
|
5
5
|
def init; end
|
6
6
|
|
7
7
|
# Returns the number of elements in the list
|
@@ -17,22 +17,23 @@ module Familia
|
|
17
17
|
|
18
18
|
def value
|
19
19
|
echo :value, caller(0..0) if Familia.debug
|
20
|
-
|
21
|
-
deserialize_value
|
20
|
+
dbclient.setnx dbkey, @opts[:default] if @opts[:default]
|
21
|
+
deserialize_value dbclient.get(dbkey)
|
22
22
|
end
|
23
23
|
alias content value
|
24
24
|
alias get value
|
25
25
|
|
26
26
|
def to_s
|
27
|
-
|
27
|
+
return super if value.to_s.empty?
|
28
|
+
value.to_s
|
28
29
|
end
|
29
30
|
|
30
31
|
def to_i
|
31
|
-
value
|
32
|
+
value&.to_i || 0
|
32
33
|
end
|
33
34
|
|
34
35
|
def value=(val)
|
35
|
-
ret =
|
36
|
+
ret = dbclient.set(dbkey, serialize_value(val))
|
36
37
|
update_expiration
|
37
38
|
ret
|
38
39
|
end
|
@@ -40,68 +41,68 @@ module Familia
|
|
40
41
|
alias set value=
|
41
42
|
|
42
43
|
def setnx(val)
|
43
|
-
ret =
|
44
|
+
ret = dbclient.setnx(dbkey, serialize_value(val))
|
44
45
|
update_expiration
|
45
46
|
ret
|
46
47
|
end
|
47
48
|
|
48
49
|
def increment
|
49
|
-
ret =
|
50
|
+
ret = dbclient.incr(dbkey)
|
50
51
|
update_expiration
|
51
52
|
ret
|
52
53
|
end
|
53
54
|
alias incr increment
|
54
55
|
|
55
56
|
def incrementby(val)
|
56
|
-
ret =
|
57
|
+
ret = dbclient.incrby(dbkey, val.to_i)
|
57
58
|
update_expiration
|
58
59
|
ret
|
59
60
|
end
|
60
61
|
alias incrby incrementby
|
61
62
|
|
62
63
|
def decrement
|
63
|
-
ret =
|
64
|
+
ret = dbclient.decr dbkey
|
64
65
|
update_expiration
|
65
66
|
ret
|
66
67
|
end
|
67
68
|
alias decr decrement
|
68
69
|
|
69
70
|
def decrementby(val)
|
70
|
-
ret =
|
71
|
+
ret = dbclient.decrby dbkey, val.to_i
|
71
72
|
update_expiration
|
72
73
|
ret
|
73
74
|
end
|
74
75
|
alias decrby decrementby
|
75
76
|
|
76
77
|
def append(val)
|
77
|
-
ret =
|
78
|
+
ret = dbclient.append dbkey, val
|
78
79
|
update_expiration
|
79
80
|
ret
|
80
81
|
end
|
81
82
|
alias << append
|
82
83
|
|
83
84
|
def getbit(offset)
|
84
|
-
|
85
|
+
dbclient.getbit dbkey, offset
|
85
86
|
end
|
86
87
|
|
87
88
|
def setbit(offset, val)
|
88
|
-
ret =
|
89
|
+
ret = dbclient.setbit dbkey, offset, val
|
89
90
|
update_expiration
|
90
91
|
ret
|
91
92
|
end
|
92
93
|
|
93
94
|
def getrange(spoint, epoint)
|
94
|
-
|
95
|
+
dbclient.getrange dbkey, spoint, epoint
|
95
96
|
end
|
96
97
|
|
97
98
|
def setrange(offset, val)
|
98
|
-
ret =
|
99
|
+
ret = dbclient.setrange dbkey, offset, val
|
99
100
|
update_expiration
|
100
101
|
ret
|
101
102
|
end
|
102
103
|
|
103
104
|
def getset(val)
|
104
|
-
ret =
|
105
|
+
ret = dbclient.getset dbkey, val
|
105
106
|
update_expiration
|
106
107
|
ret
|
107
108
|
end
|
@@ -110,8 +111,8 @@ module Familia
|
|
110
111
|
value.nil?
|
111
112
|
end
|
112
113
|
|
113
|
-
Familia::
|
114
|
-
Familia::
|
115
|
-
Familia::
|
114
|
+
Familia::DataType.register self, :string
|
115
|
+
Familia::DataType.register self, :counter
|
116
|
+
Familia::DataType.register self, :lock
|
116
117
|
end
|
117
118
|
end
|
@@ -1,12 +1,12 @@
|
|
1
|
-
#
|
1
|
+
# lib/familia/datatype/types/unsorted_set.rb
|
2
2
|
|
3
3
|
module Familia
|
4
|
-
class Set <
|
4
|
+
class Set < DataType
|
5
5
|
|
6
6
|
# Returns the number of elements in the unsorted set
|
7
7
|
# @return [Integer] number of elements
|
8
8
|
def element_count
|
9
|
-
|
9
|
+
dbclient.scard dbkey
|
10
10
|
end
|
11
11
|
alias size element_count
|
12
12
|
|
@@ -15,7 +15,7 @@ module Familia
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def add *values
|
18
|
-
values.flatten.compact.each { |v|
|
18
|
+
values.flatten.compact.each { |v| dbclient.sadd? dbkey, serialize_value(v) }
|
19
19
|
update_expiration
|
20
20
|
self
|
21
21
|
end
|
@@ -33,7 +33,7 @@ module Familia
|
|
33
33
|
alias to_a members
|
34
34
|
|
35
35
|
def membersraw
|
36
|
-
|
36
|
+
dbclient.smembers(dbkey)
|
37
37
|
end
|
38
38
|
|
39
39
|
def each(&blk)
|
@@ -69,7 +69,7 @@ module Familia
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def member?(val)
|
72
|
-
|
72
|
+
dbclient.sismember dbkey, serialize_value(val)
|
73
73
|
end
|
74
74
|
alias include? member?
|
75
75
|
|
@@ -77,7 +77,7 @@ module Familia
|
|
77
77
|
# @param value The value to remove from the set
|
78
78
|
# @return [Integer] The number of members that were removed (0 or 1)
|
79
79
|
def remove_element(value)
|
80
|
-
|
80
|
+
dbclient.srem dbkey, serialize_value(value)
|
81
81
|
end
|
82
82
|
alias remove remove_element # deprecated
|
83
83
|
|
@@ -86,11 +86,11 @@ module Familia
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def pop
|
89
|
-
|
89
|
+
dbclient.spop dbkey
|
90
90
|
end
|
91
91
|
|
92
92
|
def move(dstkey, val)
|
93
|
-
|
93
|
+
dbclient.smove dbkey, dstkey, val
|
94
94
|
end
|
95
95
|
|
96
96
|
def random
|
@@ -98,7 +98,7 @@ module Familia
|
|
98
98
|
end
|
99
99
|
|
100
100
|
def randomraw
|
101
|
-
|
101
|
+
dbclient.srandmember(dbkey)
|
102
102
|
end
|
103
103
|
|
104
104
|
## Make the value stored at KEY identical to the given list
|
@@ -122,6 +122,6 @@ module Familia
|
|
122
122
|
# end
|
123
123
|
# end
|
124
124
|
|
125
|
-
Familia::
|
125
|
+
Familia::DataType.register self, :set
|
126
126
|
end
|
127
127
|
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
# lib/familia/datatype.rb
|
2
|
+
|
3
|
+
require_relative 'datatype/commands'
|
4
|
+
require_relative 'datatype/serialization'
|
5
|
+
|
6
|
+
module Familia
|
7
|
+
|
8
|
+
# DataType - Base class for Database data type wrappers
|
9
|
+
#
|
10
|
+
# This class provides common functionality for various Database data types
|
11
|
+
# such as String, List, Set, SortedSet, and HashKey.
|
12
|
+
#
|
13
|
+
# @abstract Subclass and implement Database data type specific methods
|
14
|
+
class DataType
|
15
|
+
include Familia::Base
|
16
|
+
extend Familia::Features
|
17
|
+
|
18
|
+
@registered_types = {}
|
19
|
+
@valid_options = %i[class parent default_expiration default logical_database dbkey dbclient suffix prefix]
|
20
|
+
@logical_database = nil
|
21
|
+
|
22
|
+
feature :expiration
|
23
|
+
feature :quantization
|
24
|
+
|
25
|
+
class << self
|
26
|
+
attr_reader :registered_types, :valid_options, :has_relations
|
27
|
+
attr_accessor :parent
|
28
|
+
attr_writer :logical_database, :uri
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
# To be called inside every class that inherits DataType
|
33
|
+
# +methname+ is the term used for the class and instance methods
|
34
|
+
# that are created for the given +klass+ (e.g. set, list, etc)
|
35
|
+
def register(klass, methname)
|
36
|
+
Familia.ld "[#{self}] Registering #{klass} as #{methname.inspect}"
|
37
|
+
|
38
|
+
@registered_types[methname] = klass
|
39
|
+
end
|
40
|
+
|
41
|
+
def logical_database(val = nil)
|
42
|
+
@logical_database = val unless val.nil?
|
43
|
+
@logical_database || parent&.logical_database
|
44
|
+
end
|
45
|
+
|
46
|
+
def uri(val = nil)
|
47
|
+
@uri = val unless val.nil?
|
48
|
+
@uri || (parent ? parent.uri : Familia.uri)
|
49
|
+
end
|
50
|
+
|
51
|
+
def inherited(obj)
|
52
|
+
Familia.trace :DATATYPE, nil, "#{obj} is my kinda type", caller(1..1) if Familia.debug?
|
53
|
+
obj.logical_database = logical_database
|
54
|
+
obj.default_expiration = default_expiration # method added via Features::Expiration
|
55
|
+
obj.uri = uri
|
56
|
+
obj.parent = self
|
57
|
+
super(obj)
|
58
|
+
end
|
59
|
+
|
60
|
+
def valid_keys_only(opts)
|
61
|
+
opts.select { |k, _| DataType.valid_options.include? k }
|
62
|
+
end
|
63
|
+
|
64
|
+
def has_relations?
|
65
|
+
@has_relations ||= false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
extend ClassMethods
|
69
|
+
|
70
|
+
attr_reader :keystring, :parent, :opts
|
71
|
+
attr_writer :dump_method, :load_method
|
72
|
+
|
73
|
+
# +keystring+: If parent is set, this will be used as the suffix
|
74
|
+
# for dbkey. Otherwise this becomes the value of the key.
|
75
|
+
# If this is an Array, the elements will be joined.
|
76
|
+
#
|
77
|
+
# Options:
|
78
|
+
#
|
79
|
+
# :class => A class that responds to Familia.load_method and
|
80
|
+
# Familia.dump_method. These will be used when loading and
|
81
|
+
# saving data from/to the database to unmarshal/marshal the class.
|
82
|
+
#
|
83
|
+
# :parent => The Familia object that this datatype object belongs
|
84
|
+
# to. This can be a class that includes Familia or an instance.
|
85
|
+
#
|
86
|
+
# :default_expiration => the time to live in seconds. When not nil, this will
|
87
|
+
# set the default expiration for this dbkey whenever #save is called.
|
88
|
+
# You can also call it explicitly via #update_expiration.
|
89
|
+
#
|
90
|
+
# :default => the default value (String-only)
|
91
|
+
#
|
92
|
+
# :logical_database => the logical database index to use (ignored if :dbclient is used).
|
93
|
+
#
|
94
|
+
# :dbclient => an instance of database client.
|
95
|
+
#
|
96
|
+
# :dbkey => a hardcoded key to use instead of the deriving the from
|
97
|
+
# the name and parent (e.g. a derived key: customer:custid:secret_counter).
|
98
|
+
#
|
99
|
+
# :suffix => the suffix to use for the key (e.g. 'scores' in customer:custid:scores).
|
100
|
+
# :prefix => the prefix to use for the key (e.g. 'customer' in customer:custid:scores).
|
101
|
+
#
|
102
|
+
# Connection precendence: uses the database connection of the parent or the
|
103
|
+
# value of opts[:dbclient] or Familia.dbclient (in that order).
|
104
|
+
def initialize(keystring, opts = {})
|
105
|
+
#Familia.ld " [initializing] #{self.class} #{opts}"
|
106
|
+
@keystring = keystring
|
107
|
+
@keystring = @keystring.join(Familia.delim) if @keystring.is_a?(Array)
|
108
|
+
|
109
|
+
# Remove all keys from the opts that are not in the allowed list
|
110
|
+
@opts = opts || {}
|
111
|
+
@opts = DataType.valid_keys_only(@opts)
|
112
|
+
|
113
|
+
# Apply the options to instance method setters of the same name
|
114
|
+
@opts.each do |k, v|
|
115
|
+
# Bewarde logging :parent instance here implicitly calls #to_s which for
|
116
|
+
# some classes could include the identifier which could still be nil at
|
117
|
+
# this point. This would result in a Familia::Problem being raised. So
|
118
|
+
# to be on the safe-side here until we have a better understanding of
|
119
|
+
# the issue, we'll just log the class name for each key-value pair.
|
120
|
+
Familia.ld " [setting] #{k} #{v.class}"
|
121
|
+
send(:"#{k}=", v) if respond_to? :"#{k}="
|
122
|
+
end
|
123
|
+
|
124
|
+
init if respond_to? :init
|
125
|
+
end
|
126
|
+
|
127
|
+
def dbclient
|
128
|
+
return Fiber[:familia_transaction] if Fiber[:familia_transaction]
|
129
|
+
return @dbclient if @dbclient
|
130
|
+
|
131
|
+
parent? ? parent.dbclient : Familia.dbclient(opts[:logical_database])
|
132
|
+
end
|
133
|
+
|
134
|
+
# Produces the full dbkey for this object.
|
135
|
+
#
|
136
|
+
# @return [String] The full dbkey.
|
137
|
+
#
|
138
|
+
# This method determines the appropriate dbkey based on the context of the DataType object:
|
139
|
+
#
|
140
|
+
# 1. If a hardcoded key is set in the options, it returns that key.
|
141
|
+
# 2. For instance-level DataType objects, it uses the parent instance's dbkey method.
|
142
|
+
# 3. For class-level DataType objects, it uses the parent class's dbkey method.
|
143
|
+
# 4. For standalone DataType objects, it uses the keystring as the full dbkey.
|
144
|
+
#
|
145
|
+
# For class-level DataType objects (parent_class? == true):
|
146
|
+
# - The suffix is optional and used to differentiate between different types of objects.
|
147
|
+
# - If no suffix is provided, the class's default suffix is used (via the self.suffix method).
|
148
|
+
# - If a nil suffix is explicitly passed, it won't appear in the resulting dbkey.
|
149
|
+
# - Passing nil as the suffix is how class-level DataType objects are created without
|
150
|
+
# the global default 'object' suffix.
|
151
|
+
#
|
152
|
+
# @example Instance-level DataType
|
153
|
+
# user_instance.some_datatype.dbkey # => "user:123:some_datatype"
|
154
|
+
#
|
155
|
+
# @example Class-level DataType
|
156
|
+
# User.some_datatype.dbkey # => "user:some_datatype"
|
157
|
+
#
|
158
|
+
# @example Standalone DataType
|
159
|
+
# DataType.new("mykey").dbkey # => "mykey"
|
160
|
+
#
|
161
|
+
# @example Class-level DataType with explicit nil suffix
|
162
|
+
# User.dbkey("123", nil) # => "user:123"
|
163
|
+
#
|
164
|
+
def dbkey
|
165
|
+
# Return the hardcoded key if it's set. This is useful for
|
166
|
+
# support legacy keys that aren't derived in the same way.
|
167
|
+
return opts[:dbkey] if opts[:dbkey]
|
168
|
+
|
169
|
+
if parent_instance?
|
170
|
+
# This is an instance-level datatype object so the parent instance's
|
171
|
+
# dbkey method is defined in Familia::Horreum::InstanceMethods.
|
172
|
+
parent.dbkey(keystring)
|
173
|
+
elsif parent_class?
|
174
|
+
# This is a class-level datatype object so the parent class' dbkey
|
175
|
+
# method is defined in Familia::Horreum::ClassMethods.
|
176
|
+
parent.dbkey(keystring, nil)
|
177
|
+
else
|
178
|
+
# This is a standalone DataType object where it's keystring
|
179
|
+
# is the full database key (dbkey).
|
180
|
+
keystring
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def class?
|
185
|
+
!@opts[:class].to_s.empty? && @opts[:class].is_a?(Familia)
|
186
|
+
end
|
187
|
+
|
188
|
+
def parent_instance?
|
189
|
+
parent.is_a?(Familia::Horreum)
|
190
|
+
end
|
191
|
+
|
192
|
+
def parent_class?
|
193
|
+
parent.is_a?(Class) && parent <= Familia::Horreum
|
194
|
+
end
|
195
|
+
|
196
|
+
def parent?
|
197
|
+
parent_class? || parent_instance?
|
198
|
+
end
|
199
|
+
|
200
|
+
def parent
|
201
|
+
@opts[:parent]
|
202
|
+
end
|
203
|
+
|
204
|
+
def logical_database
|
205
|
+
@opts[:logical_database] || self.class.logical_database
|
206
|
+
end
|
207
|
+
|
208
|
+
def uri
|
209
|
+
# If a specific URI is set in opts, use it
|
210
|
+
return @opts[:uri] if @opts[:uri]
|
211
|
+
|
212
|
+
# If parent has a DB set, create a URI with that DB
|
213
|
+
if parent? && parent.respond_to?(:logical_database) && parent.logical_database
|
214
|
+
base_uri = self.class.uri || Familia.uri
|
215
|
+
if base_uri
|
216
|
+
uri_with_db = base_uri.dup
|
217
|
+
uri_with_db.db = parent.logical_database
|
218
|
+
return uri_with_db
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Otherwise fall back to class URI
|
223
|
+
self.class.uri
|
224
|
+
end
|
225
|
+
|
226
|
+
def dump_method
|
227
|
+
@dump_method || self.class.dump_method
|
228
|
+
end
|
229
|
+
|
230
|
+
def load_method
|
231
|
+
@load_method || self.class.load_method
|
232
|
+
end
|
233
|
+
|
234
|
+
include Commands
|
235
|
+
include Serialization
|
236
|
+
end
|
237
|
+
|
238
|
+
require_relative 'datatype/types/list'
|
239
|
+
require_relative 'datatype/types/unsorted_set'
|
240
|
+
require_relative 'datatype/types/sorted_set'
|
241
|
+
require_relative 'datatype/types/hashkey'
|
242
|
+
require_relative 'datatype/types/string'
|
243
|
+
end
|
data/lib/familia/errors.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# lib/familia/errors.rb
|
2
|
+
#
|
3
3
|
module Familia
|
4
4
|
class Problem < RuntimeError; end
|
5
5
|
class NoIdentifier < Problem; end
|
@@ -31,6 +31,9 @@ module Familia
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
+
# Set Familia.connection_provider or use middleware to provide connections.
|
35
|
+
class NoConnectionAvailable < Problem; end
|
36
|
+
|
34
37
|
# Raised when attempting to refresh an object whose key doesn't exist in Redis
|
35
38
|
class KeyNotFoundError < Problem
|
36
39
|
attr_reader :key
|