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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +68 -0
  3. data/.github/workflows/docs.yml +64 -0
  4. data/.gitignore +4 -0
  5. data/.pre-commit-config.yaml +3 -1
  6. data/.rubocop.yml +16 -9
  7. data/.rubocop_todo.yml +177 -31
  8. data/.yardopts +9 -0
  9. data/CLAUDE.md +141 -0
  10. data/Gemfile +16 -2
  11. data/Gemfile.lock +97 -36
  12. data/README.md +39 -23
  13. data/bin/irb +3 -0
  14. data/docs/connection_pooling.md +192 -0
  15. data/familia.gemspec +10 -6
  16. data/lib/familia/base.rb +19 -9
  17. data/lib/familia/connection.rb +232 -65
  18. data/lib/familia/core_ext.rb +1 -1
  19. data/lib/familia/datatype/commands.rb +59 -0
  20. data/lib/familia/{redistype → datatype}/serialization.rb +9 -13
  21. data/lib/familia/{redistype → datatype}/types/hashkey.rb +25 -25
  22. data/lib/familia/{redistype → datatype}/types/list.rb +13 -13
  23. data/lib/familia/{redistype → datatype}/types/sorted_set.rb +20 -20
  24. data/lib/familia/{redistype → datatype}/types/string.rb +22 -21
  25. data/lib/familia/{redistype → datatype}/types/unsorted_set.rb +11 -11
  26. data/lib/familia/datatype.rb +243 -0
  27. data/lib/familia/errors.rb +5 -2
  28. data/lib/familia/features/expiration.rb +33 -34
  29. data/lib/familia/features/quantization.rb +9 -3
  30. data/lib/familia/features/safe_dump.rb +2 -3
  31. data/lib/familia/features.rb +2 -2
  32. data/lib/familia/horreum/class_methods.rb +97 -110
  33. data/lib/familia/horreum/commands.rb +46 -51
  34. data/lib/familia/horreum/connection.rb +82 -0
  35. data/lib/familia/horreum/{relations_management.rb → related_fields_management.rb} +37 -35
  36. data/lib/familia/horreum/serialization.rb +61 -198
  37. data/lib/familia/horreum/settings.rb +6 -17
  38. data/lib/familia/horreum/utils.rb +11 -10
  39. data/lib/familia/horreum.rb +69 -60
  40. data/lib/familia/logging.rb +12 -12
  41. data/lib/familia/multi_result.rb +72 -0
  42. data/lib/familia/refinements.rb +7 -44
  43. data/lib/familia/settings.rb +11 -11
  44. data/lib/familia/utils.rb +123 -90
  45. data/lib/familia/version.rb +4 -21
  46. data/lib/familia.rb +18 -13
  47. data/lib/middleware/database_middleware.rb +150 -0
  48. data/try/configuration/scenarios_try.rb +65 -0
  49. data/try/core/connection_try.rb +58 -0
  50. data/try/core/errors_try.rb +93 -0
  51. data/try/core/extensions_try.rb +26 -0
  52. data/try/{10_familia_try.rb → core/familia_extended_try.rb} +11 -10
  53. data/try/{00_familia_try.rb → core/familia_try.rb} +7 -5
  54. data/try/core/middleware_try.rb +68 -0
  55. data/try/core/refinements_try.rb +39 -0
  56. data/try/core/settings_try.rb +76 -0
  57. data/try/core/tools_try.rb +54 -0
  58. data/try/core/utils_try.rb +189 -0
  59. data/try/{26_redis_bool_try.rb → datatypes/boolean_try.rb} +4 -2
  60. data/try/datatypes/datatype_base_try.rb +69 -0
  61. data/try/{25_redis_type_hash_try.rb → datatypes/hash_try.rb} +5 -3
  62. data/try/{23_redis_type_list_try.rb → datatypes/list_try.rb} +5 -3
  63. data/try/{22_redis_type_set_try.rb → datatypes/set_try.rb} +5 -3
  64. data/try/{21_redis_type_zset_try.rb → datatypes/sorted_set_try.rb} +6 -4
  65. data/try/{24_redis_type_string_try.rb → datatypes/string_try.rb} +8 -8
  66. data/try/edge_cases/empty_identifiers_try.rb +48 -0
  67. data/try/{92_symbolize_try.rb → edge_cases/hash_symbolization_try.rb} +12 -7
  68. data/try/edge_cases/json_serialization_try.rb +85 -0
  69. data/try/edge_cases/race_conditions_try.rb +60 -0
  70. data/try/edge_cases/reserved_keywords_try.rb +59 -0
  71. data/try/{93_string_coercion_try.rb → edge_cases/string_coercion_try.rb} +60 -59
  72. data/try/edge_cases/ttl_side_effects_try.rb +51 -0
  73. data/try/features/expiration_try.rb +86 -0
  74. data/try/features/quantization_try.rb +90 -0
  75. data/try/{35_feature_safedump_try.rb → features/safe_dump_advanced_try.rb} +7 -6
  76. data/try/features/safe_dump_try.rb +137 -0
  77. data/try/{test_helpers.rb → helpers/test_helpers.rb} +25 -60
  78. data/try/{27_redis_horreum_try.rb → horreum/base_try.rb} +39 -14
  79. data/try/horreum/class_methods_try.rb +41 -0
  80. data/try/horreum/commands_try.rb +49 -0
  81. data/try/{29_redis_horreum_initialization_try.rb → horreum/initialization_try.rb} +9 -7
  82. data/try/horreum/relations_try.rb +146 -0
  83. data/try/{28_redis_horreum_serialization_try.rb → horreum/serialization_try.rb} +13 -11
  84. data/try/horreum/settings_try.rb +43 -0
  85. data/try/integration/cross_component_try.rb +46 -0
  86. data/try/{41_customer_safedump_try.rb → models/customer_safe_dump_try.rb} +9 -7
  87. data/try/{40_customer_try.rb → models/customer_try.rb} +21 -18
  88. data/try/models/datatype_base_try.rb +100 -0
  89. data/try/{30_familia_object_try.rb → models/familia_object_try.rb} +18 -16
  90. data/try/performance/benchmarks_try.rb +55 -0
  91. data/try/pooling/README.md +20 -0
  92. data/try/pooling/configurable_stress_test_try.rb +435 -0
  93. data/try/pooling/connection_pool_test_try.rb +273 -0
  94. data/try/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
  95. data/try/pooling/lib/connection_pool_metrics.rb +372 -0
  96. data/try/pooling/lib/connection_pool_stress_test.rb +959 -0
  97. data/try/pooling/lib/connection_pool_threading_models.rb +421 -0
  98. data/try/pooling/lib/visualize_stress_results.rb +434 -0
  99. data/try/pooling/pool_siege_try.rb +509 -0
  100. data/try/pooling/run_stress_tests_try.rb +482 -0
  101. data/try/prototypes/atomic_saves_v1_context_proxy.rb +121 -0
  102. data/try/prototypes/atomic_saves_v2_connection_switching.rb +161 -0
  103. data/try/prototypes/atomic_saves_v3_connection_pool.rb +189 -0
  104. data/try/prototypes/atomic_saves_v4.rb +105 -0
  105. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +124 -0
  106. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
  107. metadata +143 -46
  108. data/.github/workflows/ruby.yml +0 -71
  109. data/VERSION.yml +0 -4
  110. data/lib/familia/redistype/commands.rb +0 -59
  111. data/lib/familia/redistype.rb +0 -228
  112. data/lib/familia/tools.rb +0 -68
  113. data/lib/redis_middleware.rb +0 -109
  114. data/try/20_redis_type_try.rb +0 -70
  115. data/try/91_json_bug_try.rb +0 -86
@@ -1,103 +1,270 @@
1
- # frozen_string_literal: true
1
+ # lib/familia/connection.rb
2
2
 
3
- require_relative '../../lib/redis_middleware'
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
- @redis_clients = {}
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 Redis connection management for Familia.
12
- # It allows easy setup and access to Redis clients across different URIs.
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 [Hash] A hash of Redis clients, keyed by server ID
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 [Boolean] Whether Redis command logging is enabled
21
- attr_accessor :enable_redis_logging
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 [Boolean] Whether Redis command counter is enabled
24
- attr_accessor :enable_redis_counter
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 Redis server.
50
+ # Establishes a connection to a Database server.
27
51
  #
28
- # @param uri [String, URI, nil] The URI of the Redis server to connect to.
29
- # If nil, uses the default URI from `@redis_uri_by_class` or `Familia.uri`.
30
- # @return [Redis] The connected Redis client.
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
- uri = URI.parse(uri) if uri.is_a?(String)
36
- uri ||= @redis_uri_by_class[self]
37
- uri ||= Familia.uri
59
+ parsed_uri = normalize_uri(uri)
60
+ serverid = parsed_uri.serverid
38
61
 
39
- raise ArgumentError, 'No URI specified' unless uri
40
-
41
- conf = uri.conf
42
- @redis_uri_by_class[self] = uri.serverid
62
+ if Familia.enable_database_logging
63
+ DatabaseLogger.logger = Familia.logger
64
+ RedisClient.register(DatabaseLogger)
65
+ end
43
66
 
44
- if Familia.enable_redis_logging
45
- RedisLogger.logger = Familia.logger
46
- RedisClient.register(RedisLogger)
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
- if Familia.enable_redis_counter
50
- # NOTE: This middleware stays thread-safe with a mutex so it will
51
- # be a bottleneck when enabled in multi-threaded environments.
52
- RedisClient.register(RedisCommandCounter)
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
- redis = Redis.new(conf)
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
- @redis_clients[uri.serverid].close if @redis_clients[uri.serverid]
59
- @redis_clients[uri.serverid] = redis
88
+ @database_clients[serverid].close if @database_clients.key?(serverid)
89
+
90
+ connect(parsed_uri)
60
91
  end
61
92
 
62
- # Retrieves or creates a Redis client for the given URI.
93
+ # Retrieves a Database connection from the appropriate pool.
94
+ # Handles DB selection automatically based on the URI.
63
95
  #
64
- # @param uri [String, URI, nil] The URI of the Redis server.
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.redis('redis://localhost:6379')
69
- def redis(uri = nil)
70
- if uri.is_a?(Integer)
71
- tmp = Familia.uri
72
- tmp.db = uri
73
- uri = tmp
74
- elsif uri.is_a?(String)
75
- uri &&= URI.parse uri
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
- # Retrieves the Redis client associated with the given class.
143
+ # Executes Database commands atomically within a transaction (MULTI/EXEC).
83
144
  #
84
- # @param klass [Class] The class for which to retrieve the Redis client.
85
- # @return [Redis] The Redis client associated with the given class.
86
- def redis_uri_by_class(klass)
87
- uri = @redis_uri_by_class[klass]
88
- connect(uri)
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.
147
+ #
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
- # Sets the default URI for Redis connections.
183
+ # Executes Database commands in a pipeline for improved performance.
92
184
  #
93
- # @param v [String, URI] The new default URI
94
- # @example
95
- # Familia.uri = 'redis://localhost:6379'
96
- def uri=(val)
97
- @uri = val.is_a?(URI) ? v : URI.parse(val)
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
- alias url uri
101
- alias url= uri=
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
@@ -1,4 +1,4 @@
1
- # frozen_string_literal: true
1
+ # lib/familia/core_ext.rb
2
2
 
3
3
  # Extends the String class with time-related functionality
4
4
  #
@@ -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
- # rubocop:disable all
1
+ # lib/familia/datatype/serialization.rb
2
2
 
3
- class Familia::RedisType
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, redis, "#{val}<#{val.class}|#{opts[:class]}>", caller(1..1) if Familia.debug?
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, redis, "#{val}<#{val.class}|#{opts[:class]}> => #{prepared}<#{prepared.class}>", caller(1..1) if Familia.debug?
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 #{rediskey} (#{load_method}): #{e.message}"
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 Redis.
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 RedisType class uses this method. Horreum
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 RedisType supports. It uses to_redis
121
- # for serialization since everything becomes a string in Redis.
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
@@ -1,11 +1,11 @@
1
- # frozen_string_literal: true
1
+ # lib/familia/datatype/types/hashkey.rb
2
2
 
3
3
  module Familia
4
- class HashKey < RedisType
4
+ class HashKey < DataType
5
5
  # Returns the number of fields in the hash
6
6
  # @return [Integer] number of fields
7
7
  def field_count
8
- redis.hlen rediskey
8
+ dbclient.hlen dbkey
9
9
  end
10
10
  alias size field_count
11
11
 
@@ -16,22 +16,22 @@ module Familia
16
16
  # +return+ [Integer] Returns 1 if the field is new and added, 0 if the
17
17
  # field already existed and the value was updated.
18
18
  def []=(field, val)
19
- ret = redis.hset rediskey, field.to_s, serialize_value(val)
19
+ ret = dbclient.hset dbkey, field.to_s, serialize_value(val)
20
20
  update_expiration
21
21
  ret
22
22
  rescue TypeError => e
23
23
  Familia.le "[hset]= #{e.message}"
24
- Familia.ld "[hset]= #{rediskey} #{field}=#{val}" if Familia.debug
25
- echo :hset, caller(1..1).first if Familia.debug # logs via echo to redis and back
24
+ Familia.ld "[hset]= #{dbkey} #{field}=#{val}" if Familia.debug
25
+ echo :hset, caller(1..1).first if Familia.debug # logs via echo to the db and back
26
26
  klass = val.class
27
- msg = "Cannot store #{field} => #{val.inspect} (#{klass}) in #{rediskey}"
27
+ msg = "Cannot store #{field} => #{val.inspect} (#{klass}) in #{dbkey}"
28
28
  raise e.class, msg
29
29
  end
30
30
  alias put []=
31
31
  alias store []=
32
32
 
33
33
  def [](field)
34
- deserialize_value redis.hget(rediskey, field.to_s)
34
+ deserialize_value dbclient.hget(dbkey, field.to_s)
35
35
  end
36
36
  alias get []
37
37
 
@@ -47,22 +47,22 @@ module Familia
47
47
  end
48
48
 
49
49
  def keys
50
- redis.hkeys rediskey
50
+ dbclient.hkeys dbkey
51
51
  end
52
52
 
53
53
  def values
54
- redis.hvals(rediskey).map { |v| deserialize_value v }
54
+ dbclient.hvals(dbkey).map { |v| deserialize_value v }
55
55
  end
56
56
 
57
57
  def hgetall
58
- redis.hgetall(rediskey).each_with_object({}) do |(k,v), ret|
58
+ dbclient.hgetall(dbkey).each_with_object({}) do |(k,v), ret|
59
59
  ret[k] = deserialize_value v
60
60
  end
61
61
  end
62
62
  alias all hgetall
63
63
 
64
64
  def key?(field)
65
- redis.hexists rediskey, field.to_s
65
+ dbclient.hexists dbkey, field.to_s
66
66
  end
67
67
  alias has_key? key?
68
68
  alias include? key?
@@ -72,12 +72,12 @@ module Familia
72
72
  # @param field [String] The field to remove
73
73
  # @return [Integer] The number of fields that were removed (0 or 1)
74
74
  def remove_field(field)
75
- redis.hdel rediskey, field.to_s
75
+ dbclient.hdel dbkey, field.to_s
76
76
  end
77
77
  alias remove remove_field # deprecated
78
78
 
79
79
  def increment(field, by = 1)
80
- redis.hincrby(rediskey, field.to_s, by).to_i
80
+ dbclient.hincrby(dbkey, field.to_s, by).to_i
81
81
  end
82
82
  alias incr increment
83
83
  alias incrby increment
@@ -93,7 +93,7 @@ module Familia
93
93
 
94
94
  data = hsh.inject([]) { |ret, pair| ret << [pair[0], serialize_value(pair[1])] }.flatten
95
95
 
96
- ret = redis.hmset(rediskey, *data)
96
+ ret = dbclient.hmset(dbkey, *data)
97
97
  update_expiration
98
98
  ret
99
99
  end
@@ -101,11 +101,11 @@ module Familia
101
101
 
102
102
  def values_at *fields
103
103
  string_fields = fields.flatten.compact.map(&:to_s)
104
- elements = redis.hmget(rediskey, *string_fields)
104
+ elements = dbclient.hmget(dbkey, *string_fields)
105
105
  deserialize_values(*elements)
106
106
  end
107
107
 
108
- # The Great Redis Refresh-o-matic 3000 for HashKey!
108
+ # The Great Database Refresh-o-matic 3000 for HashKey!
109
109
  #
110
110
  # This method performs a complete refresh of the hash's state from Redis.
111
111
  # It's like giving your hash a memory transfusion - out with the old state,
@@ -117,7 +117,7 @@ module Familia
117
117
  # @return [void] Returns nothing, but your hash will be sparkling clean
118
118
  # with all its fields synchronized with Redis.
119
119
  #
120
- # @raise [Familia::KeyNotFoundError] If the Redis key for this hash no
120
+ # @raise [Familia::KeyNotFoundError] If the dbkey for this hash no
121
121
  # longer exists. Time travelers beware!
122
122
  #
123
123
  # @example Basic usage
@@ -127,14 +127,14 @@ module Familia
127
127
  # begin
128
128
  # my_hash.refresh!
129
129
  # rescue Familia::KeyNotFoundError
130
- # puts "Oops! Our hash seems to have vanished into the Redis void!"
130
+ # puts "Oops! Our hash seems to have vanished into the Database void!"
131
131
  # end
132
132
  def refresh!
133
- Familia.trace :REFRESH, redis, redisuri, caller(1..1) if Familia.debug?
134
- raise Familia::KeyNotFoundError, rediskey unless redis.exists(rediskey)
133
+ Familia.trace :REFRESH, dbclient, uri, caller(1..1) if Familia.debug?
134
+ raise Familia::KeyNotFoundError, dbkey unless dbclient.exists(dbkey)
135
135
 
136
136
  fields = hgetall
137
- Familia.ld "[refresh!] #{self.class} #{rediskey} #{fields.keys}"
137
+ Familia.ld "[refresh!] #{self.class} #{dbkey} #{fields.keys}"
138
138
 
139
139
  # For HashKey, we update by merging the fresh data
140
140
  update(fields)
@@ -148,7 +148,7 @@ module Familia
148
148
  #
149
149
  # @return [self] Returns the refreshed hash, ready for more adventures!
150
150
  #
151
- # @raise [Familia::KeyNotFoundError] If the Redis key does not exist.
151
+ # @raise [Familia::KeyNotFoundError] If the dbkey does not exist.
152
152
  # The hash must exist in Redis-land for this to work!
153
153
  #
154
154
  # @example Refresh and chain
@@ -161,7 +161,7 @@ module Familia
161
161
  self
162
162
  end
163
163
 
164
- Familia::RedisType.register self, :hash # legacy, deprecated
165
- Familia::RedisType.register self, :hashkey
164
+ Familia::DataType.register self, :hash # legacy, deprecated
165
+ Familia::DataType.register self, :hashkey
166
166
  end
167
167
  end