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
@@ -0,0 +1,273 @@
|
|
1
|
+
# try/pooling/connection_pool_test_try.rb
|
2
|
+
|
3
|
+
# USAGE: FAMILIA_TRACE=1 FAMILIA_DEBUG=1 bundle exec tryouts try/pooling/connection_pool_test_try.rb
|
4
|
+
|
5
|
+
require 'bundler/setup'
|
6
|
+
require 'securerandom'
|
7
|
+
require 'thread'
|
8
|
+
require_relative '../helpers/test_helpers'
|
9
|
+
|
10
|
+
# Configure connection pooling via connection_provider
|
11
|
+
require 'connection_pool'
|
12
|
+
|
13
|
+
# Create pools for each logical database
|
14
|
+
@pools = {}
|
15
|
+
|
16
|
+
Familia.connection_provider = lambda do |uri|
|
17
|
+
@pools[uri] ||= ConnectionPool.new(size: 5, timeout: 2) do
|
18
|
+
parsed = URI.parse(uri)
|
19
|
+
Redis.new(
|
20
|
+
host: parsed.host,
|
21
|
+
port: parsed.port,
|
22
|
+
db: parsed.db || 0
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
@pools[uri].with { |conn| conn }
|
27
|
+
end
|
28
|
+
|
29
|
+
# Test model for connection pool testing
|
30
|
+
class PoolTestAccount < Familia::Horreum
|
31
|
+
identifier_field :account_id
|
32
|
+
field :account_id
|
33
|
+
field :balance
|
34
|
+
field :holder_name
|
35
|
+
|
36
|
+
def init
|
37
|
+
@account_id ||= SecureRandom.hex(6)
|
38
|
+
@balance = @balance.to_f if @balance
|
39
|
+
end
|
40
|
+
|
41
|
+
def balance
|
42
|
+
@balance&.to_f
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class PoolTestSession < Familia::Horreum
|
47
|
+
identifier_field :session_id
|
48
|
+
field :session_id
|
49
|
+
field :user_id
|
50
|
+
field :created_at
|
51
|
+
|
52
|
+
def init
|
53
|
+
@session_id ||= SecureRandom.hex(8)
|
54
|
+
@created_at ||= Time.now.to_i
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
## Clean up before tests
|
59
|
+
PoolTestAccount.dbclient.flushdb
|
60
|
+
#=> "OK"
|
61
|
+
|
62
|
+
## Test 1: Connection provider configuration
|
63
|
+
Familia.connection_provider.is_a?(Proc)
|
64
|
+
#=> true
|
65
|
+
|
66
|
+
## Test 2: Connection pool created automatically
|
67
|
+
@account1 = PoolTestAccount.new(balance: 1000, holder_name: "Alice")
|
68
|
+
@account1.save
|
69
|
+
#=> true
|
70
|
+
|
71
|
+
## Test 3: Basic pool functionality
|
72
|
+
@account1.balance
|
73
|
+
#=> 1000.0
|
74
|
+
|
75
|
+
## Test 4: Multiple logical databases with separate pools
|
76
|
+
# Account in DB 0 (default)
|
77
|
+
@account_db0 = PoolTestAccount.new(balance: 500, holder_name: "Bob")
|
78
|
+
@account_db0.save
|
79
|
+
#=> true
|
80
|
+
|
81
|
+
## Test 5: Account in DB 1 via class configuration
|
82
|
+
class PoolTestAccountDB1 < Familia::Horreum
|
83
|
+
self.logical_database = 1
|
84
|
+
identifier_field :account_id
|
85
|
+
field :account_id
|
86
|
+
field :balance
|
87
|
+
field :holder_name
|
88
|
+
|
89
|
+
def init
|
90
|
+
@account_id ||= SecureRandom.hex(6)
|
91
|
+
@balance = @balance.to_f if @balance
|
92
|
+
end
|
93
|
+
|
94
|
+
def balance
|
95
|
+
@balance&.to_f
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
@account_db1 = PoolTestAccountDB1.new(balance: 750, holder_name: "Charlie")
|
100
|
+
@account_db1.save
|
101
|
+
#=> true
|
102
|
+
|
103
|
+
## Test 6: Verify accounts are in different databases
|
104
|
+
@account_db0.balance
|
105
|
+
#=> 500.0
|
106
|
+
|
107
|
+
## Test 7: Verify DB1 account works independently
|
108
|
+
@account_db1.balance
|
109
|
+
#=> 750.0
|
110
|
+
|
111
|
+
## Test 8: Connection pool thread safety
|
112
|
+
@results = []
|
113
|
+
@mutex = Mutex.new
|
114
|
+
|
115
|
+
# Create multiple threads performing concurrent operations
|
116
|
+
threads = 5.times.map do |i|
|
117
|
+
Thread.new do
|
118
|
+
account = PoolTestAccount.new(balance: 1000, holder_name: "Thread#{i}")
|
119
|
+
result = account.save
|
120
|
+
@mutex.synchronize { @results << result }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
threads.each(&:join)
|
125
|
+
@results.all?
|
126
|
+
#=> true
|
127
|
+
|
128
|
+
## Test 9: Thread safety verification
|
129
|
+
@results.size
|
130
|
+
#=> 5
|
131
|
+
|
132
|
+
## Test 10: Transaction support with connection pools
|
133
|
+
@account_a = PoolTestAccount.new(balance: 1000, holder_name: "AccountA")
|
134
|
+
@account_b = PoolTestAccount.new(balance: 500, holder_name: "AccountB")
|
135
|
+
[@account_a.save, @account_b.save]
|
136
|
+
#=> [true, true]
|
137
|
+
|
138
|
+
## Test 11: Multi/EXEC transaction operations
|
139
|
+
@transfer_result = Familia.transaction do |conn|
|
140
|
+
# Test that transaction connection is available
|
141
|
+
conn.ping
|
142
|
+
end
|
143
|
+
# Transaction returns array with results
|
144
|
+
@transfer_result.first
|
145
|
+
#=> "PONG"
|
146
|
+
|
147
|
+
## Test 12: Transaction block executes properly
|
148
|
+
# Simple verification that accounts maintain their values
|
149
|
+
[@account_a.balance, @account_b.balance]
|
150
|
+
#=> [1000.0, 500.0]
|
151
|
+
|
152
|
+
## Test 13: with_connection method
|
153
|
+
@connection_test_result = Familia.with_connection do |conn|
|
154
|
+
conn.set("test_key_#{SecureRandom.hex(4)}", "test_value")
|
155
|
+
end
|
156
|
+
@connection_test_result
|
157
|
+
#=> "OK"
|
158
|
+
|
159
|
+
## Test 14: Pipeline operations with connection pool
|
160
|
+
@pipeline_results = Familia.pipeline do |conn|
|
161
|
+
conn.ping
|
162
|
+
end
|
163
|
+
# Pipeline executes successfully
|
164
|
+
@pipeline_results.first
|
165
|
+
#=> "PONG"
|
166
|
+
|
167
|
+
## Test 15: Multi/EXEC operations with connection pool
|
168
|
+
@multi_results = Familia.multi do |conn|
|
169
|
+
conn.ping
|
170
|
+
end
|
171
|
+
# Multi/EXEC executes successfully
|
172
|
+
@multi_results.first
|
173
|
+
#=> "PONG"
|
174
|
+
|
175
|
+
## Test 16: Error handling in transactions
|
176
|
+
@error_account = PoolTestAccount.new(balance: 100, holder_name: "ErrorTest")
|
177
|
+
@error_account.save
|
178
|
+
#=> true
|
179
|
+
|
180
|
+
## Test 17: Transaction error handling
|
181
|
+
begin
|
182
|
+
Familia.transaction do |conn|
|
183
|
+
conn.ping
|
184
|
+
raise "Simulated error"
|
185
|
+
end
|
186
|
+
false
|
187
|
+
rescue => e
|
188
|
+
# Error propagates correctly from transaction block
|
189
|
+
true
|
190
|
+
end
|
191
|
+
#=> true
|
192
|
+
|
193
|
+
## Test 18: Verify account state after transaction error
|
194
|
+
@error_account.refresh!
|
195
|
+
@error_account.balance
|
196
|
+
#=> 100.0
|
197
|
+
|
198
|
+
## Test 19: Multiple pools created for different databases
|
199
|
+
@pools.size >= 1
|
200
|
+
#=> true
|
201
|
+
|
202
|
+
## Test 20: Connection pool timeout handling
|
203
|
+
timeout_threads = []
|
204
|
+
timeout_results = []
|
205
|
+
timeout_mutex = Mutex.new
|
206
|
+
|
207
|
+
# Start threads that hold connections briefly
|
208
|
+
3.times do |i|
|
209
|
+
timeout_threads << Thread.new do
|
210
|
+
begin
|
211
|
+
result = Familia.with_connection do |conn|
|
212
|
+
sleep(0.1) # Brief hold
|
213
|
+
conn.ping
|
214
|
+
end
|
215
|
+
timeout_mutex.synchronize { timeout_results << result }
|
216
|
+
rescue => e
|
217
|
+
timeout_mutex.synchronize { timeout_results << e.class.name }
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
timeout_threads.each(&:join)
|
223
|
+
timeout_results.all? { |r| r == "PONG" }
|
224
|
+
#=> true
|
225
|
+
|
226
|
+
## Test 21: Debug mode validation (if enabled)
|
227
|
+
# This test only runs if FAMILIA_DEBUG=1 is set
|
228
|
+
if ENV['FAMILIA_DEBUG']
|
229
|
+
Familia.debug = true
|
230
|
+
# Test that debug mode doesn't break normal operation
|
231
|
+
debug_account = PoolTestAccount.new(balance: 123, holder_name: "Debug")
|
232
|
+
debug_account.save
|
233
|
+
else
|
234
|
+
true # Skip debug test if not in debug mode
|
235
|
+
end
|
236
|
+
#=> true
|
237
|
+
|
238
|
+
## Test 22: Backward compatibility - existing code works unchanged
|
239
|
+
@compat_result = PoolTestAccount.dbclient.ping
|
240
|
+
@compat_result
|
241
|
+
#=> "PONG"
|
242
|
+
|
243
|
+
## Test 23: Field operations work unchanged
|
244
|
+
@compat_account = PoolTestAccount.new(balance: 9999, holder_name: "Compat")
|
245
|
+
@compat_account.save
|
246
|
+
#=> true
|
247
|
+
|
248
|
+
## Test 24: Direct field access works
|
249
|
+
@compat_account.hget("balance").to_f
|
250
|
+
#=> 9999.0
|
251
|
+
|
252
|
+
## Test 25: Connection provider receives correct URIs
|
253
|
+
@captured_uris = []
|
254
|
+
original_provider = Familia.connection_provider
|
255
|
+
|
256
|
+
# Temporarily wrap provider to capture URIs
|
257
|
+
Familia.connection_provider = lambda do |uri|
|
258
|
+
@captured_uris << uri
|
259
|
+
original_provider.call(uri)
|
260
|
+
end
|
261
|
+
|
262
|
+
# Trigger some operations to capture URIs
|
263
|
+
test_account = PoolTestAccount.new(balance: 555, holder_name: "URITest")
|
264
|
+
test_account.save
|
265
|
+
|
266
|
+
# Restore original provider
|
267
|
+
Familia.connection_provider = original_provider
|
268
|
+
|
269
|
+
# Verify URIs contain database information
|
270
|
+
@captured_uris.any? { |uri| uri.include?('redis://') }
|
271
|
+
#=> true
|
272
|
+
|
273
|
+
puts "Connection pool tests completed successfully!"
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# try/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb
|
2
|
+
|
3
|
+
##
|
4
|
+
# Atomic Save V3 Proof of Concept - Connection Pool Integration
|
5
|
+
#
|
6
|
+
# This implementation explores atomic saves with Database connection pooling
|
7
|
+
# for thread safety in multi-threaded environments (like Puma).
|
8
|
+
#
|
9
|
+
# Key Goals:
|
10
|
+
# 1. **Connection Pool Integration**: Use ConnectionPool gem for thread safety
|
11
|
+
# 2. **Dual Approach Testing**: Compare proxy vs explicit connection passing
|
12
|
+
# 3. **Thread Safety Validation**: Prove pool handles concurrent operations
|
13
|
+
# 4. **Separate Transaction Boundaries**: Each atomic block gets own transaction
|
14
|
+
#
|
15
|
+
# Approaches Tested:
|
16
|
+
# - Proxy Approach: Familia.atomic { ... } (transparent, V2 style)
|
17
|
+
# - Explicit Approach: Familia.atomic { |conn| ... } (clear boundaries)
|
18
|
+
|
19
|
+
require 'connection_pool'
|
20
|
+
require 'json'
|
21
|
+
|
22
|
+
# Test models
|
23
|
+
class BankAccount < Familia::Horreum
|
24
|
+
identifier_field :account_number
|
25
|
+
field :account_number
|
26
|
+
field :balance
|
27
|
+
field :holder_name
|
28
|
+
field :metadata # Variable-sized JSON field for workload simulation
|
29
|
+
|
30
|
+
def init
|
31
|
+
@account_number ||= SecureRandom.hex(8)
|
32
|
+
@balance = @balance.to_f if @balance
|
33
|
+
@metadata = @metadata.is_a?(String) ? JSON.parse(@metadata) : @metadata rescue @metadata
|
34
|
+
end
|
35
|
+
|
36
|
+
def balance
|
37
|
+
@balance&.to_f
|
38
|
+
end
|
39
|
+
|
40
|
+
def withdraw(amount)
|
41
|
+
raise "Insufficient funds" if balance < amount
|
42
|
+
self.balance -= amount
|
43
|
+
end
|
44
|
+
|
45
|
+
def deposit(amount)
|
46
|
+
self.balance += amount
|
47
|
+
end
|
48
|
+
|
49
|
+
def metadata=(value)
|
50
|
+
@metadata = value.is_a?(Hash) || value.is_a?(Array) ? JSON.generate(value) : value
|
51
|
+
end
|
52
|
+
|
53
|
+
# Add method that accepts explicit connection
|
54
|
+
def save(using: nil)
|
55
|
+
if using
|
56
|
+
# Use provided connection explicitly
|
57
|
+
original_instance = @dbclient
|
58
|
+
@dbclient = using
|
59
|
+
begin
|
60
|
+
super()
|
61
|
+
ensure
|
62
|
+
@dbclient = original_instance
|
63
|
+
end
|
64
|
+
else
|
65
|
+
# Use normal save behavior
|
66
|
+
super()
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class TransactionRecord < Familia::Horreum
|
72
|
+
identifier_field :transaction_id
|
73
|
+
field :transaction_id
|
74
|
+
field :from_account
|
75
|
+
field :to_account
|
76
|
+
field :amount
|
77
|
+
field :status
|
78
|
+
field :created_at
|
79
|
+
|
80
|
+
def initialize(from: nil, to: nil, amount: 0)
|
81
|
+
@transaction_id = SecureRandom.hex(8)
|
82
|
+
@from_account = from
|
83
|
+
@to_account = to
|
84
|
+
@amount = amount.to_f
|
85
|
+
@status = "pending"
|
86
|
+
@created_at = Time.now.to_i
|
87
|
+
end
|
88
|
+
|
89
|
+
def amount
|
90
|
+
@amount&.to_f
|
91
|
+
end
|
92
|
+
|
93
|
+
def created_at
|
94
|
+
@created_at&.to_i
|
95
|
+
end
|
96
|
+
|
97
|
+
def save(using: nil)
|
98
|
+
if using
|
99
|
+
original_instance = @dbclient
|
100
|
+
@dbclient = using
|
101
|
+
begin
|
102
|
+
super()
|
103
|
+
ensure
|
104
|
+
@dbclient = original_instance
|
105
|
+
end
|
106
|
+
else
|
107
|
+
super()
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
module Familia
|
113
|
+
# Connection pool for Database connections
|
114
|
+
@@connection_pool = ConnectionPool.new(size: 10, timeout: 5) do
|
115
|
+
Redis.new(url: Familia.uri.to_s)
|
116
|
+
end
|
117
|
+
|
118
|
+
class << self
|
119
|
+
def connection_pool
|
120
|
+
@@connection_pool
|
121
|
+
end
|
122
|
+
|
123
|
+
def current_transaction
|
124
|
+
Thread.current[:familia_current_transaction_v3]
|
125
|
+
end
|
126
|
+
|
127
|
+
def current_transaction=(transaction)
|
128
|
+
Thread.current[:familia_current_transaction_v3] = transaction
|
129
|
+
end
|
130
|
+
|
131
|
+
# Proxy approach - transparent like V2
|
132
|
+
def atomic(&block)
|
133
|
+
if current_transaction
|
134
|
+
# Nested atomic - create separate transaction
|
135
|
+
atomic_separate(&block)
|
136
|
+
else
|
137
|
+
# Use connection pool to get connection
|
138
|
+
# For this prototype, we'll use a simple approach that works with Redis
|
139
|
+
connection_pool.with do |conn|
|
140
|
+
begin
|
141
|
+
# Store the connection for use within the block
|
142
|
+
self.current_transaction = conn
|
143
|
+
result = yield
|
144
|
+
result
|
145
|
+
ensure
|
146
|
+
self.current_transaction = nil
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Explicit approach - connection passed to block
|
153
|
+
def atomic_explicit(&block)
|
154
|
+
connection_pool.with do |conn|
|
155
|
+
# For this prototype, pass the connection directly
|
156
|
+
yield(conn)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Helper for separate nested transactions
|
161
|
+
def atomic_separate(&block)
|
162
|
+
connection_pool.with do |conn|
|
163
|
+
begin
|
164
|
+
old_transaction = current_transaction
|
165
|
+
# Use a separate connection for nested transactions
|
166
|
+
self.current_transaction = conn
|
167
|
+
result = yield
|
168
|
+
result
|
169
|
+
ensure
|
170
|
+
self.current_transaction = old_transaction
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Override dbclient method for proxy approach
|
177
|
+
module ConnectionPoolRedis
|
178
|
+
def dbclient
|
179
|
+
Familia.current_transaction || super
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Inject into Horreum for proxy approach
|
184
|
+
class Horreum
|
185
|
+
prepend ConnectionPoolRedis
|
186
|
+
end
|
187
|
+
|
188
|
+
# Inject into DataType for proxy approach
|
189
|
+
class DataType
|
190
|
+
prepend ConnectionPoolRedis
|
191
|
+
end
|
192
|
+
end
|