familia 1.2.3 → 2.0.0.pre.pre
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 +3 -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 +15 -2
- data/Gemfile.lock +61 -61
- data/README.md +39 -23
- data/bin/irb +3 -0
- data/docs/connection_pooling.md +317 -0
- data/familia.gemspec +8 -5
- 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 -130
- 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 +17 -12
- 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} +5 -3
- 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 -8
- 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} +63 -60
- 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} +20 -17
- data/try/models/datatype_base_try.rb +101 -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 +124 -38
- 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
data/lib/familia/base.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
#
|
1
|
+
# lib/familia/base.rb
|
2
2
|
|
3
3
|
#
|
4
4
|
module Familia
|
5
|
-
# A common module for Familia::
|
5
|
+
# A common module for Familia::DataType and Familia::Horreum to include.
|
6
6
|
#
|
7
7
|
# This allows us to use a single comparison to check if a class is a
|
8
8
|
# Familia class. e.g.
|
@@ -11,13 +11,23 @@ module Familia
|
|
11
11
|
# klass.ancestors.member?(Familia::Base) # => true
|
12
12
|
#
|
13
13
|
# @see Familia::Horreum
|
14
|
-
# @see Familia::
|
14
|
+
# @see Familia::DataType
|
15
15
|
#
|
16
16
|
module Base
|
17
17
|
@features = nil
|
18
18
|
@dump_method = :to_json
|
19
19
|
@load_method = :from_json
|
20
20
|
|
21
|
+
# Returns a string representation of the object. Implementing classes
|
22
|
+
# are welcome to override this method to provide a more meaningful
|
23
|
+
# representation. Using this as a default via super is recommended.
|
24
|
+
#
|
25
|
+
# @return [String] A string representation of the object. Never nil.
|
26
|
+
#
|
27
|
+
def to_s
|
28
|
+
"#<#{self.class}:0x#{object_id.to_s(16)}>"
|
29
|
+
end
|
30
|
+
|
21
31
|
class << self
|
22
32
|
attr_reader :features
|
23
33
|
attr_accessor :dump_method, :load_method
|
@@ -34,23 +44,23 @@ module Familia
|
|
34
44
|
# with the :expiration feature's implementation.
|
35
45
|
#
|
36
46
|
# This is a no-op implementation that gets overridden by features like
|
37
|
-
# :expiration. It accepts an optional
|
47
|
+
# :expiration. It accepts an optional default_expiration parameter to maintain interface
|
38
48
|
# compatibility with the overriding implementations.
|
39
49
|
#
|
40
|
-
# @param
|
50
|
+
# @param default_expiration [Integer, nil] Time To Live in seconds (ignored in base implementation)
|
41
51
|
# @return [nil] Always returns nil
|
42
52
|
#
|
43
53
|
# @note This is a no-op implementation. Classes that need expiration
|
44
54
|
# functionality should include the :expiration feature.
|
45
55
|
#
|
46
|
-
def update_expiration(
|
47
|
-
Familia.ld "[update_expiration] Feature not enabled for #{self.class}. Key: #{
|
56
|
+
def update_expiration(default_expiration: nil)
|
57
|
+
Familia.ld "[update_expiration] Feature not enabled for #{self.class}. Key: #{dbkey} (caller: #{caller(1..1)})"
|
48
58
|
nil
|
49
59
|
end
|
50
60
|
|
51
61
|
def generate_id
|
52
|
-
@
|
53
|
-
@
|
62
|
+
@identifier ||= Familia.generate_id
|
63
|
+
@identifier
|
54
64
|
end
|
55
65
|
|
56
66
|
def uuid
|
data/lib/familia/connection.rb
CHANGED
@@ -1,103 +1,270 @@
|
|
1
|
-
#
|
1
|
+
# lib/familia/connection.rb
|
2
2
|
|
3
|
-
require_relative '../../lib/
|
3
|
+
require_relative '../../lib/middleware/database_middleware'
|
4
|
+
require_relative 'multi_result'
|
4
5
|
|
6
|
+
# Familia
|
7
|
+
#
|
8
|
+
# A family warehouse for your keystore data.
|
5
9
|
#
|
6
10
|
module Familia
|
7
|
-
@uri = URI.parse 'redis://127.0.0.1'
|
8
|
-
@
|
9
|
-
@redis_uri_by_class = {}
|
11
|
+
@uri = URI.parse 'redis://127.0.0.1:6379'
|
12
|
+
@database_clients = {}
|
10
13
|
|
11
|
-
# The Connection module provides
|
12
|
-
# It allows easy setup and access to
|
14
|
+
# The Connection module provides Database connection management for Familia.
|
15
|
+
# It allows easy setup and access to Database clients across different URIs
|
16
|
+
# with robust connection pooling for thread safety.
|
13
17
|
module Connection
|
14
|
-
# @return [
|
15
|
-
attr_reader :redis_clients
|
16
|
-
|
17
|
-
# @return [URI] The default URI for Redis connections
|
18
|
+
# @return [URI] The default URI for Database connections
|
18
19
|
attr_reader :uri
|
19
20
|
|
20
|
-
# @return [
|
21
|
-
|
21
|
+
# @return [Hash] A hash of Database clients, keyed by server ID
|
22
|
+
attr_reader :database_clients
|
23
|
+
|
24
|
+
# @return [Boolean] Whether Database command logging is enabled
|
25
|
+
attr_accessor :enable_database_logging
|
26
|
+
|
27
|
+
# @return [Boolean] Whether Database command counter is enabled
|
28
|
+
attr_accessor :enable_database_counter
|
22
29
|
|
23
|
-
# @return [
|
24
|
-
attr_accessor :
|
30
|
+
# @return [Proc] A callable that provides Database connections
|
31
|
+
attr_accessor :connection_provider
|
32
|
+
|
33
|
+
# @return [Boolean] Whether to require external connections (no fallback)
|
34
|
+
attr_accessor :connection_required
|
35
|
+
|
36
|
+
# Sets the default URI for Database connections.
|
37
|
+
#
|
38
|
+
# NOTE: uri is not a property of the Settings module b/c it's not
|
39
|
+
# configured in class defintions like default_expiration or logical DB index.
|
40
|
+
#
|
41
|
+
# @param v [String, URI] The new default URI
|
42
|
+
# @example
|
43
|
+
# Familia.uri = 'redis://localhost:6379'
|
44
|
+
def uri=(uri)
|
45
|
+
@uri = normalize_uri(uri)
|
46
|
+
end
|
47
|
+
alias url uri
|
48
|
+
alias url= uri=
|
25
49
|
|
26
|
-
# Establishes a connection to a
|
50
|
+
# Establishes a connection to a Database server.
|
27
51
|
#
|
28
|
-
# @param uri [String, URI, nil] The URI of the
|
29
|
-
# If nil, uses the default URI from `@
|
30
|
-
# @return [Redis] The connected
|
52
|
+
# @param uri [String, URI, nil] The URI of the Database server to connect to.
|
53
|
+
# If nil, uses the default URI from `@database_clients` or `Familia.uri`.
|
54
|
+
# @return [Redis] The connected Database client.
|
31
55
|
# @raise [ArgumentError] If no URI is specified.
|
32
56
|
# @example
|
33
57
|
# Familia.connect('redis://localhost:6379')
|
34
58
|
def connect(uri = nil)
|
35
|
-
|
36
|
-
|
37
|
-
uri ||= Familia.uri
|
38
|
-
|
39
|
-
raise ArgumentError, 'No URI specified' unless uri
|
59
|
+
parsed_uri = normalize_uri(uri)
|
60
|
+
serverid = parsed_uri.serverid
|
40
61
|
|
41
|
-
|
42
|
-
|
62
|
+
if Familia.enable_database_logging
|
63
|
+
DatabaseLogger.logger = Familia.logger
|
64
|
+
RedisClient.register(DatabaseLogger)
|
65
|
+
end
|
43
66
|
|
44
|
-
if Familia.
|
45
|
-
|
46
|
-
|
67
|
+
if Familia.enable_database_counter
|
68
|
+
# NOTE: This middleware uses AtommicFixnum from concurrent-ruby which is
|
69
|
+
# less contentious than Mutex-based counters. Safe for
|
70
|
+
RedisClient.register(DatabaseCommandCounter)
|
47
71
|
end
|
48
72
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
73
|
+
dbclient = Redis.new(parsed_uri.conf)
|
74
|
+
|
75
|
+
if @database_clients.key?(serverid)
|
76
|
+
msg = "Overriding existing connection for #{serverid}"
|
77
|
+
Familia.warn(msg)
|
53
78
|
end
|
54
79
|
|
55
|
-
|
80
|
+
@database_clients[serverid] = dbclient
|
81
|
+
end
|
82
|
+
|
83
|
+
def reconnect(uri = nil)
|
84
|
+
parsed_uri = normalize_uri(uri)
|
85
|
+
serverid = parsed_uri.serverid
|
56
86
|
|
57
87
|
# Close the existing connection if it exists
|
58
|
-
@
|
59
|
-
|
88
|
+
@database_clients[serverid].close if @database_clients.key?(serverid)
|
89
|
+
|
90
|
+
connect(parsed_uri)
|
60
91
|
end
|
61
92
|
|
62
|
-
# Retrieves
|
93
|
+
# Retrieves a Database connection from the appropriate pool.
|
94
|
+
# Handles DB selection automatically based on the URI.
|
63
95
|
#
|
64
|
-
# @
|
65
|
-
# If nil, uses the default URI.
|
66
|
-
# @return [Redis] The Redis client for the specified URI
|
96
|
+
# @return [Redis] The Database client for the specified URI
|
67
97
|
# @example
|
68
|
-
# Familia.
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
98
|
+
# Familia.dbclient('redis://localhost:6379/1')
|
99
|
+
# Familia.dbclient(2) # Use DB 2 with default server
|
100
|
+
def dbclient(uri = nil)
|
101
|
+
# First priority: Thread-local connection (middleware pattern)
|
102
|
+
return Thread.current[:familia_connection] if Thread.current.key?(:familia_connection)
|
103
|
+
|
104
|
+
# Second priority: Connection provider
|
105
|
+
if connection_provider
|
106
|
+
# Always pass normalized URI with database to provider
|
107
|
+
# Provider MUST return connection already on the correct database
|
108
|
+
parsed_uri = normalize_uri(uri)
|
109
|
+
connection = connection_provider.call(parsed_uri.to_s)
|
110
|
+
|
111
|
+
# In debug mode, verify the provider honored the contract
|
112
|
+
if Familia.debug? && connection.respond_to?(:client)
|
113
|
+
current_db = connection.client.db
|
114
|
+
expected_db = parsed_uri.db || 0
|
115
|
+
if current_db != expected_db
|
116
|
+
Familia.warn "Connection provider returned connection on DB #{current_db}, expected #{expected_db}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
return connection
|
121
|
+
end
|
122
|
+
|
123
|
+
# Third priority: Fallback behavior or error
|
124
|
+
raise Familia::NoConnectionAvailable, 'No connection available.' if connection_required
|
125
|
+
|
126
|
+
# Legacy behavior: create connection
|
127
|
+
parsed_uri = normalize_uri(uri)
|
128
|
+
|
129
|
+
# Only cache when no specific URI/DB is requested to avoid DB conflicts
|
130
|
+
if uri.nil?
|
131
|
+
@dbclient ||= connect(parsed_uri)
|
132
|
+
@dbclient.select(parsed_uri.db) if parsed_uri.db
|
133
|
+
@dbclient
|
134
|
+
else
|
135
|
+
# When a specific DB is requested, create a new connection
|
136
|
+
# to avoid conflicts with cached connections
|
137
|
+
connection = connect(parsed_uri)
|
138
|
+
connection.select(parsed_uri.db) if parsed_uri.db
|
139
|
+
connection
|
76
140
|
end
|
77
|
-
uri ||= Familia.uri
|
78
|
-
connect(uri) unless @redis_clients[uri.serverid]
|
79
|
-
@redis_clients[uri.serverid]
|
80
141
|
end
|
81
142
|
|
82
|
-
#
|
143
|
+
# Executes Database commands atomically within a transaction (MULTI/EXEC).
|
144
|
+
#
|
145
|
+
# Database transactions queue commands and execute them atomically as a single unit.
|
146
|
+
# All commands succeed together or all fail together, ensuring data consistency.
|
83
147
|
#
|
84
|
-
# @
|
85
|
-
# @return [
|
86
|
-
|
87
|
-
|
88
|
-
|
148
|
+
# @yield [Redis] The Database transaction connection
|
149
|
+
# @return [Array] Results of all commands executed in the transaction
|
150
|
+
#
|
151
|
+
# @example Basic transaction usage
|
152
|
+
# Familia.transaction do |trans|
|
153
|
+
# trans.set("key1", "value1")
|
154
|
+
# trans.incr("counter")
|
155
|
+
# trans.lpush("list", "item")
|
156
|
+
# end
|
157
|
+
# # Returns: ["OK", 2, 1] - results of all commands
|
158
|
+
#
|
159
|
+
# @note **Comparison of Database batch operations:**
|
160
|
+
#
|
161
|
+
# | Feature | Multi/Exec | Pipeline |
|
162
|
+
# |-----------------|-----------------|-----------------|
|
163
|
+
# | Atomicity | Yes | No |
|
164
|
+
# | Performance | Good | Better |
|
165
|
+
# | Error handling | All-or-nothing | Per-command |
|
166
|
+
# | Use case | Data consistency| Bulk operations |
|
167
|
+
#
|
168
|
+
def transaction(&)
|
169
|
+
block_result = nil
|
170
|
+
result = dbclient.multi do |conn|
|
171
|
+
Fiber[:familia_transaction] = conn
|
172
|
+
begin
|
173
|
+
block_result = yield(conn) # rubocop:disable Lint/UselessAssignment
|
174
|
+
ensure
|
175
|
+
Fiber[:familia_transaction] = nil # cleanup reference
|
176
|
+
end
|
177
|
+
end
|
178
|
+
# Return the multi result which contains the transaction results
|
179
|
+
result
|
89
180
|
end
|
181
|
+
alias multi transaction
|
90
182
|
|
91
|
-
#
|
183
|
+
# Executes Database commands in a pipeline for improved performance.
|
92
184
|
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
|
97
|
-
|
185
|
+
# Pipelines send multiple commands without waiting for individual responses,
|
186
|
+
# reducing network round-trips. Commands execute independently and can
|
187
|
+
# succeed or fail without affecting other commands in the pipeline.
|
188
|
+
#
|
189
|
+
# @yield [Redis] The Database pipeline connection
|
190
|
+
# @return [Array] Results of all commands executed in the pipeline
|
191
|
+
#
|
192
|
+
# @example Basic pipeline usage
|
193
|
+
# Familia.pipeline do |pipe|
|
194
|
+
# pipe.set("key1", "value1")
|
195
|
+
# pipe.incr("counter")
|
196
|
+
# pipe.lpush("list", "item")
|
197
|
+
# end
|
198
|
+
# # Returns: ["OK", 2, 1] - results of all commands
|
199
|
+
#
|
200
|
+
# @example Error handling - commands succeed/fail independently
|
201
|
+
# results = Familia.pipeline do |conn|
|
202
|
+
# conn.set("valid_key", "value") # This will succeed
|
203
|
+
# conn.incr("string_key") # This will fail (wrong type)
|
204
|
+
# conn.set("another_key", "value2") # This will still succeed
|
205
|
+
# end
|
206
|
+
# # Returns: ["OK", Redis::CommandError, "OK"]
|
207
|
+
# # Notice how the error doesn't prevent other commands from executing
|
208
|
+
#
|
209
|
+
# @example Contrast with transaction behavior
|
210
|
+
# results = Familia.transaction do |conn|
|
211
|
+
# conn.set("inventory:item1", 100)
|
212
|
+
# conn.incr("invalid_key") # Fails, rolls back everything
|
213
|
+
# conn.set("inventory:item2", 200) # Won't be applied
|
214
|
+
# end
|
215
|
+
# # Result: neither item1 nor item2 are set due to the error
|
216
|
+
#
|
217
|
+
def pipeline(&)
|
218
|
+
block_result = nil
|
219
|
+
result = dbclient.pipelined do |conn|
|
220
|
+
Fiber[:familia_pipeline] = conn
|
221
|
+
begin
|
222
|
+
block_result = yield(conn) # rubocop:disable Lint/UselessAssignment
|
223
|
+
ensure
|
224
|
+
Fiber[:familia_pipeline] = nil # cleanup reference
|
225
|
+
end
|
226
|
+
end
|
227
|
+
# Return the pipeline result which contains the command results
|
228
|
+
result
|
98
229
|
end
|
99
230
|
|
100
|
-
|
101
|
-
|
231
|
+
# Provides explicit access to a Database connection.
|
232
|
+
#
|
233
|
+
# This method is useful when you need direct access to a connection
|
234
|
+
# for operations not covered by other methods. The connection is
|
235
|
+
# properly managed and returned to the pool (if using connection_provider).
|
236
|
+
#
|
237
|
+
# @yield [Redis] A Database connection
|
238
|
+
# @return The result of the block
|
239
|
+
#
|
240
|
+
# @example Using with_connection for custom operations
|
241
|
+
# Familia.with_connection do |conn|
|
242
|
+
# conn.set("custom_key", "value")
|
243
|
+
# conn.expire("custom_key", 3600)
|
244
|
+
# end
|
245
|
+
#
|
246
|
+
def with_connection(&block)
|
247
|
+
yield dbclient
|
248
|
+
end
|
249
|
+
|
250
|
+
private
|
251
|
+
|
252
|
+
# Normalizes various URI formats to a consistent URI object
|
253
|
+
def normalize_uri(uri)
|
254
|
+
case uri
|
255
|
+
when Integer
|
256
|
+
new_uri = Familia.uri.dup
|
257
|
+
new_uri.db = uri
|
258
|
+
new_uri
|
259
|
+
when ->(obj) { obj.is_a?(String) || obj.instance_of?(::String) }
|
260
|
+
URI.parse(uri)
|
261
|
+
when URI
|
262
|
+
uri
|
263
|
+
when nil
|
264
|
+
Familia.uri
|
265
|
+
else
|
266
|
+
raise ArgumentError, "Invalid URI type: #{uri.class.name}"
|
267
|
+
end
|
268
|
+
end
|
102
269
|
end
|
103
270
|
end
|
data/lib/familia/core_ext.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
# lib/familia/datatype/commands.rb
|
2
|
+
|
3
|
+
class Familia::DataType
|
4
|
+
|
5
|
+
# Must be included in all DataType classes to provide Redis
|
6
|
+
# commands. The class must have a dbkey method.
|
7
|
+
module Commands
|
8
|
+
|
9
|
+
def move(logical_database)
|
10
|
+
dbclient.move dbkey, logical_database
|
11
|
+
end
|
12
|
+
|
13
|
+
def rename(newkey)
|
14
|
+
dbclient.rename dbkey, newkey
|
15
|
+
end
|
16
|
+
|
17
|
+
def renamenx(newkey)
|
18
|
+
dbclient.renamenx dbkey, newkey
|
19
|
+
end
|
20
|
+
|
21
|
+
def type
|
22
|
+
dbclient.type dbkey
|
23
|
+
end
|
24
|
+
|
25
|
+
# Deletes the entire dbkey
|
26
|
+
# @return [Boolean] true if the key was deleted, false otherwise
|
27
|
+
def delete!
|
28
|
+
Familia.trace :DELETE!, dbclient, uri, caller(1..1) if Familia.debug?
|
29
|
+
ret = dbclient.del dbkey
|
30
|
+
ret.positive?
|
31
|
+
end
|
32
|
+
alias clear delete!
|
33
|
+
|
34
|
+
def exists?
|
35
|
+
dbclient.exists(dbkey) && !size.zero?
|
36
|
+
end
|
37
|
+
|
38
|
+
def current_expiration
|
39
|
+
dbclient.ttl dbkey
|
40
|
+
end
|
41
|
+
|
42
|
+
def expire(sec)
|
43
|
+
dbclient.expire dbkey, sec.to_i
|
44
|
+
end
|
45
|
+
|
46
|
+
def expireat(unixtime)
|
47
|
+
dbclient.expireat dbkey, unixtime
|
48
|
+
end
|
49
|
+
|
50
|
+
def persist
|
51
|
+
dbclient.persist dbkey
|
52
|
+
end
|
53
|
+
|
54
|
+
def echo(meth, trace)
|
55
|
+
dbclient.echo "[#{self.class}\##{meth}] #{trace} (#{@opts[:class]}\#)"
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# lib/familia/datatype/serialization.rb
|
2
2
|
|
3
|
-
class Familia::
|
3
|
+
class Familia::DataType
|
4
4
|
|
5
5
|
module Serialization
|
6
6
|
|
@@ -29,7 +29,7 @@ class Familia::RedisType
|
|
29
29
|
def serialize_value(val, strict_values: true)
|
30
30
|
prepared = nil
|
31
31
|
|
32
|
-
Familia.trace :TOREDIS,
|
32
|
+
Familia.trace :TOREDIS, dbclient, "#{val}<#{val.class}|#{opts[:class]}>", caller(1..1) if Familia.debug?
|
33
33
|
|
34
34
|
if opts[:class]
|
35
35
|
prepared = Familia.distinguisher(opts[:class], strict_values: strict_values)
|
@@ -42,12 +42,11 @@ class Familia::RedisType
|
|
42
42
|
Familia.ld " from <#{val.class}> => <#{prepared.class}>"
|
43
43
|
end
|
44
44
|
|
45
|
-
Familia.trace :TOREDIS,
|
45
|
+
Familia.trace :TOREDIS, dbclient, "#{val}<#{val.class}|#{opts[:class]}> => #{prepared}<#{prepared.class}>", caller(1..1) if Familia.debug?
|
46
46
|
|
47
47
|
Familia.warn "[#{self.class}\#serialize_value] nil returned for #{opts[:class]}\##{name}" if prepared.nil?
|
48
48
|
prepared
|
49
49
|
end
|
50
|
-
alias to_redis serialize_value
|
51
50
|
|
52
51
|
# Deserializes multiple values from Redis, removing nil values.
|
53
52
|
#
|
@@ -63,7 +62,6 @@ class Familia::RedisType
|
|
63
62
|
# expected value.
|
64
63
|
deserialize_values_with_nil(*values).compact
|
65
64
|
end
|
66
|
-
alias from_redis deserialize_values
|
67
65
|
|
68
66
|
# Deserializes multiple values from Redis, preserving nil values.
|
69
67
|
#
|
@@ -97,16 +95,15 @@ class Familia::RedisType
|
|
97
95
|
val
|
98
96
|
rescue StandardError => e
|
99
97
|
Familia.info val
|
100
|
-
Familia.info "Parse error for #{
|
98
|
+
Familia.info "Parse error for #{dbkey} (#{load_method}): #{e.message}"
|
101
99
|
Familia.info e.backtrace
|
102
100
|
nil
|
103
101
|
end
|
104
102
|
|
105
103
|
values
|
106
104
|
end
|
107
|
-
alias from_redis_with_nil deserialize_values_with_nil
|
108
105
|
|
109
|
-
# Deserializes a single value from
|
106
|
+
# Deserializes a single value from the database.
|
110
107
|
#
|
111
108
|
# @param val [String, nil] The value to deserialize.
|
112
109
|
# @return [Object, nil] The deserialized object, the default value if
|
@@ -115,10 +112,10 @@ class Familia::RedisType
|
|
115
112
|
# @note If no class option is specified, the original value is
|
116
113
|
# returned unchanged.
|
117
114
|
#
|
118
|
-
# NOTE: Currently only the
|
115
|
+
# NOTE: Currently only the DataType class uses this method. Horreum
|
119
116
|
# fields are a newer addition and don't support the full range of
|
120
|
-
# deserialization options that
|
121
|
-
# for serialization since everything becomes a string in
|
117
|
+
# deserialization options that DataType supports. It uses serialize_value
|
118
|
+
# for serialization since everything becomes a string in Valkey.
|
122
119
|
#
|
123
120
|
def deserialize_value(val)
|
124
121
|
return @opts[:default] if val.nil?
|
@@ -127,7 +124,6 @@ class Familia::RedisType
|
|
127
124
|
ret = deserialize_values val
|
128
125
|
ret&.first # return the object or nil
|
129
126
|
end
|
130
|
-
alias from_redis deserialize_value
|
131
127
|
end
|
132
128
|
|
133
129
|
end
|