familia 2.0.0.pre15 → 2.0.0.pre17
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 +2 -2
- data/.github/workflows/code-quality.yml +138 -0
- data/.github/workflows/code-smells.yml +85 -0
- data/.github/workflows/docs.yml +31 -8
- data/.gitignore +3 -1
- data/.pre-commit-config.yaml +7 -1
- data/.reek.yml +98 -0
- data/.rubocop.yml +54 -10
- data/.talismanrc +9 -0
- data/.yardopts +18 -13
- data/CHANGELOG.rst +86 -4
- data/CLAUDE.md +39 -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 +42 -42
- 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 +2 -2
- data/docs/migrating/v2.0.0-pre12.md +2 -2
- 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 +624 -20
- 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 +7 -7
- data/examples/single_connection_transaction_confusions.rb +379 -0
- data/lib/familia/base.rb +51 -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/class_methods.rb +63 -0
- data/lib/familia/data_type/commands.rb +53 -51
- data/lib/familia/data_type/connection.rb +83 -0
- data/lib/familia/data_type/serialization.rb +108 -107
- data/lib/familia/data_type/settings.rb +96 -0
- data/lib/familia/data_type/types/counter.rb +1 -1
- data/lib/familia/data_type/types/hashkey.rb +15 -11
- 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 +128 -14
- data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -9
- data/lib/familia/data_type/types/unsorted_set.rb +20 -27
- data/lib/familia/data_type.rb +12 -171
- 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/features/autoloader.rb +30 -12
- 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 +71 -66
- data/lib/familia/features/expiration/extensions.rb +1 -1
- data/lib/familia/features/expiration.rb +31 -26
- data/lib/familia/features/external_identifier.rb +57 -19
- data/lib/familia/features/object_identifier.rb +134 -25
- data/lib/familia/features/quantization.rb +16 -21
- 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 +306 -0
- data/lib/familia/features/relationships/indexing.rb +182 -256
- data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
- data/lib/familia/features/relationships/participation/participant_methods.rb +164 -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 +65 -266
- data/lib/familia/features/safe_dump.rb +127 -130
- 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 +10 -7
- data/lib/familia/features.rb +10 -14
- data/lib/familia/field_type.rb +6 -4
- data/lib/familia/horreum/connection.rb +297 -0
- data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +27 -17
- data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +139 -74
- data/lib/familia/horreum/{subclass/management.rb → management.rb} +73 -27
- data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +108 -185
- data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +104 -23
- data/lib/familia/horreum/serialization.rb +172 -0
- data/lib/familia/horreum/{shared/settings.rb → settings.rb} +2 -1
- data/lib/familia/horreum/{core/utils.rb → utils.rb} +2 -1
- data/lib/familia/horreum.rb +222 -119
- 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 -14
- 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 +2 -2
- 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 +120 -2
- data/try/core/connection_try.rb +10 -10
- 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 +11 -10
- data/try/core/errors_try.rb +11 -14
- data/try/core/familia_extended_try.rb +2 -2
- data/try/core/familia_members_methods_try.rb +76 -0
- data/try/core/familia_try.rb +1 -1
- 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 +3 -3
- 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/sorted_set_zadd_options_try.rb +625 -0
- 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/field_groups_try.rb +244 -0
- 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 +443 -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 +3 -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 +6 -7
- data/try/horreum/auto_indexing_on_save_try.rb +212 -0
- data/try/horreum/base_try.rb +3 -2
- data/try/horreum/commands_try.rb +3 -1
- data/try/horreum/defensive_initialization_try.rb +86 -0
- data/try/horreum/destroy_related_fields_cleanup_try.rb +332 -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/horreum/settings_try.rb +2 -0
- 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 +2 -2
- data/try/models/customer_safe_dump_try.rb +1 -1
- data/try/models/customer_try.rb +13 -15
- 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
- data/try/valkey.conf +26 -0
- metadata +92 -52
- 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 -198
- 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/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/horreum/core/connection.rb +0 -73
- data/lib/familia/horreum/core.rb +0 -21
- 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/relationships/categorical_permissions_try.rb +0 -515
- data/try/features/safe_dump/module_based_extensions_try.rb +0 -100
- data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -107
- 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
@@ -1,30 +1,29 @@
|
|
1
|
-
# lib/
|
1
|
+
# lib/multi_result.rb
|
2
2
|
|
3
|
-
#
|
3
|
+
# Represents the result of a Valkey/Redis transaction operation.
|
4
4
|
#
|
5
|
-
# This
|
6
|
-
#
|
7
|
-
#
|
8
|
-
# keeps the results safe in its pocket dimension.
|
5
|
+
# This class encapsulates the outcome of a Database transaction,
|
6
|
+
# providing access to both the success status and the individual
|
7
|
+
# command results returned by the transaction.
|
9
8
|
#
|
10
|
-
# @attr_reader success [Boolean]
|
11
|
-
#
|
12
|
-
# @attr_reader results [Array<String>]
|
13
|
-
#
|
9
|
+
# @attr_reader success [Boolean] Indicates whether all commands
|
10
|
+
# in the transaction completed successfully.
|
11
|
+
# @attr_reader results [Array<String>] Array of return values
|
12
|
+
# from the Database commands executed in the transaction.
|
14
13
|
#
|
15
|
-
# @example
|
14
|
+
# @example Creating a MultiResult instance
|
16
15
|
# result = MultiResult.new(true, ["OK", "OK"])
|
17
16
|
#
|
18
|
-
# @example
|
17
|
+
# @example Checking transaction success
|
19
18
|
# if result.successful?
|
20
|
-
# puts "
|
19
|
+
# puts "Transaction completed successfully"
|
21
20
|
# else
|
22
|
-
# puts "
|
21
|
+
# puts "Transaction failed"
|
23
22
|
# end
|
24
23
|
#
|
25
|
-
# @example
|
24
|
+
# @example Accessing individual command results
|
26
25
|
# result.results.each_with_index do |value, index|
|
27
|
-
# puts "Command #{index + 1}
|
26
|
+
# puts "Command #{index + 1} returned: #{value}"
|
28
27
|
# end
|
29
28
|
#
|
30
29
|
class MultiResult
|
@@ -58,6 +57,13 @@ class MultiResult
|
|
58
57
|
end
|
59
58
|
alias to_a tuple
|
60
59
|
|
60
|
+
# Returns the number of results in the multi-operation.
|
61
|
+
#
|
62
|
+
# @return [Integer] The number of individual command results returned by the transaction.
|
63
|
+
def size
|
64
|
+
results.size
|
65
|
+
end
|
66
|
+
|
61
67
|
def to_h
|
62
68
|
{ success: successful?, results: results }
|
63
69
|
end
|
@@ -69,4 +75,5 @@ class MultiResult
|
|
69
75
|
@success
|
70
76
|
end
|
71
77
|
alias success? successful?
|
78
|
+
alias areyouhappynow? successful?
|
72
79
|
end
|
@@ -24,11 +24,11 @@ rescue StandardError => e
|
|
24
24
|
end
|
25
25
|
#=> false
|
26
26
|
|
27
|
-
## custom Redis URI configuration doesn't always work
|
27
|
+
## custom Valkey/Redis URI configuration doesn't always work
|
28
28
|
begin
|
29
29
|
# Test with custom URI
|
30
30
|
original_uri = Familia.uri
|
31
|
-
test_uri = 'redis://localhost:
|
31
|
+
test_uri = 'redis://localhost:2525/10'
|
32
32
|
|
33
33
|
Familia.uri = test_uri
|
34
34
|
current_uri = Familia.uri
|
@@ -0,0 +1,250 @@
|
|
1
|
+
# Fiber Context Preservation Tryouts
|
2
|
+
#
|
3
|
+
# Tests that verify the removal of previous_conn preservation logic
|
4
|
+
# in transaction and pipelined methods is safe with the new operation
|
5
|
+
# guard system. The operation guards prevent unsafe handlers from
|
6
|
+
# reaching the transaction/pipeline blocks, making fiber preservation
|
7
|
+
# redundant and safe to remove.
|
8
|
+
#
|
9
|
+
# Key scenarios tested:
|
10
|
+
# - Transaction/pipeline blocks don't interfere with safe handlers
|
11
|
+
# - Fiber context is properly managed without manual preservation
|
12
|
+
# - Operation guards prevent unsafe scenarios before fiber issues arise
|
13
|
+
# - Method aliases work correctly
|
14
|
+
|
15
|
+
require_relative '../helpers/test_helpers'
|
16
|
+
|
17
|
+
## Transaction method works without previous_conn preservation
|
18
|
+
customer = Customer.new(custid: 'tx_test')
|
19
|
+
original_fiber_conn = Fiber[:familia_connection]
|
20
|
+
|
21
|
+
# Test transaction execution
|
22
|
+
result = Familia.transaction do |conn|
|
23
|
+
conn.set('familia:tx_test', 'transaction_success')
|
24
|
+
conn.get('familia:tx_test')
|
25
|
+
end
|
26
|
+
|
27
|
+
# Verify transaction succeeded and fiber state is clean
|
28
|
+
transaction_worked = result.results.last == 'transaction_success'
|
29
|
+
fiber_state_clean = Fiber[:familia_connection] == original_fiber_conn
|
30
|
+
transaction_worked && fiber_state_clean
|
31
|
+
#=> true
|
32
|
+
|
33
|
+
## Pipelined method works without previous_conn preservation
|
34
|
+
original_fiber_conn = Fiber[:familia_connection]
|
35
|
+
|
36
|
+
# Test pipeline execution
|
37
|
+
result = Familia.pipelined do |conn|
|
38
|
+
conn.set('familia:pipe_test', 'pipeline_success')
|
39
|
+
conn.get('familia:pipe_test')
|
40
|
+
end
|
41
|
+
|
42
|
+
# Verify pipeline succeeded and fiber state is clean
|
43
|
+
pipeline_worked = result.results.last == 'pipeline_success'
|
44
|
+
fiber_state_clean = Fiber[:familia_connection] == original_fiber_conn
|
45
|
+
pipeline_worked && fiber_state_clean
|
46
|
+
#=> true
|
47
|
+
|
48
|
+
## Transaction method cleans up fiber context on success
|
49
|
+
Fiber[:familia_transaction]
|
50
|
+
#=> nil
|
51
|
+
|
52
|
+
## Pipelined method cleans up fiber context on success
|
53
|
+
Fiber[:familia_pipeline]
|
54
|
+
#=> nil
|
55
|
+
|
56
|
+
## Transaction method cleans up fiber context on exception
|
57
|
+
begin
|
58
|
+
Familia.transaction do |conn|
|
59
|
+
conn.set('test', 'value')
|
60
|
+
raise StandardError, 'test error'
|
61
|
+
end
|
62
|
+
rescue StandardError
|
63
|
+
# Expected error
|
64
|
+
end
|
65
|
+
|
66
|
+
# Fiber should be clean even after exception
|
67
|
+
Fiber[:familia_transaction]
|
68
|
+
#=> nil
|
69
|
+
|
70
|
+
## Pipelined method cleans up fiber context on exception
|
71
|
+
begin
|
72
|
+
Familia.pipelined do |conn|
|
73
|
+
conn.set('test', 'value')
|
74
|
+
raise StandardError, 'test error'
|
75
|
+
end
|
76
|
+
rescue StandardError
|
77
|
+
# Expected error
|
78
|
+
end
|
79
|
+
|
80
|
+
# Fiber should be clean even after exception
|
81
|
+
Fiber[:familia_pipeline]
|
82
|
+
#=> nil
|
83
|
+
|
84
|
+
## Nested transactions work with reentrant handler
|
85
|
+
result = Familia.transaction do |outer_conn|
|
86
|
+
outer_value = outer_conn.set('outer', 'outer_value')
|
87
|
+
|
88
|
+
inner_result = Familia.transaction do |inner_conn|
|
89
|
+
inner_conn.set('inner', 'inner_value')
|
90
|
+
inner_conn.get('inner')
|
91
|
+
end
|
92
|
+
|
93
|
+
[outer_value, inner_result]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Both outer and inner operations should succeed
|
97
|
+
result.results.first == 'OK' && result.results.last == 'inner_value'
|
98
|
+
#=> true
|
99
|
+
|
100
|
+
## Safe handlers don't trigger preservation logic
|
101
|
+
# Test with CreateConnectionHandler (fresh connections)
|
102
|
+
customer = Customer.new(custid: 'safe_test')
|
103
|
+
|
104
|
+
# Transaction should work normally
|
105
|
+
tx_result = customer.transaction do |conn|
|
106
|
+
conn.set(customer.dbkey('tx_field'), 'tx_value')
|
107
|
+
end
|
108
|
+
|
109
|
+
# Pipeline should work normally
|
110
|
+
pipe_result = customer.pipelined do |conn|
|
111
|
+
conn.set(customer.dbkey('pipe_field'), 'pipe_value')
|
112
|
+
end
|
113
|
+
|
114
|
+
tx_result.results.first == 'OK' && pipe_result.results.first == 'OK'
|
115
|
+
#=> true
|
116
|
+
|
117
|
+
## Connection provider works with transactions and pipelines
|
118
|
+
original_provider = Familia.connection_provider
|
119
|
+
Familia.connection_provider = ->(uri) { Redis.new(url: uri) }
|
120
|
+
|
121
|
+
begin
|
122
|
+
customer = Customer.new(custid: 'provider_test')
|
123
|
+
|
124
|
+
# Transaction with provider
|
125
|
+
tx_result = customer.transaction do |conn|
|
126
|
+
conn.set('provider:tx', 'tx_success')
|
127
|
+
conn.get('provider:tx')
|
128
|
+
end
|
129
|
+
|
130
|
+
# Pipeline with provider
|
131
|
+
pipe_result = customer.pipelined do |conn|
|
132
|
+
conn.set('provider:pipe', 'pipe_success')
|
133
|
+
conn.get('provider:pipe')
|
134
|
+
end
|
135
|
+
|
136
|
+
tx_result.results.last == 'tx_success' && pipe_result.results.last == 'pipe_success'
|
137
|
+
ensure
|
138
|
+
Familia.connection_provider = original_provider
|
139
|
+
end
|
140
|
+
#=> true
|
141
|
+
|
142
|
+
## Operation guards prevent fiber issues before they occur
|
143
|
+
# FiberConnectionHandler blocks transactions before fiber interference
|
144
|
+
begin
|
145
|
+
# Set strict mode to ensure OperationModeError is raised
|
146
|
+
original_mode = Familia.transaction_mode
|
147
|
+
Familia.configure { |config| config.transaction_mode = :strict }
|
148
|
+
|
149
|
+
Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
|
150
|
+
Fiber[:familia_connection_handler_class] = Familia::Connection::FiberConnectionHandler
|
151
|
+
|
152
|
+
customer = Customer.new
|
153
|
+
customer.transaction { |conn| conn.set('should_not_execute', 'value') }
|
154
|
+
false
|
155
|
+
rescue Familia::OperationModeError => e
|
156
|
+
# Operation was blocked - no fiber interference possible
|
157
|
+
e.message.include?('FiberConnectionHandler')
|
158
|
+
ensure
|
159
|
+
Fiber[:familia_connection] = nil
|
160
|
+
Fiber[:familia_connection_handler_class] = nil
|
161
|
+
# Restore original mode
|
162
|
+
Familia.configure { |config| config.transaction_mode = original_mode }
|
163
|
+
end
|
164
|
+
#=> true
|
165
|
+
|
166
|
+
## Operation guards prevent pipeline fiber issues before they occur
|
167
|
+
begin
|
168
|
+
# Ensure we're in strict mode for this test
|
169
|
+
Familia.configure { |config| config.pipeline_mode = :strict }
|
170
|
+
|
171
|
+
Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
|
172
|
+
Fiber[:familia_connection_handler_class] = Familia::Connection::FiberConnectionHandler
|
173
|
+
|
174
|
+
customer = Customer.new
|
175
|
+
customer.pipelined { |conn| conn.set('should_not_execute', 'value') }
|
176
|
+
false
|
177
|
+
rescue Familia::OperationModeError => e
|
178
|
+
# Operation was blocked - no fiber interference possible
|
179
|
+
e.message.include?('FiberConnectionHandler')
|
180
|
+
ensure
|
181
|
+
Fiber[:familia_connection] = nil
|
182
|
+
Fiber[:familia_connection_handler_class] = nil
|
183
|
+
end
|
184
|
+
#=> true
|
185
|
+
|
186
|
+
## Method aliases work correctly
|
187
|
+
# pipeline alias for pipelined
|
188
|
+
result1 = Familia.pipeline do |conn|
|
189
|
+
conn.set('alias_test', 'alias_success')
|
190
|
+
conn.get('alias_test')
|
191
|
+
end
|
192
|
+
|
193
|
+
# pipelined method
|
194
|
+
result2 = Familia.pipelined do |conn|
|
195
|
+
conn.set('alias_test2', 'alias_success2')
|
196
|
+
conn.get('alias_test2')
|
197
|
+
end
|
198
|
+
|
199
|
+
result1.results.last == 'alias_success' && result2.results.last == 'alias_success2'
|
200
|
+
#=> true
|
201
|
+
|
202
|
+
## Horreum instance method aliases work correctly
|
203
|
+
customer = Customer.new(custid: 'alias_test')
|
204
|
+
|
205
|
+
# pipeline alias
|
206
|
+
result1 = customer.pipeline do |conn|
|
207
|
+
conn.set('horreum:alias1', 'success1')
|
208
|
+
conn.get('horreum:alias1')
|
209
|
+
end
|
210
|
+
|
211
|
+
# pipelined method
|
212
|
+
result2 = customer.pipelined do |conn|
|
213
|
+
conn.set('horreum:alias2', 'success2')
|
214
|
+
conn.get('horreum:alias2')
|
215
|
+
end
|
216
|
+
|
217
|
+
result1.results.last == 'success1' && result2.results.last == 'success2'
|
218
|
+
#=> true
|
219
|
+
|
220
|
+
## Fiber context remains isolated per operation
|
221
|
+
# Set up initial fiber state
|
222
|
+
initial_connection = Customer.create_dbclient
|
223
|
+
Fiber[:test_marker] = 'initial_state'
|
224
|
+
|
225
|
+
# Transaction should not affect unrelated fiber state
|
226
|
+
Familia.transaction do |conn|
|
227
|
+
Fiber[:test_marker] = 'modified_in_transaction'
|
228
|
+
conn.set('isolation_test', 'success')
|
229
|
+
end
|
230
|
+
|
231
|
+
# Unrelated fiber state should be preserved
|
232
|
+
Fiber[:test_marker] == 'modified_in_transaction'
|
233
|
+
#=> true
|
234
|
+
|
235
|
+
## Pipeline context remains isolated per operation
|
236
|
+
# Reset test marker
|
237
|
+
Fiber[:test_marker] = 'initial_state'
|
238
|
+
|
239
|
+
# Pipeline should not affect unrelated fiber state
|
240
|
+
Familia.pipelined do |conn|
|
241
|
+
Fiber[:test_marker] = 'modified_in_pipeline'
|
242
|
+
conn.set('isolation_test2', 'success')
|
243
|
+
end
|
244
|
+
|
245
|
+
# Unrelated fiber state should be preserved
|
246
|
+
Fiber[:test_marker] == 'modified_in_pipeline'
|
247
|
+
#=> true
|
248
|
+
|
249
|
+
# Cleanup
|
250
|
+
Fiber[:test_marker] = nil
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Handler Constraint Methods Tryouts
|
2
|
+
#
|
3
|
+
# Tests that each connection handler class correctly defines its operation
|
4
|
+
# constraints through class methods. These constraints determine what Redis
|
5
|
+
# operations are safe for each connection type:
|
6
|
+
#
|
7
|
+
# - Single connections (middleware/cached) → unsafe for multi-mode operations
|
8
|
+
# - Fresh connections (provider/create) → safe for all operations
|
9
|
+
# - Transaction connections → safe for reentrant transactions only
|
10
|
+
|
11
|
+
require_relative '../helpers/test_helpers'
|
12
|
+
|
13
|
+
## FiberTransactionHandler constraints
|
14
|
+
Familia::Connection::FiberTransactionHandler.allows_transaction
|
15
|
+
#=> :reentrant
|
16
|
+
|
17
|
+
## FiberTransactionHandler blocks pipelines
|
18
|
+
Familia::Connection::FiberTransactionHandler.allows_pipelined
|
19
|
+
#=> false
|
20
|
+
|
21
|
+
## FiberConnectionHandler blocks transactions
|
22
|
+
Familia::Connection::FiberConnectionHandler.allows_transaction
|
23
|
+
#=> false
|
24
|
+
|
25
|
+
## FiberConnectionHandler blocks pipelines
|
26
|
+
Familia::Connection::FiberConnectionHandler.allows_pipelined
|
27
|
+
#=> false
|
28
|
+
|
29
|
+
## CachedConnectionHandler blocks transactions
|
30
|
+
Familia::Connection::CachedConnectionHandler.allows_transaction
|
31
|
+
#=> false
|
32
|
+
|
33
|
+
## CachedConnectionHandler blocks pipelines
|
34
|
+
Familia::Connection::CachedConnectionHandler.allows_pipelined
|
35
|
+
#=> false
|
36
|
+
|
37
|
+
## ProviderConnectionHandler allows transactions
|
38
|
+
Familia::Connection::ProviderConnectionHandler.allows_transaction
|
39
|
+
#=> true
|
40
|
+
|
41
|
+
## ProviderConnectionHandler allows pipelines
|
42
|
+
Familia::Connection::ProviderConnectionHandler.allows_pipelined
|
43
|
+
#=> true
|
44
|
+
|
45
|
+
## CreateConnectionHandler allows transactions
|
46
|
+
Familia::Connection::CreateConnectionHandler.allows_transaction
|
47
|
+
#=> true
|
48
|
+
|
49
|
+
## CreateConnectionHandler allows pipelines
|
50
|
+
Familia::Connection::CreateConnectionHandler.allows_pipelined
|
51
|
+
#=> true
|
52
|
+
|
53
|
+
## BaseConnectionHandler defaults to allow all
|
54
|
+
Familia::Connection::BaseConnectionHandler.allows_transaction
|
55
|
+
#=> true
|
56
|
+
|
57
|
+
## BaseConnectionHandler defaults to allow all pipelines
|
58
|
+
Familia::Connection::BaseConnectionHandler.allows_pipelined
|
59
|
+
#=> true
|
@@ -0,0 +1,208 @@
|
|
1
|
+
# Operation Mode Guards Tryouts
|
2
|
+
#
|
3
|
+
# Tests the connection handler operation mode enforcement that prevents Redis
|
4
|
+
# mode confusion bugs. Each handler type has specific operation constraints:
|
5
|
+
#
|
6
|
+
# - FiberTransactionHandler: Allow reentrant transactions, block pipelines
|
7
|
+
# - FiberConnectionHandler: Block all multi-mode operations (middleware single conn)
|
8
|
+
# - CachedConnectionHandler: Block all multi-mode operations (cached single conn)
|
9
|
+
# - ProviderConnectionHandler: Allow all operations (fresh checkout each time)
|
10
|
+
# - CreateConnectionHandler: Allow all operations (new connection each time)
|
11
|
+
#
|
12
|
+
# This prevents bugs where middleware/cached connections return "QUEUED" instead
|
13
|
+
# of actual values, breaking conditional logic and business rules.
|
14
|
+
|
15
|
+
require_relative '../helpers/test_helpers'
|
16
|
+
|
17
|
+
## FiberConnectionHandler blocks transactions in strict mode
|
18
|
+
begin
|
19
|
+
# Ensure we're in strict mode for this test
|
20
|
+
Familia.configure { |config| config.transaction_mode = :strict }
|
21
|
+
|
22
|
+
# Simulate middleware connection
|
23
|
+
Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
|
24
|
+
Fiber[:familia_connection_handler_class] = Familia::Connection::FiberConnectionHandler
|
25
|
+
customer = Customer.new
|
26
|
+
customer.transaction { |conn| conn.set('test', 'value') }
|
27
|
+
false
|
28
|
+
rescue Familia::OperationModeError
|
29
|
+
true
|
30
|
+
ensure
|
31
|
+
Fiber[:familia_connection] = nil
|
32
|
+
Fiber[:familia_connection_handler_class] = nil
|
33
|
+
end
|
34
|
+
#=> true
|
35
|
+
|
36
|
+
## FiberConnectionHandler blocks pipelines
|
37
|
+
begin
|
38
|
+
# Ensure we're in strict mode for this test
|
39
|
+
Familia.configure { |config| config.pipeline_mode = :strict }
|
40
|
+
|
41
|
+
# Simulate middleware connection
|
42
|
+
Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
|
43
|
+
Fiber[:familia_connection_handler_class] = Familia::Connection::FiberConnectionHandler
|
44
|
+
customer = Customer.new
|
45
|
+
customer.pipelined { |conn| conn.set('test', 'value') }
|
46
|
+
false
|
47
|
+
rescue Familia::OperationModeError
|
48
|
+
true
|
49
|
+
ensure
|
50
|
+
Fiber[:familia_connection] = nil
|
51
|
+
Fiber[:familia_connection_handler_class] = nil
|
52
|
+
end
|
53
|
+
#=> true
|
54
|
+
|
55
|
+
## CreateConnectionHandler allows transactions
|
56
|
+
begin
|
57
|
+
customer = Customer.new
|
58
|
+
customer.custid = 'test_tx'
|
59
|
+
result = customer.transaction { |conn| conn.set('tx_test', 'success') }
|
60
|
+
true
|
61
|
+
rescue Familia::OperationModeError
|
62
|
+
false
|
63
|
+
end
|
64
|
+
#=> true
|
65
|
+
|
66
|
+
## CreateConnectionHandler allows pipelines
|
67
|
+
begin
|
68
|
+
customer = Customer.new
|
69
|
+
customer.custid = 'test_pipe'
|
70
|
+
result = customer.dbclient.pipelined { |conn| conn.set('pipe_test', 'success') }
|
71
|
+
true
|
72
|
+
rescue Familia::OperationModeError
|
73
|
+
false
|
74
|
+
end
|
75
|
+
#=> true
|
76
|
+
|
77
|
+
## ProviderConnectionHandler allows all operations
|
78
|
+
# Set up connection provider
|
79
|
+
original_provider = Familia.connection_provider
|
80
|
+
Familia.connection_provider = ->(uri) { Redis.new(url: uri) }
|
81
|
+
|
82
|
+
begin
|
83
|
+
customer = Customer.new
|
84
|
+
customer.custid = 'test_provider'
|
85
|
+
tx_result = customer.transaction { |conn| conn.set('provider_tx', 'success') }
|
86
|
+
pipe_result = customer.dbclient.pipelined { |conn| conn.set('provider_pipe', 'success') }
|
87
|
+
true
|
88
|
+
rescue Familia::OperationModeError
|
89
|
+
false
|
90
|
+
ensure
|
91
|
+
Familia.connection_provider = original_provider
|
92
|
+
end
|
93
|
+
#=> true
|
94
|
+
|
95
|
+
## Handler class tracking works correctly
|
96
|
+
customer = Customer.new
|
97
|
+
customer.dbclient # Trigger connection
|
98
|
+
Fiber[:familia_connection_handler_class]
|
99
|
+
#=> Familia::Connection::CreateConnectionHandler
|
100
|
+
|
101
|
+
## Error messages are descriptive
|
102
|
+
begin
|
103
|
+
Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
|
104
|
+
Fiber[:familia_connection_handler_class] = Familia::Connection::FiberConnectionHandler
|
105
|
+
customer = Customer.new
|
106
|
+
customer.transaction { }
|
107
|
+
rescue Familia::OperationModeError => e
|
108
|
+
e.message.include?('FiberConnectionHandler') && e.message.include?('connection pools')
|
109
|
+
ensure
|
110
|
+
Fiber[:familia_connection] = nil
|
111
|
+
Fiber[:familia_connection_handler_class] = nil
|
112
|
+
end
|
113
|
+
#=> true
|
114
|
+
|
115
|
+
# Transaction Mode Backward Compatibility Tests
|
116
|
+
# Ensure new configurable transaction modes don't break existing behavior
|
117
|
+
|
118
|
+
# Store original transaction mode
|
119
|
+
@original_transaction_mode = Familia.transaction_mode
|
120
|
+
|
121
|
+
## Transaction mode reflects current setting
|
122
|
+
# Note: May be :warn (default) or :strict (if changed by previous tests)
|
123
|
+
[:warn, :strict].include?(Familia.transaction_mode)
|
124
|
+
#=> true
|
125
|
+
|
126
|
+
## CachedConnectionHandler still blocks transactions in strict mode
|
127
|
+
begin
|
128
|
+
# Ensure we're in strict mode
|
129
|
+
Familia.configure { |config| config.transaction_mode = :strict }
|
130
|
+
|
131
|
+
# Force CachedConnectionHandler
|
132
|
+
Customer.instance_variable_set(:@dbclient, Familia.create_dbclient)
|
133
|
+
|
134
|
+
customer = Customer.new
|
135
|
+
customer.custid = 'strict_compat_test'
|
136
|
+
customer.transaction { |conn| conn.set('should_fail', 'value') }
|
137
|
+
false # Should not reach here
|
138
|
+
rescue Familia::OperationModeError => e
|
139
|
+
e.message.include?('CachedConnectionHandler')
|
140
|
+
ensure
|
141
|
+
Customer.remove_instance_variable(:@dbclient)
|
142
|
+
end
|
143
|
+
#=> true
|
144
|
+
|
145
|
+
## Normal transactions still work exactly as before
|
146
|
+
begin
|
147
|
+
customer = Customer.new
|
148
|
+
customer.custid = 'normal_compat_test'
|
149
|
+
|
150
|
+
result = customer.transaction do |conn|
|
151
|
+
conn.set('compat_test', 'success')
|
152
|
+
conn.get('compat_test')
|
153
|
+
end
|
154
|
+
|
155
|
+
# Should return MultiResult as before
|
156
|
+
result.is_a?(MultiResult) && result.results.last == 'success'
|
157
|
+
end
|
158
|
+
#=> true
|
159
|
+
|
160
|
+
## Pipeline operations maintain existing behavior
|
161
|
+
begin
|
162
|
+
customer = Customer.new
|
163
|
+
customer.custid = 'pipeline_compat_test'
|
164
|
+
|
165
|
+
result = customer.pipelined do |conn|
|
166
|
+
conn.set('pipe_compat', 'pipeline_success')
|
167
|
+
conn.get('pipe_compat')
|
168
|
+
end
|
169
|
+
|
170
|
+
result.is_a?(MultiResult) && result.results.last == 'pipeline_success'
|
171
|
+
end
|
172
|
+
#=> true
|
173
|
+
|
174
|
+
## Connection handler capabilities unchanged for CreateConnectionHandler allows_transaction
|
175
|
+
Familia::Connection::CreateConnectionHandler.allows_transaction
|
176
|
+
#=> true
|
177
|
+
|
178
|
+
## Connection handler capabilities unchanged for CreateConnectionHandler allows_pipelined
|
179
|
+
Familia::Connection::CreateConnectionHandler.allows_pipelined
|
180
|
+
#=> true
|
181
|
+
|
182
|
+
## Connection handler capabilities unchanged for CachedConnectionHandler allows_transaction
|
183
|
+
Familia::Connection::CachedConnectionHandler.allows_transaction
|
184
|
+
#=> false
|
185
|
+
|
186
|
+
## Connection handler capabilities unchanged for CachedConnectionHandler allows_pipelined
|
187
|
+
Familia::Connection::CachedConnectionHandler.allows_pipelined
|
188
|
+
#=> false
|
189
|
+
|
190
|
+
## Transaction modes don't affect normal connection resolution
|
191
|
+
begin
|
192
|
+
# Change to permissive mode
|
193
|
+
Familia.configure { |config| config.transaction_mode = :permissive }
|
194
|
+
|
195
|
+
# Normal operations should still work the same way
|
196
|
+
customer = Customer.new
|
197
|
+
customer.custid = 'resolution_test'
|
198
|
+
|
199
|
+
# Should still use CreateConnectionHandler for normal transactions
|
200
|
+
conn = customer.dbclient
|
201
|
+
Fiber[:familia_connection_handler_class] == Familia::Connection::CreateConnectionHandler
|
202
|
+
ensure
|
203
|
+
Familia.configure { |config| config.transaction_mode = :strict }
|
204
|
+
end
|
205
|
+
#=> true
|
206
|
+
|
207
|
+
# Restore original transaction mode
|
208
|
+
Familia.configure { |config| config.transaction_mode = @original_transaction_mode }
|