familia 2.0.0.pre14 → 2.0.0.pre16
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/code-quality.yml +138 -0
- data/.github/workflows/code-smellage.yml +145 -0
- data/.github/workflows/docs.yml +31 -8
- data/.gitignore +1 -1
- data/.pre-commit-config.yaml +7 -1
- data/.reek.yml +98 -0
- data/.rubocop.yml +48 -10
- data/.talismanrc +9 -0
- data/.yardopts +18 -13
- data/CHANGELOG.rst +66 -6
- data/CLAUDE.md +1 -1
- data/Gemfile +6 -5
- data/Gemfile.lock +99 -23
- data/LICENSE.txt +1 -1
- data/README.md +285 -85
- data/changelog.d/README.md +2 -2
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
- data/docs/archive/FAMILIA_TECHNICAL.md +41 -41
- data/docs/archive/FAMILIA_UPDATE.md +3 -3
- data/docs/archive/README.md +3 -2
- data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
- data/docs/conf.py +29 -0
- data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
- data/docs/guides/feature-encrypted-fields.md +785 -0
- data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
- data/docs/guides/feature-external-identifiers.md +637 -0
- data/docs/guides/feature-object-identifiers.md +435 -0
- data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
- data/docs/guides/feature-relationships-methods.md +684 -0
- data/docs/guides/feature-relationships.md +200 -0
- data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
- data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
- data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
- data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
- data/docs/guides/index.md +176 -0
- data/docs/guides/{Security-Model.md → security-model.md} +1 -1
- data/docs/migrating/v2.0.0-pre.md +1 -1
- data/docs/migrating/v2.0.0-pre11.md +4 -4
- data/docs/migrating/v2.0.0-pre12.md +2 -2
- data/docs/migrating/v2.0.0-pre13.md +1 -1
- data/docs/migrating/v2.0.0-pre5.md +33 -12
- data/docs/migrating/v2.0.0-pre6.md +2 -2
- data/docs/migrating/v2.0.0-pre7.md +8 -8
- data/docs/overview.md +623 -19
- data/docs/reference/api-technical.md +1365 -0
- data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
- data/examples/autoloader/mega_customer.rb +3 -1
- data/examples/encrypted_fields.rb +378 -0
- data/examples/json_usage_patterns.rb +144 -0
- data/examples/relationships.rb +13 -13
- data/examples/safe_dump.rb +6 -6
- data/examples/single_connection_transaction_confusions.rb +379 -0
- data/lib/familia/base.rb +49 -10
- data/lib/familia/connection/handlers.rb +223 -0
- data/lib/familia/connection/individual_command_proxy.rb +64 -0
- data/lib/familia/connection/middleware.rb +75 -0
- data/lib/familia/connection/operation_core.rb +93 -0
- data/lib/familia/connection/operations.rb +277 -0
- data/lib/familia/connection/pipeline_core.rb +87 -0
- data/lib/familia/connection/transaction_core.rb +100 -0
- data/lib/familia/connection.rb +60 -186
- data/lib/familia/data_type/commands.rb +53 -51
- data/lib/familia/data_type/serialization.rb +108 -107
- data/lib/familia/data_type/types/counter.rb +1 -1
- data/lib/familia/data_type/types/hashkey.rb +13 -10
- data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
- data/lib/familia/data_type/types/lock.rb +3 -2
- data/lib/familia/data_type/types/sorted_set.rb +26 -15
- data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -5
- data/lib/familia/data_type/types/unsorted_set.rb +20 -27
- data/lib/familia/data_type.rb +75 -47
- data/lib/familia/distinguisher.rb +85 -0
- data/lib/familia/encryption/encrypted_data.rb +15 -24
- data/lib/familia/encryption/manager.rb +6 -4
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
- data/lib/familia/encryption/request_cache.rb +7 -7
- data/lib/familia/encryption.rb +2 -3
- data/lib/familia/errors.rb +9 -3
- data/lib/familia/{autoloader.rb → features/autoloader.rb} +49 -23
- data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
- data/lib/familia/features/encrypted_fields.rb +68 -66
- data/lib/familia/features/expiration/extensions.rb +61 -0
- data/lib/familia/features/expiration.rb +35 -87
- data/lib/familia/features/external_identifier.rb +11 -12
- data/lib/familia/features/object_identifier.rb +58 -20
- data/lib/familia/features/quantization.rb +17 -22
- data/lib/familia/features/relationships/README.md +97 -0
- data/lib/familia/features/relationships/collection_operations.rb +104 -0
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +301 -0
- data/lib/familia/features/relationships/indexing.rb +176 -256
- data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
- data/lib/familia/features/relationships/participation/participant_methods.rb +160 -0
- data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
- data/lib/familia/features/relationships/participation.rb +656 -0
- data/lib/familia/features/relationships/participation_relationship.rb +31 -0
- data/lib/familia/features/relationships/score_encoding.rb +20 -20
- data/lib/familia/features/relationships.rb +69 -271
- data/lib/familia/features/safe_dump.rb +127 -132
- data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
- data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
- data/lib/familia/features/transient_fields.rb +5 -5
- data/lib/familia/features.rb +21 -21
- data/lib/familia/field_type.rb +24 -4
- data/lib/familia/horreum/core/connection.rb +229 -26
- data/lib/familia/horreum/core/database_commands.rb +27 -17
- data/lib/familia/horreum/core/serialization.rb +40 -20
- data/lib/familia/horreum/core/utils.rb +2 -1
- data/lib/familia/horreum/shared/settings.rb +2 -1
- data/lib/familia/horreum/subclass/definition.rb +33 -45
- data/lib/familia/horreum/subclass/management.rb +72 -24
- data/lib/familia/horreum/subclass/related_fields_management.rb +82 -21
- data/lib/familia/horreum.rb +196 -114
- data/lib/familia/json_serializer.rb +0 -1
- data/lib/familia/logging.rb +11 -114
- data/lib/familia/refinements/dear_json.rb +122 -0
- data/lib/familia/refinements/logger_trace.rb +20 -17
- data/lib/familia/refinements/stylize_words.rb +65 -0
- data/lib/familia/refinements/time_literals.rb +60 -52
- data/lib/familia/refinements.rb +2 -1
- data/lib/familia/secure_identifier.rb +60 -28
- data/lib/familia/settings.rb +83 -7
- data/lib/familia/utils.rb +5 -87
- data/lib/familia/verifiable_identifier.rb +4 -4
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +72 -15
- data/lib/middleware/database_middleware.rb +56 -14
- data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
- data/try/configuration/scenarios_try.rb +1 -1
- data/try/connection/fiber_context_preservation_try.rb +250 -0
- data/try/connection/handler_constraints_try.rb +59 -0
- data/try/connection/operation_mode_guards_try.rb +208 -0
- data/try/connection/pipeline_fallback_integration_try.rb +128 -0
- data/try/connection/responsibility_chain_tracking_try.rb +72 -0
- data/try/connection/transaction_fallback_integration_try.rb +288 -0
- data/try/connection/transaction_mode_permissive_try.rb +153 -0
- data/try/connection/transaction_mode_strict_try.rb +98 -0
- data/try/connection/transaction_mode_warn_try.rb +131 -0
- data/try/connection/transaction_modes_try.rb +249 -0
- data/try/core/autoloader_try.rb +129 -11
- data/try/core/connection_try.rb +7 -7
- data/try/core/conventional_inheritance_try.rb +130 -0
- data/try/core/create_method_try.rb +15 -23
- data/try/core/database_consistency_try.rb +10 -10
- data/try/core/errors_try.rb +8 -11
- data/try/core/familia_extended_try.rb +2 -2
- data/try/core/familia_members_methods_try.rb +76 -0
- data/try/core/isolated_dbclient_try.rb +165 -0
- data/try/core/middleware_try.rb +16 -16
- data/try/core/persistence_operations_try.rb +4 -4
- data/try/core/pools_try.rb +42 -26
- data/try/core/secure_identifier_try.rb +28 -24
- data/try/core/time_utils_try.rb +10 -10
- data/try/core/tools_try.rb +1 -1
- data/try/core/utils_try.rb +2 -2
- data/try/data_types/boolean_try.rb +4 -4
- data/try/data_types/datatype_base_try.rb +0 -2
- data/try/data_types/list_try.rb +10 -10
- data/try/data_types/sorted_set_try.rb +5 -5
- data/try/data_types/string_try.rb +12 -12
- data/try/data_types/unsortedset_try.rb +33 -0
- data/try/debugging/cache_behavior_tracer.rb +7 -7
- data/try/debugging/debug_aad_process.rb +1 -1
- data/try/debugging/debug_concealed_internal.rb +1 -1
- data/try/debugging/debug_cross_context.rb +1 -1
- data/try/debugging/debug_fresh_cross_context.rb +1 -1
- data/try/debugging/encryption_method_tracer.rb +10 -10
- data/try/edge_cases/hash_symbolization_try.rb +1 -1
- data/try/edge_cases/ttl_side_effects_try.rb +1 -1
- data/try/encryption/config_persistence_try.rb +2 -2
- data/try/encryption/encryption_core_try.rb +19 -19
- data/try/encryption/instance_variable_scope_try.rb +1 -1
- data/try/encryption/module_loading_try.rb +2 -2
- data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
- data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
- data/try/encryption/secure_memory_handling_try.rb +1 -1
- data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
- data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
- data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
- data/try/features/external_identifier/external_identifier_try.rb +1 -1
- data/try/features/feature_dependencies_try.rb +3 -3
- data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
- data/try/features/object_identifier/object_identifier_try.rb +10 -0
- data/try/features/quantization/quantization_try.rb +1 -1
- data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
- data/try/features/relationships/indexing_try.rb +433 -0
- data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
- data/try/features/relationships/participation_commands_verification_try.rb +105 -0
- data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
- data/try/features/relationships/participation_reverse_index_try.rb +196 -0
- data/try/features/relationships/relationships_api_changes_try.rb +72 -71
- data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
- data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
- data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
- data/try/features/relationships/relationships_performance_try.rb +20 -20
- data/try/features/relationships/relationships_try.rb +27 -38
- data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
- data/try/features/transient_fields/refresh_reset_try.rb +1 -1
- data/try/features/transient_fields/simple_refresh_test.rb +1 -1
- data/try/helpers/test_cleanup.rb +86 -0
- data/try/helpers/test_helpers.rb +3 -3
- data/try/horreum/base_try.rb +3 -2
- data/try/horreum/commands_try.rb +1 -1
- data/try/horreum/destroy_related_fields_cleanup_try.rb +330 -0
- data/try/horreum/initialization_try.rb +11 -7
- data/try/horreum/relations_try.rb +21 -13
- data/try/horreum/serialization_try.rb +12 -11
- data/try/integration/cross_component_try.rb +3 -3
- data/try/memory/memory_basic_test.rb +1 -1
- data/try/memory/memory_docker_ruby_dump.sh +1 -1
- data/try/models/customer_safe_dump_try.rb +1 -1
- data/try/models/customer_try.rb +8 -10
- data/try/models/datatype_base_try.rb +3 -3
- data/try/models/familia_object_try.rb +9 -8
- data/try/performance/benchmarks_try.rb +2 -2
- data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
- data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
- data/try/prototypes/atomic_saves_v4.rb +1 -1
- data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
- data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
- data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
- data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
- data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
- data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
- data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
- data/try/prototypes/pooling/pool_siege.rb +11 -11
- data/try/prototypes/pooling/run_stress_tests.rb +7 -7
- data/try/refinements/dear_json_array_methods_try.rb +53 -0
- data/try/refinements/dear_json_hash_methods_try.rb +54 -0
- data/try/refinements/logger_trace_methods_try.rb +44 -0
- data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
- data/try/refinements/time_literals_string_methods_try.rb +80 -0
- metadata +77 -45
- data/.rubocop_todo.yml +0 -208
- data/docs/connection_pooling.md +0 -192
- data/docs/guides/Connection-Pooling-Guide.md +0 -437
- data/docs/guides/Encrypted-Fields-Overview.md +0 -101
- data/docs/guides/Feature-System-Autoloading.md +0 -228
- data/docs/guides/Home.md +0 -116
- data/docs/guides/Relationships-Guide.md +0 -737
- data/docs/guides/relationships-methods.md +0 -266
- data/docs/reference/auditing_database_commands.rb +0 -228
- data/examples/permissions.rb +0 -240
- data/lib/familia/features/autoloadable.rb +0 -113
- data/lib/familia/features/relationships/cascading.rb +0 -437
- data/lib/familia/features/relationships/membership.rb +0 -497
- data/lib/familia/features/relationships/permission_management.rb +0 -264
- data/lib/familia/features/relationships/querying.rb +0 -615
- data/lib/familia/features/relationships/redis_operations.rb +0 -274
- data/lib/familia/features/relationships/tracking.rb +0 -418
- data/lib/familia/refinements/snake_case.rb +0 -40
- data/lib/familia/validation/command_recorder.rb +0 -336
- data/lib/familia/validation/expectations.rb +0 -519
- data/lib/familia/validation/validation_helpers.rb +0 -443
- data/lib/familia/validation/validator.rb +0 -412
- data/lib/familia/validation.rb +0 -140
- data/try/data_types/set_try.rb +0 -33
- data/try/features/autoloadable/autoloadable_try.rb +0 -61
- data/try/features/relationships/categorical_permissions_try.rb +0 -515
- data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -111
- data/try/validation/atomic_operations_try.rb.disabled +0 -320
- data/try/validation/command_validation_try.rb.disabled +0 -207
- data/try/validation/performance_validation_try.rb.disabled +0 -324
- data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -0,0 +1,277 @@
|
|
1
|
+
# lib/familia/connection/operations.rb
|
2
|
+
|
3
|
+
# Familia
|
4
|
+
#
|
5
|
+
# A family warehouse for your keystore data.
|
6
|
+
#
|
7
|
+
module Familia
|
8
|
+
module Connection
|
9
|
+
module Operations
|
10
|
+
# Executes Database commands atomically within a transaction (MULTI/EXEC).
|
11
|
+
#
|
12
|
+
# Database transactions queue commands and execute them atomically as a single unit.
|
13
|
+
# All commands succeed together or all fail together, ensuring data consistency.
|
14
|
+
#
|
15
|
+
# @yield [Redis] The Database transaction connection
|
16
|
+
# @return [Array] Results of all commands executed in the transaction
|
17
|
+
#
|
18
|
+
# @example Basic transaction usage
|
19
|
+
# Familia.transaction do |trans|
|
20
|
+
# trans.set("key1", "value1")
|
21
|
+
# trans.incr("counter")
|
22
|
+
# trans.lpush("list", "item")
|
23
|
+
# end
|
24
|
+
# # Returns: ["OK", 2, 1] - results of all commands
|
25
|
+
#
|
26
|
+
# @note **Comparison of Database batch operations:**
|
27
|
+
#
|
28
|
+
# | Feature | Multi/Exec | Pipeline |
|
29
|
+
# |-----------------|-----------------|-----------------|
|
30
|
+
# | Atomicity | Yes | No |
|
31
|
+
# | Performance | Good | Better |
|
32
|
+
# | Error handling | All-or-nothing | Per-command |
|
33
|
+
# | Use case | Data consistency| Bulk operations |
|
34
|
+
#
|
35
|
+
# Executes a Redis transaction (MULTI/EXEC) with proper connection handling.
|
36
|
+
#
|
37
|
+
# Provides atomic execution of multiple Redis commands with automatic connection
|
38
|
+
# management and operation mode enforcement. Returns a MultiResult object containing
|
39
|
+
# both success status and command results.
|
40
|
+
#
|
41
|
+
# @param [Proc] block The block containing Redis commands to execute atomically
|
42
|
+
# @yield [Redis] conn The Redis connection configured for transaction mode
|
43
|
+
# @return [MultiResult] Result object with success status and command results
|
44
|
+
#
|
45
|
+
# @raise [Familia::OperationModeError] When called with incompatible connection handlers
|
46
|
+
# (e.g., FiberConnectionHandler or DefaultConnectionHandler that don't support transactions)
|
47
|
+
#
|
48
|
+
# @example Basic transaction usage
|
49
|
+
# result = Familia.transaction do |conn|
|
50
|
+
# conn.set('key1', 'value1')
|
51
|
+
# conn.set('key2', 'value2')
|
52
|
+
# conn.get('key1')
|
53
|
+
# end
|
54
|
+
# result.successful? # => true (if all commands succeeded)
|
55
|
+
# result.results # => ["OK", "OK", "value1"]
|
56
|
+
# result.results.first # => "OK"
|
57
|
+
#
|
58
|
+
# @example Checking transaction success
|
59
|
+
# result = Familia.transaction do |conn|
|
60
|
+
# conn.incr('counter')
|
61
|
+
# conn.decr('other_counter')
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# if result.successful?
|
65
|
+
# puts "All commands succeeded: #{result.results}"
|
66
|
+
# else
|
67
|
+
# puts "Some commands failed: #{result.results}"
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# @example Nested transactions (reentrant behavior)
|
71
|
+
# result = Familia.transaction do |outer_conn|
|
72
|
+
# outer_conn.set('outer', 'value')
|
73
|
+
#
|
74
|
+
# # Nested transaction reuses the same connection
|
75
|
+
# inner_result = Familia.transaction do |inner_conn|
|
76
|
+
# inner_conn.set('inner', 'value')
|
77
|
+
# inner_conn.get('inner') # Returns the value directly in nested context
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# [outer_result, inner_result]
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# @note Connection Handler Compatibility:
|
84
|
+
# - FiberTransactionHandler: Supports reentrant transactions
|
85
|
+
# - ProviderConnectionHandler: Full transaction support
|
86
|
+
# - CreateConnectionHandler: Full transaction support
|
87
|
+
# - FiberConnectionHandler: Blocked (raises OperationModeError)
|
88
|
+
# - DefaultConnectionHandler: Blocked (raises OperationModeError)
|
89
|
+
#
|
90
|
+
# @note Thread Safety:
|
91
|
+
# Uses Fiber-local storage to maintain transaction context across nested calls
|
92
|
+
# and ensure proper cleanup even when exceptions occur.
|
93
|
+
#
|
94
|
+
# @see MultiResult For details on the return value structure
|
95
|
+
# @see Familia.pipelined For non-atomic command batching
|
96
|
+
# @see #batch_update For similar MultiResult pattern in Horreum models
|
97
|
+
def transaction(&)
|
98
|
+
Familia::Connection::TransactionCore.execute_transaction(-> { dbclient }, &)
|
99
|
+
end
|
100
|
+
alias multi transaction
|
101
|
+
|
102
|
+
# Executes Database commands in a pipeline for improved performance.
|
103
|
+
#
|
104
|
+
# Pipelines send multiple commands without waiting for individual responses,
|
105
|
+
# reducing network round-trips. Commands execute independently and can
|
106
|
+
# succeed or fail without affecting other commands in the pipeline.
|
107
|
+
#
|
108
|
+
# @yield [Redis] The Database pipeline connection
|
109
|
+
# @return [Array] Results of all commands executed in the pipeline
|
110
|
+
#
|
111
|
+
# @example Basic pipeline usage
|
112
|
+
# Familia.pipelined do |pipe|
|
113
|
+
# pipe.set("key1", "value1")
|
114
|
+
# pipe.incr("counter")
|
115
|
+
# pipe.lpush("list", "item")
|
116
|
+
# end
|
117
|
+
# # Returns: ["OK", 2, 1] - results of all commands
|
118
|
+
#
|
119
|
+
# @example Error handling - commands succeed/fail independently
|
120
|
+
# results = Familia.pipelined do |conn|
|
121
|
+
# conn.set("valid_key", "value") # This will succeed
|
122
|
+
# conn.incr("string_key") # This will fail (wrong type)
|
123
|
+
# conn.set("another_key", "value2") # This will still succeed
|
124
|
+
# end
|
125
|
+
# # Returns: ["OK", Redis::CommandError, "OK"]
|
126
|
+
# # Notice how the error doesn't prevent other commands from executing
|
127
|
+
#
|
128
|
+
# @example Contrast with transaction behavior
|
129
|
+
# results = Familia.transaction do |conn|
|
130
|
+
# conn.set("inventory:item1", 100)
|
131
|
+
# conn.incr("invalid_key") # Fails, rolls back everything
|
132
|
+
# conn.set("inventory:item2", 200) # Won't be applied
|
133
|
+
# end
|
134
|
+
# # Result: neither item1 nor item2 are set due to the error
|
135
|
+
#
|
136
|
+
# Executes Redis commands in a pipeline for improved performance.
|
137
|
+
#
|
138
|
+
# Batches multiple Redis commands together and sends them in a single network
|
139
|
+
# round-trip, improving performance for multiple independent operations. Returns
|
140
|
+
# a MultiResult object containing both success status and command results.
|
141
|
+
#
|
142
|
+
# @param [Proc] block The block containing Redis commands to execute in pipeline
|
143
|
+
# @yield [Redis] conn The Redis connection configured for pipelined mode
|
144
|
+
# @return [MultiResult] Result object with success status and command results
|
145
|
+
#
|
146
|
+
# @raise [Familia::OperationModeError] When called with incompatible connection handlers
|
147
|
+
# (e.g., FiberConnectionHandler or DefaultConnectionHandler that don't support pipelines)
|
148
|
+
#
|
149
|
+
# @example Basic pipeline usage
|
150
|
+
# result = Familia.pipelined do |conn|
|
151
|
+
# conn.set('key1', 'value1')
|
152
|
+
# conn.set('key2', 'value2')
|
153
|
+
# conn.get('key1')
|
154
|
+
# conn.incr('counter')
|
155
|
+
# end
|
156
|
+
# result.successful? # => true (if all commands succeeded)
|
157
|
+
# result.results # => ["OK", "OK", "value1", 1]
|
158
|
+
# result.results.length # => 4
|
159
|
+
#
|
160
|
+
# @example Performance optimization with pipeline
|
161
|
+
# # Instead of multiple round-trips:
|
162
|
+
# # value1 = redis.get('key1') # Round-trip 1
|
163
|
+
# # value2 = redis.get('key2') # Round-trip 2
|
164
|
+
# # value3 = redis.get('key3') # Round-trip 3
|
165
|
+
#
|
166
|
+
# # Use pipeline for single round-trip:
|
167
|
+
# result = Familia.pipelined do |conn|
|
168
|
+
# conn.get('key1')
|
169
|
+
# conn.get('key2')
|
170
|
+
# conn.get('key3')
|
171
|
+
# end
|
172
|
+
# values = result.results # => ["value1", "value2", "value3"]
|
173
|
+
#
|
174
|
+
# @example Checking pipeline success
|
175
|
+
# result = Familia.pipelined do |conn|
|
176
|
+
# conn.set('temp_key', 'temp_value')
|
177
|
+
# conn.expire('temp_key', 60)
|
178
|
+
# conn.get('temp_key')
|
179
|
+
# end
|
180
|
+
#
|
181
|
+
# if result.successful?
|
182
|
+
# puts "Pipeline completed: #{result.results}"
|
183
|
+
# else
|
184
|
+
# puts "Some operations failed: #{result.results}"
|
185
|
+
# end
|
186
|
+
#
|
187
|
+
# @example Nested pipelines (reentrant behavior)
|
188
|
+
# result = Familia.pipelined do |outer_conn|
|
189
|
+
# outer_conn.set('outer', 'value')
|
190
|
+
#
|
191
|
+
# # Nested pipeline reuses the same connection
|
192
|
+
# inner_result = Familia.pipelined do |inner_conn|
|
193
|
+
# inner_conn.get('outer') # Returns Redis::Future in nested context
|
194
|
+
# end
|
195
|
+
#
|
196
|
+
# outer_conn.get('outer')
|
197
|
+
# end
|
198
|
+
#
|
199
|
+
# @note Pipeline vs Transaction Differences:
|
200
|
+
# - Pipeline: Commands executed independently, some may succeed while others fail
|
201
|
+
# - Transaction: All-or-nothing execution, commands are atomic as a group
|
202
|
+
# - Pipeline: Better performance for independent operations
|
203
|
+
# - Transaction: Better consistency for related operations
|
204
|
+
#
|
205
|
+
# @note Connection Handler Compatibility:
|
206
|
+
# - ProviderConnectionHandler: Full pipeline support
|
207
|
+
# - CreateConnectionHandler: Full pipeline support
|
208
|
+
# - FiberTransactionHandler: Blocked (raises OperationModeError)
|
209
|
+
# - FiberConnectionHandler: Blocked (raises OperationModeError)
|
210
|
+
# - DefaultConnectionHandler: Blocked (raises OperationModeError)
|
211
|
+
#
|
212
|
+
# @note Thread Safety:
|
213
|
+
# Uses Fiber-local storage to maintain pipeline context across nested calls
|
214
|
+
# and ensure proper cleanup even when exceptions occur.
|
215
|
+
#
|
216
|
+
# @see MultiResult For details on the return value structure
|
217
|
+
# @see Familia.transaction For atomic command execution
|
218
|
+
# @see #batch_update For similar MultiResult pattern in Horreum models
|
219
|
+
def pipelined(&block)
|
220
|
+
PipelineCore.execute_pipeline(-> { dbclient }, &block)
|
221
|
+
end
|
222
|
+
alias pipeline pipelined
|
223
|
+
|
224
|
+
# Provides explicit access to a Database connection.
|
225
|
+
#
|
226
|
+
# This method is useful when you need direct access to a connection
|
227
|
+
# for operations not covered by other methods. The connection is
|
228
|
+
# properly managed and returned to the pool (if using connection_provider).
|
229
|
+
#
|
230
|
+
# @yield [Redis] A Database connection
|
231
|
+
# @return The result of the block
|
232
|
+
#
|
233
|
+
# @example Using with_dbclient for custom operations
|
234
|
+
# Familia.with_dbclient do |conn|
|
235
|
+
# conn.set("custom_key", "value")
|
236
|
+
# conn.expire("custom_key", 3600)
|
237
|
+
# end
|
238
|
+
#
|
239
|
+
def with_dbclient(&)
|
240
|
+
yield dbclient
|
241
|
+
end
|
242
|
+
|
243
|
+
# Provides explicit access to an isolated Database connection for temporary operations.
|
244
|
+
#
|
245
|
+
# This method creates a new connection that won't interfere with the cached
|
246
|
+
# connection pool, executes the given block with that connection, and ensures
|
247
|
+
# the connection is properly closed afterward.
|
248
|
+
#
|
249
|
+
# Perfect for database scanning, inspection, or migration operations where
|
250
|
+
# you need to access different databases without affecting your models'
|
251
|
+
# normal connections.
|
252
|
+
#
|
253
|
+
# @param uri [String, URI, Integer, nil] The URI or database number to connect to.
|
254
|
+
# @yield [Redis] An isolated Database connection
|
255
|
+
# @return The result of the block
|
256
|
+
#
|
257
|
+
# @example Safely scanning for legacy data
|
258
|
+
# Familia.with_isolated_dbclient(5) do |conn|
|
259
|
+
# conn.keys("session:*")
|
260
|
+
# end
|
261
|
+
#
|
262
|
+
# @example Performing migration tasks
|
263
|
+
# Familia.with_isolated_dbclient(1) do |conn|
|
264
|
+
# conn.scan_each(match: "user:*") { |key| puts key }
|
265
|
+
# end
|
266
|
+
#
|
267
|
+
def with_isolated_dbclient(uri = nil, &)
|
268
|
+
client = isolated_dbclient(uri)
|
269
|
+
begin
|
270
|
+
yield client
|
271
|
+
ensure
|
272
|
+
client&.close
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Familia
|
4
|
+
module Connection
|
5
|
+
# Pipeline execution with configurable fallback behavior
|
6
|
+
#
|
7
|
+
# Handles two pipeline scenarios based on connection handler capabilities:
|
8
|
+
# 1. Normal pipeline when handler supports pipelines
|
9
|
+
# 2. Individual command execution with configurable error/warn/silent modes
|
10
|
+
#
|
11
|
+
# @see OperationCore For shared fallback logic
|
12
|
+
# @see TransactionCore For similar transaction handling
|
13
|
+
#
|
14
|
+
module PipelineCore
|
15
|
+
# Executes a pipeline with configurable fallback behavior
|
16
|
+
#
|
17
|
+
# Handles pipeline execution based on connection handler capabilities.
|
18
|
+
# When handler doesn't support pipelines, fallback behavior is controlled
|
19
|
+
# by Familia.pipeline_mode setting.
|
20
|
+
#
|
21
|
+
# @param dbclient_proc [Proc] Lambda that returns the Redis connection
|
22
|
+
# @param block [Proc] Block containing Redis commands to execute
|
23
|
+
# @return [MultiResult] Result object with success status and command results
|
24
|
+
# @yield [Redis] Redis connection or proxy for command execution
|
25
|
+
#
|
26
|
+
# @example Basic usage
|
27
|
+
# result = PipelineCore.execute_pipeline(-> { dbclient }) do |conn|
|
28
|
+
# conn.set('key1', 'value1')
|
29
|
+
# conn.incr('counter')
|
30
|
+
# end
|
31
|
+
# result.successful? # => true/false
|
32
|
+
# result.results # => ["OK", 1]
|
33
|
+
#
|
34
|
+
# @example With fallback modes
|
35
|
+
# Familia.configure { |c| c.pipeline_mode = :permissive }
|
36
|
+
# result = PipelineCore.execute_pipeline(-> { cached_conn }) do |conn|
|
37
|
+
# conn.set('key', 'value') # Executes individually, no error
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
def self.execute_pipeline(dbclient_proc, &block)
|
41
|
+
# First, get the connection to populate the handler class
|
42
|
+
connection = dbclient_proc.call
|
43
|
+
handler_class = Fiber[:familia_connection_handler_class]
|
44
|
+
|
45
|
+
# Check pipeline capability
|
46
|
+
pipeline_capability = handler_class&.allows_pipelined
|
47
|
+
|
48
|
+
if pipeline_capability == false
|
49
|
+
OperationCore.handle_fallback(:pipeline, dbclient_proc, handler_class, &block)
|
50
|
+
else
|
51
|
+
# Normal pipeline flow (includes nil, true, and other values)
|
52
|
+
execute_normal_pipeline(dbclient_proc, &block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Executes a normal Redis pipeline
|
59
|
+
#
|
60
|
+
# Handles proper Fiber-local state management and cleanup in ensure blocks.
|
61
|
+
# Manages nested pipeline contexts by checking for existing pipeline state.
|
62
|
+
#
|
63
|
+
# @param dbclient_proc [Proc] Lambda that returns the Redis connection
|
64
|
+
# @param block [Proc] Block containing Redis commands to execute
|
65
|
+
# @return [MultiResult] Result object with pipeline command results
|
66
|
+
#
|
67
|
+
def self.execute_normal_pipeline(dbclient_proc, &block)
|
68
|
+
# Check for existing pipeline context
|
69
|
+
return yield(Fiber[:familia_pipeline]) if Fiber[:familia_pipeline]
|
70
|
+
|
71
|
+
command_return_values = dbclient_proc.call.pipelined do |conn|
|
72
|
+
Fiber[:familia_pipeline] = conn
|
73
|
+
begin
|
74
|
+
yield(conn)
|
75
|
+
ensure
|
76
|
+
Fiber[:familia_pipeline] = nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Return same MultiResult format as other methods
|
81
|
+
# Pipeline success is true if no exceptions occurred (all commands executed)
|
82
|
+
summary_boolean = command_return_values.none? { |ret| ret.is_a?(Exception) }
|
83
|
+
MultiResult.new(summary_boolean, command_return_values)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# lib/familia/connection/transaction_core.rb
|
2
|
+
|
3
|
+
module Familia
|
4
|
+
module Connection
|
5
|
+
# Core transaction logic shared between global and instance transaction methods
|
6
|
+
#
|
7
|
+
# This module provides unified transaction handling with configurable fallback
|
8
|
+
# behavior when transactions are unavailable due to connection handler constraints.
|
9
|
+
# Eliminates code duplication between Operations and Horreum Connection modules.
|
10
|
+
#
|
11
|
+
# @example Usage in transaction methods
|
12
|
+
# def transaction(&block)
|
13
|
+
# TransactionCore.execute_transaction(-> { dbclient }, &block)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
module TransactionCore
|
17
|
+
# Executes a transaction with configurable fallback behavior
|
18
|
+
#
|
19
|
+
# Handles three transaction scenarios based on connection handler capabilities:
|
20
|
+
# 1. Normal transaction (MULTI/EXEC) when handler supports transactions
|
21
|
+
# 2. Reentrant transaction when already within a transaction context
|
22
|
+
# 3. Individual command execution with configurable error/warn/silent modes
|
23
|
+
#
|
24
|
+
# @param dbclient_proc [Proc] Lambda that returns the Redis connection
|
25
|
+
# @param block [Proc] Block containing Redis commands to execute
|
26
|
+
# @return [MultiResult] Result object with success status and command results
|
27
|
+
# @yield [Redis] Redis connection or proxy for command execution
|
28
|
+
#
|
29
|
+
# @example Basic usage
|
30
|
+
# result = TransactionCore.execute_transaction(-> { dbclient }) do |conn|
|
31
|
+
# conn.set('key1', 'value1')
|
32
|
+
# conn.incr('counter')
|
33
|
+
# end
|
34
|
+
# result.successful? # => true/false
|
35
|
+
# result.results # => ["OK", 1]
|
36
|
+
#
|
37
|
+
def self.execute_transaction(dbclient_proc, &block)
|
38
|
+
# First, get the connection to populate the handler class
|
39
|
+
connection = dbclient_proc.call
|
40
|
+
handler_class = Fiber[:familia_connection_handler_class]
|
41
|
+
|
42
|
+
# Check transaction capability
|
43
|
+
transaction_capability = handler_class&.allows_transaction
|
44
|
+
|
45
|
+
if transaction_capability == false
|
46
|
+
handle_transaction_fallback(dbclient_proc, handler_class, &block)
|
47
|
+
elsif transaction_capability == :reentrant
|
48
|
+
# Already in transaction, just yield the connection
|
49
|
+
yield(Fiber[:familia_transaction])
|
50
|
+
else
|
51
|
+
# Normal transaction flow (includes nil, true, and other values)
|
52
|
+
execute_normal_transaction(dbclient_proc, &block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Handles transaction fallback based on configured transaction mode
|
59
|
+
#
|
60
|
+
# Delegates to OperationCore.handle_fallback for consistent behavior
|
61
|
+
# across transaction and pipeline operations.
|
62
|
+
#
|
63
|
+
# @param dbclient_proc [Proc] Lambda that returns the Redis connection
|
64
|
+
# @param handler_class [Class] The connection handler class that blocked transaction
|
65
|
+
# @param block [Proc] Block containing Redis commands to execute
|
66
|
+
# @return [MultiResult] Result from individual command execution or raises error
|
67
|
+
#
|
68
|
+
def self.handle_transaction_fallback(dbclient_proc, handler_class, &block)
|
69
|
+
OperationCore.handle_fallback(:transaction, dbclient_proc, handler_class, &block)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Executes a normal Redis transaction using MULTI/EXEC
|
73
|
+
#
|
74
|
+
# Handles the standard transaction flow including nested transaction detection,
|
75
|
+
# proper Fiber-local state management, and cleanup in ensure blocks.
|
76
|
+
#
|
77
|
+
# @param dbclient_proc [Proc] Lambda that returns the Redis connection
|
78
|
+
# @param block [Proc] Block containing Redis commands to execute
|
79
|
+
# @return [MultiResult] Result object with transaction command results
|
80
|
+
#
|
81
|
+
def self.execute_normal_transaction(dbclient_proc, &block)
|
82
|
+
# Check for existing transaction context
|
83
|
+
return yield(Fiber[:familia_transaction]) if Fiber[:familia_transaction]
|
84
|
+
|
85
|
+
command_return_values = dbclient_proc.call.multi do |conn|
|
86
|
+
Fiber[:familia_transaction] = conn
|
87
|
+
begin
|
88
|
+
yield(conn)
|
89
|
+
ensure
|
90
|
+
Fiber[:familia_transaction] = nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Return same MultiResult format as other methods
|
95
|
+
summary_boolean = command_return_values.all? { |ret| %w[OK 0 1].include?(ret.to_s) }
|
96
|
+
MultiResult.new(summary_boolean, command_return_values)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|