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,72 @@
|
|
1
|
+
# ResponsibilityChain Handler Tracking Tryouts
|
2
|
+
#
|
3
|
+
# Tests that the ResponsibilityChain correctly tracks which handler provided
|
4
|
+
# each connection by setting Fiber[:familia_connection_handler_class]. This enables
|
5
|
+
# operation mode guards to enforce constraints based on connection source.
|
6
|
+
#
|
7
|
+
# The chain should:
|
8
|
+
# - Try handlers in order until one returns a connection
|
9
|
+
# - Set Fiber[:familia_connection_handler_class] to the successful handler's class
|
10
|
+
# - Return the connection from the successful handler
|
11
|
+
# - Return nil if no handler provides a connection
|
12
|
+
|
13
|
+
require_relative '../helpers/test_helpers'
|
14
|
+
|
15
|
+
# Setup - clear any existing fiber state
|
16
|
+
Fiber[:familia_connection_handler_class] = nil
|
17
|
+
Fiber[:familia_connection] = nil
|
18
|
+
|
19
|
+
## Chain tracks CreateConnectionHandler for normal connections
|
20
|
+
customer = Customer.new
|
21
|
+
customer.custid = 'tracking_test'
|
22
|
+
connection = customer.dbclient
|
23
|
+
Fiber[:familia_connection_handler_class]
|
24
|
+
#=> Familia::Connection::CreateConnectionHandler
|
25
|
+
|
26
|
+
## Chain tracks FiberConnectionHandler for middleware connections
|
27
|
+
begin
|
28
|
+
test_conn = Customer.create_dbclient
|
29
|
+
Fiber[:familia_connection] = [test_conn, Familia.middleware_version]
|
30
|
+
|
31
|
+
customer = Customer.new
|
32
|
+
customer.custid = 'fiber_test'
|
33
|
+
connection = customer.dbclient
|
34
|
+
Fiber[:familia_connection_handler_class]
|
35
|
+
ensure
|
36
|
+
Fiber[:familia_connection] = nil
|
37
|
+
Fiber[:familia_connection_handler_class] = nil
|
38
|
+
end
|
39
|
+
#=> Familia::Connection::FiberConnectionHandler
|
40
|
+
|
41
|
+
## Chain tracks ProviderConnectionHandler for connection providers
|
42
|
+
original_provider = Familia.connection_provider
|
43
|
+
begin
|
44
|
+
Familia.connection_provider = ->(uri) { Redis.new(url: uri) }
|
45
|
+
|
46
|
+
customer = Customer.new
|
47
|
+
customer.custid = 'provider_test'
|
48
|
+
connection = customer.dbclient
|
49
|
+
Fiber[:familia_connection_handler_class]
|
50
|
+
ensure
|
51
|
+
Familia.connection_provider = original_provider
|
52
|
+
Fiber[:familia_connection_handler_class] = nil
|
53
|
+
end
|
54
|
+
#=> Familia::Connection::ProviderConnectionHandler
|
55
|
+
|
56
|
+
## Chain returns nil when no handler provides connection
|
57
|
+
# Create a mock chain with handlers that return nil
|
58
|
+
chain = Familia::Connection::ResponsibilityChain.new
|
59
|
+
chain.add_handler(Familia::Connection::FiberTransactionHandler.new)
|
60
|
+
chain.add_handler(Familia::Connection::FiberConnectionHandler.new)
|
61
|
+
|
62
|
+
result = chain.handle('test_uri')
|
63
|
+
result.nil?
|
64
|
+
#=> true
|
65
|
+
|
66
|
+
## Chain sets handler class even when tracking connections
|
67
|
+
customer = Customer.new
|
68
|
+
customer.custid = 'final_test'
|
69
|
+
conn = customer.dbclient
|
70
|
+
handler_class = Fiber[:familia_connection_handler_class]
|
71
|
+
handler_class == Familia::Connection::CreateConnectionHandler && !conn.nil?
|
72
|
+
#=> true
|
@@ -0,0 +1,288 @@
|
|
1
|
+
# Transaction Fallback Integration Tryouts
|
2
|
+
#
|
3
|
+
# Tests real-world scenarios where transaction fallback is needed, particularly
|
4
|
+
# focusing on save operations, relationship updates, and other high-level
|
5
|
+
# operations that internally use transactions.
|
6
|
+
#
|
7
|
+
# These tests verify that the transaction mode configuration works seamlessly
|
8
|
+
# with Familia's existing features when connection handlers don't support
|
9
|
+
# transactions (e.g., cached connections, middleware connections).
|
10
|
+
|
11
|
+
require_relative '../helpers/test_helpers'
|
12
|
+
|
13
|
+
# Setup - store original values
|
14
|
+
$original_transaction_mode = Familia.transaction_mode
|
15
|
+
$original_provider = Familia.connection_provider
|
16
|
+
|
17
|
+
# Create test classes for integration testing
|
18
|
+
class IntegrationTestUser < Familia::Horreum
|
19
|
+
identifier_field :user_id
|
20
|
+
field :user_id
|
21
|
+
field :name
|
22
|
+
field :email
|
23
|
+
field :status
|
24
|
+
list :activity_log
|
25
|
+
set :tags
|
26
|
+
zset :scores
|
27
|
+
end
|
28
|
+
|
29
|
+
class IntegrationTestSession < Familia::Horreum
|
30
|
+
identifier_field :session_id
|
31
|
+
field :session_id
|
32
|
+
field :user_id
|
33
|
+
field :created_at
|
34
|
+
field :expires_at
|
35
|
+
end
|
36
|
+
|
37
|
+
## Save operation works with transaction fallback in warn mode
|
38
|
+
begin
|
39
|
+
Familia.configure { |config| config.transaction_mode = :warn }
|
40
|
+
|
41
|
+
# Force CachedConnectionHandler
|
42
|
+
IntegrationTestUser.instance_variable_set(:@dbclient, Familia.create_dbclient)
|
43
|
+
|
44
|
+
user = IntegrationTestUser.new(
|
45
|
+
user_id: 'save_test_001',
|
46
|
+
name: 'Test User',
|
47
|
+
email: 'test@example.com',
|
48
|
+
status: 'active'
|
49
|
+
)
|
50
|
+
|
51
|
+
# Save internally uses transactions for atomicity
|
52
|
+
result = user.save
|
53
|
+
|
54
|
+
# Should complete successfully using individual commands
|
55
|
+
result && user.exists?
|
56
|
+
ensure
|
57
|
+
IntegrationTestUser.remove_instance_variable(:@dbclient)
|
58
|
+
Familia.configure { |config| config.transaction_mode = :strict }
|
59
|
+
end
|
60
|
+
#=> true
|
61
|
+
|
62
|
+
## Save operation works with transaction fallback in permissive mode
|
63
|
+
begin
|
64
|
+
Familia.configure { |config| config.transaction_mode = :permissive }
|
65
|
+
|
66
|
+
# Force CachedConnectionHandler
|
67
|
+
IntegrationTestUser.instance_variable_set(:@dbclient, Familia.create_dbclient)
|
68
|
+
|
69
|
+
user = IntegrationTestUser.new(
|
70
|
+
user_id: 'save_test_002',
|
71
|
+
name: 'Permissive User',
|
72
|
+
email: 'permissive@example.com'
|
73
|
+
)
|
74
|
+
|
75
|
+
# Should save silently using individual commands
|
76
|
+
result = user.save
|
77
|
+
|
78
|
+
result && user.exists?
|
79
|
+
ensure
|
80
|
+
IntegrationTestUser.remove_instance_variable(:@dbclient)
|
81
|
+
Familia.configure { |config| config.transaction_mode = :strict }
|
82
|
+
end
|
83
|
+
#=> true
|
84
|
+
|
85
|
+
## Multiple field updates work with fallback
|
86
|
+
begin
|
87
|
+
Familia.configure { |config| config.transaction_mode = :permissive }
|
88
|
+
IntegrationTestUser.instance_variable_set(:@dbclient, Familia.create_dbclient)
|
89
|
+
|
90
|
+
user = IntegrationTestUser.new(user_id: 'update_test_001')
|
91
|
+
user.save
|
92
|
+
|
93
|
+
# Update multiple fields - should use individual commands
|
94
|
+
user.name = 'Updated Name'
|
95
|
+
user.email = 'updated@example.com'
|
96
|
+
user.status = 'updated'
|
97
|
+
|
98
|
+
result = user.save
|
99
|
+
|
100
|
+
# Verify all fields were updated
|
101
|
+
reloaded_user = IntegrationTestUser.load('update_test_001')
|
102
|
+
result &&
|
103
|
+
reloaded_user.name == 'Updated Name' &&
|
104
|
+
reloaded_user.email == 'updated@example.com' &&
|
105
|
+
reloaded_user.status == 'updated'
|
106
|
+
ensure
|
107
|
+
IntegrationTestUser.remove_instance_variable(:@dbclient)
|
108
|
+
Familia.configure { |config| config.transaction_mode = :strict }
|
109
|
+
end
|
110
|
+
#=> true
|
111
|
+
|
112
|
+
## Data type operations work with transaction fallback
|
113
|
+
|
114
|
+
Familia.configure { |config| config.transaction_mode = :permissive }
|
115
|
+
IntegrationTestUser.instance_variable_set(:@dbclient, Familia.create_dbclient)
|
116
|
+
|
117
|
+
user = IntegrationTestUser.new(user_id: 'datatype_test_001')
|
118
|
+
user.transaction { |conn| conn.set('test', 'value') }
|
119
|
+
|
120
|
+
# Test list operations
|
121
|
+
user.activity_log.add('user_created')
|
122
|
+
user.activity_log.add('profile_updated')
|
123
|
+
|
124
|
+
# Test set operations
|
125
|
+
user.tags.add('premium')
|
126
|
+
user.tags.add('verified')
|
127
|
+
|
128
|
+
# Test sorted set operations
|
129
|
+
user.scores.add('game_score', 100)
|
130
|
+
user.scores.add('quiz_score', 85)
|
131
|
+
|
132
|
+
# Verify all operations worked
|
133
|
+
results = [
|
134
|
+
user.activity_log.size,
|
135
|
+
user.tags.include?('premium'),
|
136
|
+
user.scores.score('game_score')
|
137
|
+
]
|
138
|
+
|
139
|
+
user.destroy!
|
140
|
+
|
141
|
+
# Clean up test data
|
142
|
+
IntegrationTestUser.remove_instance_variable(:@dbclient)
|
143
|
+
Familia.configure { |config| config.transaction_mode = :strict }
|
144
|
+
|
145
|
+
results
|
146
|
+
#=> [2, true, 100.0]
|
147
|
+
|
148
|
+
## Connection provider integration with transaction modes
|
149
|
+
begin
|
150
|
+
Familia.configure { |config| config.transaction_mode = :warn }
|
151
|
+
|
152
|
+
# Set up connection provider that doesn't support transactions
|
153
|
+
test_connections = {}
|
154
|
+
Familia.connection_provider = lambda do |uri|
|
155
|
+
test_connections[uri] ||= Familia.create_dbclient(uri)
|
156
|
+
end
|
157
|
+
|
158
|
+
user = IntegrationTestUser.new(
|
159
|
+
user_id: 'provider_test_001',
|
160
|
+
name: 'Provider Test User'
|
161
|
+
)
|
162
|
+
|
163
|
+
# Should work with connection provider
|
164
|
+
result = user.save
|
165
|
+
|
166
|
+
result && user.exists?
|
167
|
+
ensure
|
168
|
+
Familia.connection_provider = $original_provider
|
169
|
+
Familia.configure { |config| config.transaction_mode = :strict }
|
170
|
+
end
|
171
|
+
#=> true
|
172
|
+
|
173
|
+
## Mixed connection handlers in same operation
|
174
|
+
begin
|
175
|
+
Familia.configure { |config| config.transaction_mode = :permissive }
|
176
|
+
|
177
|
+
# User class with cached connection (fallback mode)
|
178
|
+
IntegrationTestUser.instance_variable_set(:@dbclient, Familia.create_dbclient)
|
179
|
+
|
180
|
+
# Session class without cached connection (normal mode)
|
181
|
+
user = IntegrationTestUser.new(user_id: 'mixed_test_001')
|
182
|
+
session = IntegrationTestSession.new(
|
183
|
+
session_id: 'sess_mixed_001',
|
184
|
+
user_id: 'mixed_test_001',
|
185
|
+
created_at: Time.now.to_i
|
186
|
+
)
|
187
|
+
|
188
|
+
# Both should save successfully despite different connection handlers
|
189
|
+
user_saved = user.save
|
190
|
+
session_saved = session.save
|
191
|
+
|
192
|
+
user_saved && session_saved && user.exists? && session.exists?
|
193
|
+
ensure
|
194
|
+
IntegrationTestUser.remove_instance_variable(:@dbclient)
|
195
|
+
Familia.configure { |config| config.transaction_mode = :strict }
|
196
|
+
end
|
197
|
+
#=> true
|
198
|
+
|
199
|
+
## Transaction mode changes during runtime
|
200
|
+
begin
|
201
|
+
IntegrationTestUser.instance_variable_set(:@dbclient, Familia.create_dbclient)
|
202
|
+
|
203
|
+
user = IntegrationTestUser.new(user_id: 'runtime_test_001')
|
204
|
+
|
205
|
+
# Start in strict mode - should fail
|
206
|
+
Familia.configure { |config| config.transaction_mode = :strict }
|
207
|
+
strict_failed = false
|
208
|
+
begin
|
209
|
+
user.transaction { |conn| conn.set('test_key', 'test_value') }
|
210
|
+
rescue Familia::OperationModeError
|
211
|
+
strict_failed = true
|
212
|
+
end
|
213
|
+
|
214
|
+
# Switch to permissive mode - should work
|
215
|
+
Familia.configure { |config| config.transaction_mode = :permissive }
|
216
|
+
result = user.transaction { |conn| conn.set('test_key', 'test_value') }
|
217
|
+
permissive_worked = result.successful?
|
218
|
+
|
219
|
+
strict_failed && permissive_worked
|
220
|
+
ensure
|
221
|
+
IntegrationTestUser.remove_instance_variable(:@dbclient)
|
222
|
+
Familia.configure { |config| config.transaction_mode = :strict }
|
223
|
+
end
|
224
|
+
#=> true
|
225
|
+
|
226
|
+
## Large batch operations with transaction fallback
|
227
|
+
begin
|
228
|
+
Familia.configure { |config| config.transaction_mode = :permissive }
|
229
|
+
IntegrationTestUser.instance_variable_set(:@dbclient, Familia.create_dbclient)
|
230
|
+
|
231
|
+
user = IntegrationTestUser.new(user_id: 'batch_test_001')
|
232
|
+
|
233
|
+
# Simulate a large batch operation that would normally use transactions
|
234
|
+
result = user.transaction do |conn|
|
235
|
+
# Add multiple activity log entries
|
236
|
+
(1..10).each do |i|
|
237
|
+
conn.rpush(user.activity_log.dbkey, "activity_#{i}")
|
238
|
+
end
|
239
|
+
|
240
|
+
# Add multiple tags
|
241
|
+
%w[tag1 tag2 tag3 tag4 tag5].each do |tag|
|
242
|
+
conn.sadd(user.tags.dbkey, tag)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Add user fields
|
246
|
+
conn.hset(user.dbkey, 'batch_processed', 'true')
|
247
|
+
end
|
248
|
+
|
249
|
+
# Should complete successfully and return MultiResult
|
250
|
+
result.is_a?(MultiResult) && result.successful?
|
251
|
+
ensure
|
252
|
+
IntegrationTestUser.remove_instance_variable(:@dbclient)
|
253
|
+
Familia.configure { |config| config.transaction_mode = :strict }
|
254
|
+
end
|
255
|
+
#=> true
|
256
|
+
|
257
|
+
## Error handling in individual command mode
|
258
|
+
begin
|
259
|
+
Familia.configure { |config| config.transaction_mode = :permissive }
|
260
|
+
IntegrationTestUser.instance_variable_set(:@dbclient, Familia.create_dbclient)
|
261
|
+
|
262
|
+
user = IntegrationTestUser.new(user_id: 'error_test_001')
|
263
|
+
|
264
|
+
# Execute commands where some might fail
|
265
|
+
result = user.transaction do |conn|
|
266
|
+
conn.hset(user.dbkey, 'field1', 'value1') # Should succeed
|
267
|
+
|
268
|
+
# This might fail but shouldn't stop other commands
|
269
|
+
begin
|
270
|
+
conn.incr('non_numeric_key') # Might fail if key exists as string
|
271
|
+
rescue => e
|
272
|
+
# Individual commands can handle their own errors
|
273
|
+
end
|
274
|
+
|
275
|
+
conn.hset(user.dbkey, 'field2', 'value2') # Should succeed
|
276
|
+
end
|
277
|
+
|
278
|
+
# Should return MultiResult even with mixed success/failure
|
279
|
+
result.is_a?(MultiResult)
|
280
|
+
ensure
|
281
|
+
IntegrationTestUser.remove_instance_variable(:@dbclient)
|
282
|
+
Familia.configure { |config| config.transaction_mode = :strict }
|
283
|
+
end
|
284
|
+
#=> true
|
285
|
+
|
286
|
+
# Cleanup - restore original settings
|
287
|
+
Familia.configure { |config| config.transaction_mode = $original_transaction_mode }
|
288
|
+
Familia.connection_provider = $original_provider
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# Transaction Mode: Permissive Tryouts
|
2
|
+
#
|
3
|
+
# Tests permissive transaction mode behavior where operations silently
|
4
|
+
# execute commands individually when transactions are unavailable.
|
5
|
+
#
|
6
|
+
# Permissive mode: Silently uses IndividualCommandProxy for fallback
|
7
|
+
|
8
|
+
require_relative '../helpers/test_helpers'
|
9
|
+
|
10
|
+
# Test class for permissive mode testing
|
11
|
+
class PermissiveModeTestCustomer < Familia::Horreum
|
12
|
+
identifier_field :custid
|
13
|
+
field :custid
|
14
|
+
field :name
|
15
|
+
field :status
|
16
|
+
end
|
17
|
+
|
18
|
+
## Permissive mode can be configured
|
19
|
+
Familia.configure { |config| config.transaction_mode = :permissive }
|
20
|
+
Familia.transaction_mode
|
21
|
+
#=> :permissive
|
22
|
+
|
23
|
+
## Permissive mode silently executes individual commands with CachedConnectionHandler
|
24
|
+
begin
|
25
|
+
# Force CachedConnectionHandler
|
26
|
+
PermissiveModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
|
27
|
+
|
28
|
+
customer = PermissiveModeTestCustomer.new(custid: 'permissive_test')
|
29
|
+
result = customer.transaction do |conn|
|
30
|
+
# Should be IndividualCommandProxy
|
31
|
+
conn.class == Familia::Connection::IndividualCommandProxy &&
|
32
|
+
conn.hset(customer.dbkey, 'name', 'Permissive Mode Works') &&
|
33
|
+
conn.hget(customer.dbkey, 'name')
|
34
|
+
end
|
35
|
+
|
36
|
+
result.is_a?(MultiResult) && result.results.last == 'Permissive Mode Works'
|
37
|
+
ensure
|
38
|
+
PermissiveModeTestCustomer.remove_instance_variable(:@dbclient)
|
39
|
+
end
|
40
|
+
#=> true
|
41
|
+
|
42
|
+
## Permissive mode works silently with FiberConnectionHandler
|
43
|
+
begin
|
44
|
+
# Simulate middleware connection
|
45
|
+
Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
|
46
|
+
Fiber[:familia_connection_handler_class] = Familia::Connection::FiberConnectionHandler
|
47
|
+
customer = PermissiveModeTestCustomer.new(custid: 'fiber_permissive_test')
|
48
|
+
|
49
|
+
result = customer.transaction do |conn|
|
50
|
+
conn.class == Familia::Connection::IndividualCommandProxy &&
|
51
|
+
conn.hset(customer.dbkey, 'source', 'fiber_permissive') &&
|
52
|
+
conn.hget(customer.dbkey, 'source')
|
53
|
+
end
|
54
|
+
|
55
|
+
result.is_a?(MultiResult) && result.results.last == 'fiber_permissive'
|
56
|
+
ensure
|
57
|
+
Fiber[:familia_connection] = nil
|
58
|
+
Fiber[:familia_connection_handler_class] = nil
|
59
|
+
end
|
60
|
+
#=> true
|
61
|
+
|
62
|
+
## Permissive mode still uses normal transactions when available
|
63
|
+
begin
|
64
|
+
customer = PermissiveModeTestCustomer.new(custid: 'normal_permissive_test')
|
65
|
+
result = customer.transaction do |conn|
|
66
|
+
# Should be Redis::MultiConnection for normal transactions
|
67
|
+
conn.class == Redis::MultiConnection &&
|
68
|
+
conn.hset(customer.dbkey, 'type', 'normal in permissive mode') &&
|
69
|
+
conn.hget(customer.dbkey, 'type')
|
70
|
+
end
|
71
|
+
result.is_a?(MultiResult) && result.results.last == 'normal in permissive mode'
|
72
|
+
end
|
73
|
+
#=> true
|
74
|
+
|
75
|
+
## Permissive mode handles complex operations silently
|
76
|
+
begin
|
77
|
+
PermissiveModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
|
78
|
+
|
79
|
+
customer = PermissiveModeTestCustomer.new(custid: 'complex_permissive_test')
|
80
|
+
result = customer.transaction do |conn|
|
81
|
+
# Multiple operations that would normally be atomic
|
82
|
+
conn.hset(customer.dbkey, 'status', 'processing')
|
83
|
+
conn.hset(customer.dbkey, 'updated_at', Time.now.to_i)
|
84
|
+
conn.hset(customer.dbkey, 'version', '1.0')
|
85
|
+
conn.hget(customer.dbkey, 'status')
|
86
|
+
end
|
87
|
+
|
88
|
+
result.is_a?(MultiResult) && result.results.last == 'processing'
|
89
|
+
ensure
|
90
|
+
PermissiveModeTestCustomer.remove_instance_variable(:@dbclient)
|
91
|
+
end
|
92
|
+
#=> true
|
93
|
+
|
94
|
+
## Permissive mode works with nested calls
|
95
|
+
begin
|
96
|
+
PermissiveModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
|
97
|
+
|
98
|
+
customer = PermissiveModeTestCustomer.new(custid: 'nested_permissive_test')
|
99
|
+
|
100
|
+
outer_result = customer.transaction do |outer_conn|
|
101
|
+
outer_conn.hset(customer.dbkey, 'outer', 'value')
|
102
|
+
|
103
|
+
inner_result = customer.transaction do |inner_conn|
|
104
|
+
inner_conn.hset(customer.dbkey, 'inner', 'nested_value')
|
105
|
+
end
|
106
|
+
|
107
|
+
# Both should return MultiResult
|
108
|
+
inner_result.is_a?(MultiResult)
|
109
|
+
end
|
110
|
+
|
111
|
+
outer_result.is_a?(MultiResult)
|
112
|
+
ensure
|
113
|
+
PermissiveModeTestCustomer.remove_instance_variable(:@dbclient)
|
114
|
+
end
|
115
|
+
#=> true
|
116
|
+
|
117
|
+
## Batch operations work silently in permissive mode
|
118
|
+
begin
|
119
|
+
PermissiveModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
|
120
|
+
|
121
|
+
customer = PermissiveModeTestCustomer.new(custid: 'batch_permissive_test')
|
122
|
+
|
123
|
+
# Large batch that would normally be atomic
|
124
|
+
result = customer.transaction do |conn|
|
125
|
+
10.times do |i|
|
126
|
+
conn.hset(customer.dbkey, "field_#{i}", "value_#{i}")
|
127
|
+
end
|
128
|
+
conn.hget(customer.dbkey, 'field_9')
|
129
|
+
end
|
130
|
+
|
131
|
+
result.is_a?(MultiResult) && result.results.last == 'value_9'
|
132
|
+
ensure
|
133
|
+
PermissiveModeTestCustomer.remove_instance_variable(:@dbclient)
|
134
|
+
end
|
135
|
+
#=> true
|
136
|
+
|
137
|
+
## Save operations work in permissive mode
|
138
|
+
begin
|
139
|
+
PermissiveModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
|
140
|
+
|
141
|
+
customer = PermissiveModeTestCustomer.new(
|
142
|
+
custid: 'save_permissive_test',
|
143
|
+
name: 'Permissive Save User',
|
144
|
+
status: 'active'
|
145
|
+
)
|
146
|
+
|
147
|
+
# Should save successfully using individual commands
|
148
|
+
save_result = customer.save
|
149
|
+
save_result && customer.exists?
|
150
|
+
ensure
|
151
|
+
PermissiveModeTestCustomer.remove_instance_variable(:@dbclient)
|
152
|
+
end
|
153
|
+
#=> true
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# Transaction Mode: Strict Tryouts
|
2
|
+
#
|
3
|
+
# Tests strict transaction mode behavior where operations fail fast
|
4
|
+
# when connection handlers don't support transactions.
|
5
|
+
#
|
6
|
+
# Strict mode: Raises OperationModeError when transaction unavailable
|
7
|
+
|
8
|
+
require_relative '../helpers/test_helpers'
|
9
|
+
|
10
|
+
# Test class for strict mode testing
|
11
|
+
class StrictModeTestCustomer < Familia::Horreum
|
12
|
+
identifier_field :custid
|
13
|
+
field :custid
|
14
|
+
field :name
|
15
|
+
end
|
16
|
+
|
17
|
+
## Strict mode can be configured
|
18
|
+
Familia.configure { |config| config.transaction_mode = :strict }
|
19
|
+
Familia.transaction_mode
|
20
|
+
#=> :strict
|
21
|
+
|
22
|
+
## Strict mode raises error with CachedConnectionHandler
|
23
|
+
|
24
|
+
# Force CachedConnectionHandler
|
25
|
+
StrictModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
|
26
|
+
|
27
|
+
customer = StrictModeTestCustomer.new(custid: 'strict_test')
|
28
|
+
customer.transaction do |conn|
|
29
|
+
conn.hset(customer.dbkey, 'name', 'Should Not Work')
|
30
|
+
end
|
31
|
+
|
32
|
+
#=:> Familia::OperationModeError
|
33
|
+
#=~> /Cannot start transaction with/
|
34
|
+
#=~> /CachedConnectionHandler/
|
35
|
+
|
36
|
+
## Clear the dbclient instance var
|
37
|
+
StrictModeTestCustomer.remove_instance_variable(:@dbclient)
|
38
|
+
#=*>
|
39
|
+
|
40
|
+
## Strict mode raises error with FiberConnectionHandler
|
41
|
+
begin
|
42
|
+
# Simulate middleware connection
|
43
|
+
Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
|
44
|
+
Fiber[:familia_connection_handler_class] = Familia::Connection::FiberConnectionHandler
|
45
|
+
customer = StrictModeTestCustomer.new(custid: 'fiber_test')
|
46
|
+
customer.transaction { |conn| conn.set('test', 'value') }
|
47
|
+
false
|
48
|
+
rescue Familia::OperationModeError => e
|
49
|
+
e.message.include?('FiberConnectionHandler')
|
50
|
+
ensure
|
51
|
+
Fiber[:familia_connection] = nil
|
52
|
+
Fiber[:familia_connection_handler_class] = nil
|
53
|
+
end
|
54
|
+
#=> true
|
55
|
+
|
56
|
+
## Strict mode allows normal transactions with CreateConnectionHandler
|
57
|
+
begin
|
58
|
+
customer = StrictModeTestCustomer.new(custid: 'normal_test')
|
59
|
+
result = customer.transaction do |conn|
|
60
|
+
conn.hset(customer.dbkey, 'type', 'normal transaction')
|
61
|
+
conn.hget(customer.dbkey, 'type')
|
62
|
+
end
|
63
|
+
result.is_a?(MultiResult) && result.results.last == 'normal transaction'
|
64
|
+
end
|
65
|
+
#=> true
|
66
|
+
|
67
|
+
## Strict mode works with ProviderConnectionHandler
|
68
|
+
original_provider = Familia.connection_provider
|
69
|
+
begin
|
70
|
+
Familia.connection_provider = ->(uri) { Redis.new(url: uri) }
|
71
|
+
customer = StrictModeTestCustomer.new(custid: 'provider_test')
|
72
|
+
result = customer.transaction do |conn|
|
73
|
+
conn.hset(customer.dbkey, 'source', 'provider')
|
74
|
+
conn.hget(customer.dbkey, 'source')
|
75
|
+
end
|
76
|
+
result.is_a?(MultiResult) && result.results.last == 'provider'
|
77
|
+
ensure
|
78
|
+
Familia.connection_provider = original_provider
|
79
|
+
end
|
80
|
+
#=> true
|
81
|
+
|
82
|
+
## Global transactions respect strict mode with cached connections
|
83
|
+
begin
|
84
|
+
# Set a cached connection on the Familia module itself would be complex
|
85
|
+
# Instead test that cached connections on models affect their transactions
|
86
|
+
StrictModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
|
87
|
+
|
88
|
+
customer = StrictModeTestCustomer.new(custid: 'global_strict_test')
|
89
|
+
customer.transaction do |conn|
|
90
|
+
conn.set('should_fail', 'value')
|
91
|
+
end
|
92
|
+
false
|
93
|
+
rescue Familia::OperationModeError
|
94
|
+
true
|
95
|
+
ensure
|
96
|
+
StrictModeTestCustomer.remove_instance_variable(:@dbclient)
|
97
|
+
end
|
98
|
+
#=> true
|