familia 2.0.0.pre18 → 2.0.0.pre21
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/claude-code-review.yml +4 -9
- data/.github/workflows/code-smells.yml +64 -3
- data/.pre-commit-config.yaml +8 -6
- data/.reek.yml +10 -9
- data/.rubocop.yml +4 -0
- data/CHANGELOG.rst +205 -88
- data/CLAUDE.md +62 -10
- data/Gemfile +3 -3
- data/Gemfile.lock +27 -62
- data/README.md +39 -0
- data/bin/try +16 -0
- data/bin/tryouts +16 -0
- data/changelog.d/20251105_flexible_external_identifier_format.rst +66 -0
- data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +44 -0
- data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +20 -0
- data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +91 -0
- data/changelog.d/20251107_optimized_redis_exists_checks.rst +94 -0
- data/changelog.d/20251108_frozen_string_literal_pragma.rst +44 -0
- data/docs/1106-participates_in-bidirectional-solution.md +129 -0
- data/docs/guides/encryption.md +486 -0
- data/docs/guides/feature-encrypted-fields.md +123 -7
- data/docs/guides/feature-expiration.md +177 -133
- data/docs/guides/feature-external-identifiers.md +415 -443
- data/docs/guides/feature-object-identifiers.md +400 -269
- data/docs/guides/feature-quantization.md +120 -6
- data/docs/guides/feature-relationships-indexing.md +318 -0
- data/docs/guides/feature-relationships-methods.md +146 -604
- data/docs/guides/feature-relationships-participation.md +263 -0
- data/docs/guides/feature-relationships.md +118 -136
- data/docs/guides/feature-system-devs.md +176 -693
- data/docs/guides/feature-system.md +119 -6
- data/docs/guides/feature-transient-fields.md +81 -0
- data/docs/guides/field-system.md +778 -0
- data/docs/guides/index.md +32 -15
- data/docs/guides/logging.md +187 -0
- data/docs/guides/optimized-loading.md +674 -0
- data/docs/guides/thread-safety-monitoring.md +61 -0
- data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
- data/docs/migrating/v2.0.0-pre19.md +197 -0
- data/docs/migrating/v2.0.0-pre22.md +241 -0
- data/docs/overview.md +7 -9
- data/docs/reference/api-technical.md +267 -320
- data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
- data/examples/autoloader/mega_customer.rb +2 -0
- data/examples/datatype_standalone.rb +282 -0
- data/examples/encrypted_fields.rb +2 -1
- data/examples/json_usage_patterns.rb +2 -0
- data/examples/relationships.rb +3 -0
- data/examples/safe_dump.rb +2 -1
- data/examples/sampling_demo.rb +53 -0
- data/examples/single_connection_transaction_confusions.rb +2 -1
- data/familia.gemspec +2 -1
- data/lib/familia/base.rb +2 -0
- data/lib/familia/connection/behavior.rb +254 -0
- data/lib/familia/connection/handlers.rb +97 -0
- data/lib/familia/connection/individual_command_proxy.rb +2 -0
- data/lib/familia/connection/middleware.rb +34 -24
- data/lib/familia/connection/operation_core.rb +3 -1
- data/lib/familia/connection/operations.rb +2 -0
- data/lib/familia/connection/{pipeline_core.rb → pipelined_core.rb} +4 -2
- data/lib/familia/connection/transaction_core.rb +75 -9
- data/lib/familia/connection.rb +21 -5
- data/lib/familia/data_type/class_methods.rb +3 -1
- data/lib/familia/data_type/connection.rb +153 -7
- data/lib/familia/data_type/database_commands.rb +9 -4
- data/lib/familia/data_type/serialization.rb +10 -4
- data/lib/familia/data_type/settings.rb +2 -0
- data/lib/familia/data_type/types/counter.rb +2 -0
- data/lib/familia/data_type/types/hashkey.rb +8 -6
- data/lib/familia/data_type/types/listkey.rb +2 -0
- data/lib/familia/data_type/types/lock.rb +2 -0
- data/lib/familia/data_type/types/sorted_set.rb +2 -0
- data/lib/familia/data_type/types/stringkey.rb +2 -0
- data/lib/familia/data_type/types/unsorted_set.rb +2 -0
- data/lib/familia/data_type.rb +2 -0
- data/lib/familia/encryption/encrypted_data.rb +4 -2
- data/lib/familia/encryption/manager.rb +2 -0
- data/lib/familia/encryption/provider.rb +2 -0
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
- data/lib/familia/encryption/registry.rb +2 -0
- data/lib/familia/encryption/request_cache.rb +2 -0
- data/lib/familia/encryption.rb +9 -2
- data/lib/familia/errors.rb +53 -14
- data/lib/familia/features/autoloader.rb +2 -0
- data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
- data/lib/familia/features/encrypted_fields.rb +2 -2
- data/lib/familia/features/expiration/extensions.rb +11 -11
- data/lib/familia/features/expiration.rb +29 -21
- data/lib/familia/features/external_identifier.rb +33 -7
- data/lib/familia/features/object_identifier.rb +2 -0
- data/lib/familia/features/quantization.rb +3 -1
- data/lib/familia/features/relationships/README.md +3 -1
- data/lib/familia/features/relationships/collection_operations.rb +2 -0
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +177 -47
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +203 -63
- data/lib/familia/features/relationships/indexing.rb +40 -42
- data/lib/familia/features/relationships/indexing_relationship.rb +17 -5
- data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
- data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
- data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
- data/lib/familia/features/relationships/participation.rb +155 -69
- data/lib/familia/features/relationships/participation_membership.rb +69 -0
- data/lib/familia/features/relationships/participation_relationship.rb +34 -6
- data/lib/familia/features/relationships/score_encoding.rb +2 -0
- data/lib/familia/features/relationships.rb +5 -3
- data/lib/familia/features/safe_dump.rb +2 -0
- data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
- data/lib/familia/features/transient_fields.rb +2 -0
- data/lib/familia/features.rb +2 -0
- data/lib/familia/field_type.rb +5 -2
- data/lib/familia/horreum/connection.rb +28 -36
- data/lib/familia/horreum/database_commands.rb +131 -10
- data/lib/familia/horreum/definition.rb +18 -7
- data/lib/familia/horreum/management.rb +233 -57
- data/lib/familia/horreum/persistence.rb +314 -122
- data/lib/familia/horreum/related_fields.rb +2 -0
- data/lib/familia/horreum/serialization.rb +26 -4
- data/lib/familia/horreum/settings.rb +2 -0
- data/lib/familia/horreum/utils.rb +2 -8
- data/lib/familia/horreum.rb +46 -13
- data/lib/familia/identifier_extractor.rb +2 -0
- data/lib/familia/instrumentation.rb +156 -0
- data/lib/familia/json_serializer.rb +2 -0
- data/lib/familia/logging.rb +94 -37
- data/lib/familia/refinements/dear_json.rb +2 -0
- data/lib/familia/refinements/stylize_words.rb +2 -14
- data/lib/familia/refinements/time_literals.rb +2 -0
- data/lib/familia/refinements.rb +2 -0
- data/lib/familia/secure_identifier.rb +10 -2
- data/lib/familia/settings.rb +9 -7
- data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
- data/lib/familia/thread_safety/monitor.rb +328 -0
- data/lib/familia/utils.rb +13 -0
- data/lib/familia/verifiable_identifier.rb +3 -1
- data/lib/familia/version.rb +3 -1
- data/lib/familia.rb +31 -4
- data/lib/middleware/database_command_counter.rb +152 -0
- data/lib/middleware/database_logger.rb +325 -129
- data/lib/multi_result.rb +2 -0
- data/try/edge_cases/empty_identifiers_try.rb +2 -0
- data/try/edge_cases/hash_symbolization_try.rb +2 -0
- data/try/edge_cases/json_serialization_try.rb +2 -0
- data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
- data/try/edge_cases/race_conditions_try.rb +4 -0
- data/try/edge_cases/reserved_keywords_try.rb +4 -0
- data/try/edge_cases/string_coercion_try.rb +6 -4
- data/try/edge_cases/ttl_side_effects_try.rb +4 -0
- data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
- data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
- data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
- data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
- data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
- data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
- data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
- data/try/features/encrypted_fields/memory_security_try.rb +4 -0
- data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
- data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
- data/try/features/encryption/config_persistence_try.rb +4 -0
- data/try/features/encryption/core_try.rb +4 -0
- data/try/features/encryption/instance_variable_scope_try.rb +4 -0
- data/try/features/encryption/module_loading_try.rb +4 -0
- data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
- data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
- data/try/features/encryption/roundtrip_validation_try.rb +4 -0
- data/try/features/encryption/secure_memory_handling_try.rb +4 -0
- data/try/features/expiration/expiration_try.rb +5 -1
- data/try/features/external_identifier/external_identifier_try.rb +171 -8
- data/try/features/feature_dependencies_try.rb +2 -0
- data/try/features/feature_improvements_try.rb +2 -0
- data/try/features/field_groups_try.rb +2 -0
- data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
- data/try/features/object_identifier/object_identifier_try.rb +2 -0
- data/try/features/quantization/quantization_try.rb +4 -0
- data/try/features/real_feature_integration_try.rb +2 -0
- data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
- data/try/features/relationships/indexing_rebuild_try.rb +600 -0
- data/try/features/relationships/indexing_try.rb +30 -4
- data/try/features/relationships/participation_bidirectional_try.rb +242 -0
- data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
- data/try/features/relationships/participation_commands_verification_try.rb +2 -0
- data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
- data/try/features/relationships/participation_reverse_index_try.rb +15 -13
- data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
- data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
- data/try/features/relationships/relationships_api_changes_try.rb +6 -4
- data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
- data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
- data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
- data/try/features/relationships/relationships_performance_try.rb +4 -0
- data/try/features/relationships/relationships_performance_working_try.rb +4 -0
- data/try/features/relationships/relationships_try.rb +6 -4
- data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
- data/try/features/safe_dump/safe_dump_try.rb +4 -0
- data/try/features/transient_fields/redacted_string_try.rb +2 -0
- data/try/features/transient_fields/refresh_reset_try.rb +3 -0
- data/try/features/transient_fields/simple_refresh_test.rb +3 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
- data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
- data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
- data/try/integration/connection/fiber_context_preservation_try.rb +7 -3
- data/try/integration/connection/handler_constraints_try.rb +4 -0
- data/try/integration/connection/isolated_dbclient_try.rb +4 -0
- data/try/integration/connection/middleware_reconnect_try.rb +2 -0
- data/try/integration/connection/operation_mode_guards_try.rb +5 -1
- data/try/integration/connection/pipeline_fallback_integration_try.rb +15 -12
- data/try/integration/connection/pools_try.rb +4 -0
- data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
- data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
- data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
- data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
- data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
- data/try/integration/connection/transaction_modes_try.rb +4 -0
- data/try/integration/conventional_inheritance_try.rb +4 -0
- data/try/integration/create_method_try.rb +26 -22
- data/try/integration/cross_component_try.rb +4 -0
- data/try/integration/data_types/datatype_pipelines_try.rb +108 -0
- data/try/integration/data_types/datatype_transactions_try.rb +251 -0
- data/try/integration/database_consistency_try.rb +4 -0
- data/try/integration/familia_extended_try.rb +4 -0
- data/try/integration/familia_members_methods_try.rb +4 -0
- data/try/integration/models/customer_safe_dump_try.rb +9 -1
- data/try/integration/models/customer_try.rb +4 -0
- data/try/integration/models/datatype_base_try.rb +4 -0
- data/try/integration/models/familia_object_try.rb +5 -1
- data/try/integration/persistence_operations_try.rb +166 -10
- data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
- data/try/integration/save_methods_consistency_try.rb +241 -0
- data/try/integration/scenarios_try.rb +4 -0
- data/try/integration/secure_identifier_try.rb +4 -0
- data/try/integration/transaction_safety_core_try.rb +176 -0
- data/try/integration/transaction_safety_workflow_try.rb +291 -0
- data/try/integration/verifiable_identifier_try.rb +4 -0
- data/try/investigation/pipeline_routing/README.md +228 -0
- data/try/performance/benchmarks_try.rb +4 -0
- data/try/performance/transaction_safety_benchmark_try.rb +238 -0
- data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
- data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
- data/try/support/debugging/cache_behavior_tracer.rb +4 -0
- data/try/support/debugging/debug_aad_process.rb +3 -0
- data/try/support/debugging/debug_concealed_internal.rb +3 -0
- data/try/support/debugging/debug_concealed_reveal.rb +3 -0
- data/try/support/debugging/debug_context_aad.rb +3 -0
- data/try/support/debugging/debug_context_simple.rb +3 -0
- data/try/support/debugging/debug_cross_context.rb +3 -0
- data/try/support/debugging/debug_database_load.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
- data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
- data/try/support/debugging/debug_field_decrypt.rb +3 -0
- data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
- data/try/support/debugging/debug_load_path.rb +3 -0
- data/try/support/debugging/debug_method_definition.rb +3 -0
- data/try/support/debugging/debug_method_resolution.rb +3 -0
- data/try/support/debugging/debug_minimal.rb +3 -0
- data/try/support/debugging/debug_provider.rb +3 -0
- data/try/support/debugging/debug_secure_behavior.rb +3 -0
- data/try/support/debugging/debug_string_class.rb +3 -0
- data/try/support/debugging/debug_test.rb +3 -0
- data/try/support/debugging/debug_test_design.rb +3 -0
- data/try/support/debugging/encryption_method_tracer.rb +4 -0
- data/try/support/debugging/provider_diagnostics.rb +4 -0
- data/try/support/helpers/test_cleanup.rb +4 -0
- data/try/support/helpers/test_helpers.rb +5 -0
- data/try/support/memory/memory_basic_test.rb +4 -0
- data/try/support/memory/memory_detailed_test.rb +4 -0
- data/try/support/memory/memory_search_for_string.rb +4 -0
- data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
- data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
- data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
- data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
- data/try/support/prototypes/atomic_saves_v4.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
- data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
- data/try/support/prototypes/pooling/pool_siege.rb +4 -2
- data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
- data/try/thread_safety/README.md +496 -0
- data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
- data/try/thread_safety/connection_chain_race_try.rb +148 -0
- data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
- data/try/thread_safety/feature_registry_race_try.rb +226 -0
- data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
- data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
- data/try/thread_safety/field_registration_race_try.rb +222 -0
- data/try/thread_safety/logger_initialization_race_try.rb +170 -0
- data/try/thread_safety/middleware_registration_race_try.rb +154 -0
- data/try/thread_safety/module_config_race_try.rb +175 -0
- data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
- data/try/unit/core/autoloader_try.rb +4 -0
- data/try/unit/core/base_enhancements_try.rb +4 -0
- data/try/unit/core/connection_try.rb +4 -0
- data/try/unit/core/errors_try.rb +4 -0
- data/try/unit/core/extensions_try.rb +4 -0
- data/try/unit/core/familia_logger_try.rb +2 -0
- data/try/unit/core/familia_try.rb +4 -0
- data/try/unit/core/middleware_sampling_try.rb +335 -0
- data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
- data/try/unit/core/middleware_thread_safety_try.rb +245 -0
- data/try/unit/core/middleware_try.rb +4 -0
- data/try/unit/core/settings_try.rb +4 -0
- data/try/unit/core/time_utils_try.rb +4 -0
- data/try/unit/core/tools_try.rb +4 -0
- data/try/unit/core/utils_try.rb +37 -0
- data/try/unit/data_types/boolean_try.rb +5 -1
- data/try/unit/data_types/counter_try.rb +4 -0
- data/try/unit/data_types/datatype_base_try.rb +4 -0
- data/try/unit/data_types/hash_try.rb +4 -0
- data/try/unit/data_types/list_try.rb +4 -0
- data/try/unit/data_types/lock_try.rb +4 -0
- data/try/unit/data_types/sorted_set_try.rb +4 -0
- data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
- data/try/unit/data_types/string_try.rb +5 -1
- data/try/unit/data_types/unsortedset_try.rb +4 -0
- data/try/unit/familia_resolve_class_try.rb +116 -0
- data/try/unit/horreum/auto_indexing_on_save_try.rb +36 -16
- data/try/unit/horreum/automatic_index_validation_try.rb +255 -0
- data/try/unit/horreum/base_try.rb +5 -1
- data/try/unit/horreum/class_methods_try.rb +6 -2
- data/try/unit/horreum/commands_try.rb +4 -0
- data/try/unit/horreum/defensive_initialization_try.rb +4 -0
- data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +4 -0
- data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
- data/try/unit/horreum/field_categories_try.rb +4 -0
- data/try/unit/horreum/field_definition_try.rb +4 -0
- data/try/unit/horreum/initialization_try.rb +5 -1
- data/try/unit/horreum/json_type_preservation_try.rb +2 -0
- data/try/unit/horreum/optimized_loading_try.rb +156 -0
- data/try/unit/horreum/relations_try.rb +8 -4
- data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
- data/try/unit/horreum/serialization_try.rb +6 -2
- data/try/unit/horreum/settings_try.rb +4 -0
- data/try/unit/horreum/unique_index_edge_cases_try.rb +380 -0
- data/try/unit/horreum/unique_index_guard_validation_try.rb +283 -0
- data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
- data/try/unit/middleware/database_logger_methods_try.rb +251 -0
- data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
- data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
- data/try/unit/thread_safety_monitor_try.rb +149 -0
- metadata +81 -14
- data/.github/workflows/code-quality.yml +0 -138
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
- data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
- data/docs/archive/FAMILIA_UPDATE.md +0 -226
- data/docs/archive/README.md +0 -64
- data/docs/archive/api-reference.md +0 -333
- data/docs/guides/core-field-system.md +0 -806
- data/docs/guides/implementation.md +0 -276
- data/docs/guides/security-model.md +0 -183
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/connection/handlers.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
# Familia
|
|
4
6
|
#
|
|
@@ -219,5 +221,100 @@ module Familia
|
|
|
219
221
|
dbclient
|
|
220
222
|
end
|
|
221
223
|
end
|
|
224
|
+
|
|
225
|
+
# Handler for delegating connection resolution to parent object
|
|
226
|
+
#
|
|
227
|
+
# Used by DataType objects that are attached to a parent (Horreum instance or class).
|
|
228
|
+
# Delegates the connection resolution to the parent's dbclient method, which allows
|
|
229
|
+
# DataType objects to inherit connection settings, logical_database, and transaction
|
|
230
|
+
# context from their parent.
|
|
231
|
+
#
|
|
232
|
+
# This preserves the existing architectural pattern where DataType objects owned by
|
|
233
|
+
# Horreum models use the parent's connection chain. This is the primary behavior
|
|
234
|
+
# for DataType objects in typical usage.
|
|
235
|
+
#
|
|
236
|
+
# @example Instance-level DataType with parent
|
|
237
|
+
# user = User.new(userid: 'user_123')
|
|
238
|
+
# user.tags # DataType that delegates to user.dbclient
|
|
239
|
+
#
|
|
240
|
+
# @example Class-level DataType with parent
|
|
241
|
+
# User.global_users # DataType that delegates to User.dbclient
|
|
242
|
+
#
|
|
243
|
+
class ParentDelegationHandler < BaseConnectionHandler
|
|
244
|
+
@allows_transaction = true
|
|
245
|
+
@allows_pipelined = true
|
|
246
|
+
|
|
247
|
+
def initialize(data_type)
|
|
248
|
+
@data_type = data_type
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def handle(uri)
|
|
252
|
+
return nil unless @data_type.parent
|
|
253
|
+
|
|
254
|
+
# Delegate to parent's connection chain
|
|
255
|
+
# Parent can be either a Horreum class or instance
|
|
256
|
+
parent_connection = @data_type.parent.dbclient(uri)
|
|
257
|
+
|
|
258
|
+
if parent_connection
|
|
259
|
+
Familia.trace :DBCLIENT_PARENT_DELEGATION, @data_type.dbkey,
|
|
260
|
+
"Using parent connection from #{@data_type.parent.class}"
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
parent_connection
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Handler for standalone DataType objects without a parent
|
|
268
|
+
#
|
|
269
|
+
# Provides connection resolution for DataType objects that are created independently
|
|
270
|
+
# rather than being attached to a Horreum model. Checks for instance-level @dbclient
|
|
271
|
+
# first, then falls back to creating a connection based on logical_database option
|
|
272
|
+
# or global Familia connection.
|
|
273
|
+
#
|
|
274
|
+
# This enables standalone DataType usage patterns like Rack::Session implementations
|
|
275
|
+
# where DataType objects need independent connection management and transaction support.
|
|
276
|
+
#
|
|
277
|
+
# @example Standalone DataType with custom connection
|
|
278
|
+
# leaderboard = Familia::SortedSet.new('game:leaderboard')
|
|
279
|
+
# leaderboard.dbclient = ConnectionPool.new { Redis.new }
|
|
280
|
+
#
|
|
281
|
+
# @example Standalone DataType with logical_database option
|
|
282
|
+
# cache = Familia::HashKey.new('app:cache', logical_database: 2)
|
|
283
|
+
#
|
|
284
|
+
class StandaloneConnectionHandler < BaseConnectionHandler
|
|
285
|
+
@allows_transaction = true
|
|
286
|
+
@allows_pipelined = true
|
|
287
|
+
|
|
288
|
+
def initialize(data_type)
|
|
289
|
+
@data_type = data_type
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def handle(uri)
|
|
293
|
+
# If a specific URI is provided, always use it to get a connection.
|
|
294
|
+
if uri
|
|
295
|
+
connection = Familia.dbclient(uri)
|
|
296
|
+
Familia.trace :DBCLIENT_STANDALONE_DATATYPE, @data_type.dbkey,
|
|
297
|
+
"Created standalone connection for specific URI: #{uri}"
|
|
298
|
+
return connection
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Use instance @dbclient if explicitly set and no URI was passed
|
|
302
|
+
instance_dbclient = @data_type.instance_variable_get(:@dbclient)
|
|
303
|
+
if instance_dbclient
|
|
304
|
+
Familia.trace :DBCLIENT_DATATYPE_INSTANCE, @data_type.dbkey,
|
|
305
|
+
'Using DataType instance @dbclient'
|
|
306
|
+
return instance_dbclient
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Fall back to creating connection based on opts or global
|
|
310
|
+
target_uri = @data_type.opts[:logical_database]
|
|
311
|
+
connection = Familia.dbclient(target_uri)
|
|
312
|
+
|
|
313
|
+
Familia.trace :DBCLIENT_STANDALONE_DATATYPE, @data_type.dbkey,
|
|
314
|
+
"Created standalone connection for #{target_uri || 'default'}"
|
|
315
|
+
|
|
316
|
+
connection
|
|
317
|
+
end
|
|
318
|
+
end
|
|
222
319
|
end
|
|
223
320
|
end
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# lib/familia/connection/middleware.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require_relative '../../middleware/database_logger'
|
|
6
|
+
require_relative '../../middleware/database_command_counter'
|
|
4
7
|
|
|
5
8
|
module Familia
|
|
6
9
|
module Connection
|
|
@@ -13,19 +16,20 @@ module Familia
|
|
|
13
16
|
|
|
14
17
|
# @return [Integer] Current middleware version for cache invalidation
|
|
15
18
|
def middleware_version
|
|
16
|
-
@middleware_version
|
|
19
|
+
@middleware_version.value
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
# Increments the middleware version, invalidating all cached connections
|
|
20
23
|
def increment_middleware_version!
|
|
21
|
-
@middleware_version
|
|
22
|
-
Familia.trace :MIDDLEWARE_VERSION, nil, "Incremented to #{
|
|
24
|
+
new_version = @middleware_version.increment
|
|
25
|
+
Familia.trace :MIDDLEWARE_VERSION, nil, "Incremented to #{new_version}"
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
# Sets a versioned fiber-local connection
|
|
26
29
|
def fiber_connection=(connection)
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
current_version = middleware_version
|
|
31
|
+
Fiber[:familia_connection] = [connection, current_version]
|
|
32
|
+
Familia.trace :FIBER_CONNECTION, nil, "Set with version #{current_version}"
|
|
29
33
|
end
|
|
30
34
|
|
|
31
35
|
# Clears the fiber-local connection
|
|
@@ -81,18 +85,23 @@ module Familia
|
|
|
81
85
|
# Familia.reconnect! # Force new connections with middleware
|
|
82
86
|
#
|
|
83
87
|
def reconnect!
|
|
84
|
-
#
|
|
85
|
-
@
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
88
|
+
# Thread-safe: Use same mutex as dbclient to protect @connection_chain
|
|
89
|
+
@connection_chain_mutex.synchronize do
|
|
90
|
+
# Allow middleware to be re-registered by resetting all flags
|
|
91
|
+
@middleware_registered = false
|
|
92
|
+
@logger_registered = false
|
|
93
|
+
@counter_registered = false
|
|
94
|
+
register_middleware_once
|
|
95
|
+
|
|
96
|
+
# Clear connection chain to force rebuild
|
|
97
|
+
@connection_chain = nil
|
|
98
|
+
|
|
99
|
+
# Increment version to invalidate all cached connections
|
|
100
|
+
increment_middleware_version!
|
|
101
|
+
|
|
102
|
+
# Clear fiber-local connections
|
|
103
|
+
clear_fiber_connection!
|
|
104
|
+
end
|
|
96
105
|
|
|
97
106
|
Familia.trace :RECONNECT, nil, 'Connection chain cleared, will rebuild with current middleware on next use'
|
|
98
107
|
end
|
|
@@ -102,27 +111,28 @@ module Familia
|
|
|
102
111
|
# Registers middleware once globally, regardless of when clients are created.
|
|
103
112
|
# This prevents duplicate middleware registration and ensures all clients get middleware.
|
|
104
113
|
def register_middleware_once
|
|
105
|
-
# Skip if already registered
|
|
106
|
-
return if @middleware_registered
|
|
107
|
-
|
|
108
114
|
# Check if any middleware is enabled
|
|
109
115
|
return unless Familia.enable_database_logging || Familia.enable_database_counter
|
|
110
116
|
|
|
111
|
-
|
|
117
|
+
# Register each middleware independently to avoid early return bug
|
|
118
|
+
# where enabling one middleware prevents the other from being registered
|
|
119
|
+
if Familia.enable_database_logging && !@logger_registered
|
|
112
120
|
DatabaseLogger.logger = Familia.logger
|
|
113
121
|
RedisClient.register(DatabaseLogger)
|
|
114
122
|
Familia.trace :MIDDLEWARE_REGISTERED, nil, 'Registered DatabaseLogger'
|
|
123
|
+
@logger_registered = true
|
|
115
124
|
end
|
|
116
125
|
|
|
117
|
-
if Familia.enable_database_counter
|
|
126
|
+
if Familia.enable_database_counter && !@counter_registered
|
|
118
127
|
# NOTE: This middleware uses AtomicFixnum from concurrent-ruby which is
|
|
119
128
|
# less contentious than Mutex-based counters. Safe for production.
|
|
120
129
|
RedisClient.register(DatabaseCommandCounter)
|
|
121
130
|
Familia.trace :MIDDLEWARE_REGISTERED, nil, 'Registered DatabaseCommandCounter'
|
|
131
|
+
@counter_registered = true
|
|
122
132
|
end
|
|
123
133
|
|
|
124
|
-
# Set flag
|
|
125
|
-
@middleware_registered =
|
|
134
|
+
# Set global flag when any middleware is registered
|
|
135
|
+
@middleware_registered = @logger_registered || @counter_registered
|
|
126
136
|
end
|
|
127
137
|
end
|
|
128
138
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# lib/familia/connection/operation_core.rb
|
|
2
|
+
#
|
|
1
3
|
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
@@ -45,7 +47,7 @@ module Familia
|
|
|
45
47
|
when :transaction
|
|
46
48
|
Familia.transaction_mode
|
|
47
49
|
when :pipeline
|
|
48
|
-
Familia.
|
|
50
|
+
Familia.pipelined_mode
|
|
49
51
|
else
|
|
50
52
|
:strict
|
|
51
53
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# lib/familia/connection/pipelined_core.rb
|
|
2
|
+
#
|
|
1
3
|
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
@@ -16,7 +18,7 @@ module Familia
|
|
|
16
18
|
#
|
|
17
19
|
# Handles pipeline execution based on connection handler capabilities.
|
|
18
20
|
# When handler doesn't support pipelines, fallback behavior is controlled
|
|
19
|
-
# by Familia.
|
|
21
|
+
# by Familia.pipelined_mode setting.
|
|
20
22
|
#
|
|
21
23
|
# @param dbclient_proc [Proc] Lambda that returns the Redis connection
|
|
22
24
|
# @param block [Proc] Block containing Redis commands to execute
|
|
@@ -32,7 +34,7 @@ module Familia
|
|
|
32
34
|
# result.results # => ["OK", 1]
|
|
33
35
|
#
|
|
34
36
|
# @example With fallback modes
|
|
35
|
-
# Familia.configure { |c| c.
|
|
37
|
+
# Familia.configure { |c| c.pipelined_mode = :permissive }
|
|
36
38
|
# result = PipelineCore.execute_pipeline(-> { cached_conn }) do |conn|
|
|
37
39
|
# conn.set('key', 'value') # Executes individually, no error
|
|
38
40
|
# end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/connection/transaction_core.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
module Connection
|
|
@@ -8,11 +10,62 @@ module Familia
|
|
|
8
10
|
# behavior when transactions are unavailable due to connection handler constraints.
|
|
9
11
|
# Eliminates code duplication between Operations and Horreum Connection modules.
|
|
10
12
|
#
|
|
13
|
+
# ## Transaction Safety Rules
|
|
14
|
+
#
|
|
15
|
+
# ### Rule 1: No Save Operations Inside Transactions
|
|
16
|
+
# The following methods CANNOT be called within a transaction context:
|
|
17
|
+
# - `save`, `save!`, `save_if_not_exists!`, `create!`
|
|
18
|
+
# These methods require reading current state for validation, which would
|
|
19
|
+
# return uninspectable Redis::Future objects inside transactions.
|
|
20
|
+
#
|
|
21
|
+
# ### Rule 2: Reentrant Transaction Behavior
|
|
22
|
+
# Nested transaction calls reuse the same connection and do not create new
|
|
23
|
+
# MULTI/EXEC blocks. This ensures atomicity across nested operations.
|
|
24
|
+
#
|
|
25
|
+
# ### Rule 3: Read Operations Return Futures
|
|
26
|
+
# Inside transactions, read operations return Redis::Future objects that
|
|
27
|
+
# cannot be inspected until the transaction completes. Always check conditions
|
|
28
|
+
# before entering the transaction.
|
|
29
|
+
#
|
|
30
|
+
# ### Rule 4: Connection Handler Compatibility
|
|
31
|
+
# - **FiberTransactionHandler**: Supports reentrant transactions
|
|
32
|
+
# - **ProviderConnectionHandler**: Full transaction support
|
|
33
|
+
# - **CreateConnectionHandler**: Full transaction support
|
|
34
|
+
# - **FiberConnectionHandler**: Blocked (raises OperationModeError)
|
|
35
|
+
# - **DefaultConnectionHandler**: Blocked (raises OperationModeError)
|
|
36
|
+
#
|
|
37
|
+
# @example Correct Pattern: Save Before Transaction
|
|
38
|
+
# customer = Customer.new(email: 'test@example.com')
|
|
39
|
+
# customer.save # Validates unique constraints here
|
|
40
|
+
#
|
|
41
|
+
# customer.transaction do
|
|
42
|
+
# customer.increment(:login_count)
|
|
43
|
+
# customer.hset(:last_login, Time.now.to_i)
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# @example Incorrect Pattern: Save Inside Transaction
|
|
47
|
+
# Customer.transaction do
|
|
48
|
+
# customer = Customer.new(email: 'test@example.com')
|
|
49
|
+
# customer.save # Raises Familia::OperationModeError
|
|
50
|
+
# end
|
|
51
|
+
#
|
|
52
|
+
# @example Reentrant Transactions
|
|
53
|
+
# Customer.transaction do |outer_conn|
|
|
54
|
+
# outer_conn.set('key1', 'value1')
|
|
55
|
+
#
|
|
56
|
+
# # Nested call reuses same connection - no new MULTI/EXEC
|
|
57
|
+
# Customer.transaction do |inner_conn|
|
|
58
|
+
# inner_conn.set('key2', 'value2') # Same connection as outer
|
|
59
|
+
# end
|
|
60
|
+
# end
|
|
61
|
+
#
|
|
11
62
|
# @example Usage in transaction methods
|
|
12
63
|
# def transaction(&block)
|
|
13
64
|
# TransactionCore.execute_transaction(-> { dbclient }, &block)
|
|
14
65
|
# end
|
|
15
66
|
#
|
|
67
|
+
# @see docs/transaction_safety.md for complete safety guidelines
|
|
68
|
+
#
|
|
16
69
|
module TransactionCore
|
|
17
70
|
# Executes a transaction with configurable fallback behavior
|
|
18
71
|
#
|
|
@@ -21,6 +74,11 @@ module Familia
|
|
|
21
74
|
# 2. Reentrant transaction when already within a transaction context
|
|
22
75
|
# 3. Individual command execution with configurable error/warn/silent modes
|
|
23
76
|
#
|
|
77
|
+
# ## Safety Mechanisms
|
|
78
|
+
# - Fiber-local storage tracks transaction state across nested calls
|
|
79
|
+
# - Connection handler validation prevents unsafe transaction usage
|
|
80
|
+
# - Automatic cleanup ensures proper state management even on exceptions
|
|
81
|
+
#
|
|
24
82
|
# @param dbclient_proc [Proc] Lambda that returns the Redis connection
|
|
25
83
|
# @param block [Proc] Block containing Redis commands to execute
|
|
26
84
|
# @return [MultiResult] Result object with success status and command results
|
|
@@ -34,27 +92,25 @@ module Familia
|
|
|
34
92
|
# result.successful? # => true/false
|
|
35
93
|
# result.results # => ["OK", 1]
|
|
36
94
|
#
|
|
37
|
-
def self.execute_transaction(dbclient_proc, &
|
|
95
|
+
def self.execute_transaction(dbclient_proc, &)
|
|
38
96
|
# First, get the connection to populate the handler class
|
|
39
|
-
|
|
97
|
+
dbclient_proc.call
|
|
40
98
|
handler_class = Fiber[:familia_connection_handler_class]
|
|
41
99
|
|
|
42
100
|
# Check transaction capability
|
|
43
101
|
transaction_capability = handler_class&.allows_transaction
|
|
44
102
|
|
|
45
103
|
if transaction_capability == false
|
|
46
|
-
handle_transaction_fallback(dbclient_proc, handler_class, &
|
|
104
|
+
handle_transaction_fallback(dbclient_proc, handler_class, &)
|
|
47
105
|
elsif transaction_capability == :reentrant
|
|
48
106
|
# Already in transaction, just yield the connection
|
|
49
107
|
yield(Fiber[:familia_transaction])
|
|
50
108
|
else
|
|
51
109
|
# Normal transaction flow (includes nil, true, and other values)
|
|
52
|
-
execute_normal_transaction(dbclient_proc, &
|
|
110
|
+
execute_normal_transaction(dbclient_proc, &)
|
|
53
111
|
end
|
|
54
112
|
end
|
|
55
113
|
|
|
56
|
-
private
|
|
57
|
-
|
|
58
114
|
# Handles transaction fallback based on configured transaction mode
|
|
59
115
|
#
|
|
60
116
|
# Delegates to OperationCore.handle_fallback for consistent behavior
|
|
@@ -65,8 +121,8 @@ module Familia
|
|
|
65
121
|
# @param block [Proc] Block containing Redis commands to execute
|
|
66
122
|
# @return [MultiResult] Result from individual command execution or raises error
|
|
67
123
|
#
|
|
68
|
-
def self.handle_transaction_fallback(dbclient_proc, handler_class, &
|
|
69
|
-
OperationCore.handle_fallback(:transaction, dbclient_proc, handler_class, &
|
|
124
|
+
def self.handle_transaction_fallback(dbclient_proc, handler_class, &)
|
|
125
|
+
OperationCore.handle_fallback(:transaction, dbclient_proc, handler_class, &)
|
|
70
126
|
end
|
|
71
127
|
|
|
72
128
|
# Executes a normal Redis transaction using MULTI/EXEC
|
|
@@ -74,11 +130,21 @@ module Familia
|
|
|
74
130
|
# Handles the standard transaction flow including nested transaction detection,
|
|
75
131
|
# proper Fiber-local state management, and cleanup in ensure blocks.
|
|
76
132
|
#
|
|
133
|
+
# ## Implementation Details
|
|
134
|
+
# - Uses Fiber[:familia_transaction] to track active transaction connection
|
|
135
|
+
# - Reentrant behavior: yields existing connection if already in transaction
|
|
136
|
+
# - All commands queued and executed atomically on EXEC
|
|
137
|
+
# - Returns MultiResult with success status and command results
|
|
138
|
+
#
|
|
139
|
+
# ## Thread Safety
|
|
140
|
+
# Each thread has its own root fiber with isolated fiber-local storage,
|
|
141
|
+
# ensuring transactions don't interfere across threads.
|
|
142
|
+
#
|
|
77
143
|
# @param dbclient_proc [Proc] Lambda that returns the Redis connection
|
|
78
144
|
# @param block [Proc] Block containing Redis commands to execute
|
|
79
145
|
# @return [MultiResult] Result object with transaction command results
|
|
80
146
|
#
|
|
81
|
-
def self.execute_normal_transaction(dbclient_proc
|
|
147
|
+
def self.execute_normal_transaction(dbclient_proc)
|
|
82
148
|
# Check for existing transaction context
|
|
83
149
|
return yield(Fiber[:familia_transaction]) if Fiber[:familia_transaction]
|
|
84
150
|
|
data/lib/familia/connection.rb
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# lib/familia/connection.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
5
|
+
require_relative 'connection/behavior'
|
|
3
6
|
require_relative 'connection/handlers'
|
|
4
7
|
require_relative 'connection/middleware'
|
|
5
8
|
require_relative 'connection/operations'
|
|
6
9
|
require_relative 'connection/individual_command_proxy'
|
|
7
10
|
require_relative 'connection/operation_core'
|
|
8
11
|
require_relative 'connection/transaction_core'
|
|
9
|
-
require_relative 'connection/
|
|
12
|
+
require_relative 'connection/pipelined_core'
|
|
10
13
|
|
|
11
14
|
# Familia
|
|
12
15
|
#
|
|
@@ -15,7 +18,10 @@ require_relative 'connection/pipeline_core'
|
|
|
15
18
|
module Familia
|
|
16
19
|
@uri = URI.parse 'redis://127.0.0.1:6379'
|
|
17
20
|
@middleware_registered = false
|
|
18
|
-
@
|
|
21
|
+
@logger_registered = false
|
|
22
|
+
@counter_registered = false
|
|
23
|
+
@middleware_version = Concurrent::AtomicFixnum.new(0)
|
|
24
|
+
@connection_chain_mutex = Familia::ThreadSafety::InstrumentedMutex.new('connection_chain') # Thread-safe connection chain initialization
|
|
19
25
|
|
|
20
26
|
# The Connection module provides Database connection management for Familia.
|
|
21
27
|
# It allows easy setup and access to Database clients across different URIs
|
|
@@ -42,7 +48,7 @@ module Familia
|
|
|
42
48
|
@connection_chain = nil # Force rebuild of chain
|
|
43
49
|
end
|
|
44
50
|
|
|
45
|
-
# Sets the default URI for Database connections.
|
|
51
|
+
# Sets the default URI for Database connections.
|
|
46
52
|
#
|
|
47
53
|
# NOTE: uri is not a property of the Settings module b/c it's not
|
|
48
54
|
# configured in class defintions like default_expiration or logical DB index.
|
|
@@ -85,12 +91,22 @@ module Familia
|
|
|
85
91
|
# Retrieves a Database connection using the Chain of Responsibility pattern.
|
|
86
92
|
# Handles DB selection automatically based on the URI.
|
|
87
93
|
#
|
|
94
|
+
# Thread-safe: Uses double-checked locking pattern to avoid mutex overhead
|
|
95
|
+
# on the hot path. Only acquires mutex during initial lazy initialization.
|
|
96
|
+
# MRI's GIL provides implicit memory barriers making this pattern safe.
|
|
97
|
+
#
|
|
88
98
|
# @return [Redis] The Database client for the specified URI
|
|
89
99
|
# @example Familia.dbclient('redis://localhost:6379/1')
|
|
90
100
|
# Familia.dbclient(2) # Use DB 2 with default server
|
|
91
101
|
def dbclient(uri = nil)
|
|
92
|
-
|
|
93
|
-
@connection_chain
|
|
102
|
+
# Fast path - read with local variable to ensure single read
|
|
103
|
+
chain = @connection_chain
|
|
104
|
+
return chain.handle(uri) if chain
|
|
105
|
+
|
|
106
|
+
# Slow path - initialization only
|
|
107
|
+
@connection_chain_mutex.synchronize do
|
|
108
|
+
@connection_chain ||= build_connection_chain
|
|
109
|
+
end.handle(uri)
|
|
94
110
|
end
|
|
95
111
|
|
|
96
112
|
# Builds the connection chain with handlers in priority order
|
|
@@ -1,25 +1,110 @@
|
|
|
1
1
|
# lib/familia/data_type/connection.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
class DataType
|
|
5
7
|
# Connection - Instance-level connection and key generation methods
|
|
6
8
|
#
|
|
7
9
|
# This module provides instance methods for database connection resolution
|
|
8
|
-
# and Redis key generation for DataType objects.
|
|
10
|
+
# and Redis key generation for DataType objects. It includes shared connection
|
|
11
|
+
# behavior from Familia::Connection::Behavior, enabling transaction and pipeline
|
|
12
|
+
# support for both parent-owned and standalone DataType objects.
|
|
9
13
|
#
|
|
10
14
|
# Key features:
|
|
11
15
|
# * Database connection resolution with Chain of Responsibility pattern
|
|
12
16
|
# * Redis key generation based on parent context
|
|
13
17
|
# * Direct database access for advanced operations
|
|
18
|
+
# * Transaction support (MULTI/EXEC) for atomic operations
|
|
19
|
+
# * Pipeline support for batched command execution
|
|
20
|
+
# * Parent delegation for owned DataType objects
|
|
21
|
+
# * Standalone connection management for independent DataType objects
|
|
22
|
+
#
|
|
23
|
+
# Connection Chain Priority:
|
|
24
|
+
# 1. FiberTransactionHandler - Active transaction context
|
|
25
|
+
# 2. FiberConnectionHandler - Fiber-local connections
|
|
26
|
+
# 3. ProviderConnectionHandler - User-defined connection provider
|
|
27
|
+
# 4. ParentDelegationHandler - Delegate to parent object (primary for owned DataTypes)
|
|
28
|
+
# 5. StandaloneConnectionHandler - Independent DataType connection
|
|
29
|
+
#
|
|
30
|
+
# @example Parent-owned DataType (automatic delegation)
|
|
31
|
+
# class User < Familia::Horreum
|
|
32
|
+
# logical_database 2
|
|
33
|
+
# zset :scores
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# user = User.new(userid: 'user_123')
|
|
37
|
+
# user.scores.transaction do |conn|
|
|
38
|
+
# conn.zadd(user.scores.dbkey, 100, 'level1')
|
|
39
|
+
# conn.zadd(user.scores.dbkey, 200, 'level2')
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# @example Standalone DataType with transaction
|
|
43
|
+
# leaderboard = Familia::SortedSet.new('game:leaderboard')
|
|
44
|
+
# leaderboard.transaction do |conn|
|
|
45
|
+
# conn.zadd(leaderboard.dbkey, 500, 'player1')
|
|
46
|
+
# conn.zadd(leaderboard.dbkey, 600, 'player2')
|
|
47
|
+
# end
|
|
14
48
|
#
|
|
15
49
|
module Connection
|
|
16
|
-
|
|
17
|
-
|
|
50
|
+
include Familia::Connection::Behavior
|
|
51
|
+
|
|
52
|
+
# Returns the effective URI this DataType will use for connections
|
|
53
|
+
#
|
|
54
|
+
# For parent-owned DataTypes, delegates to parent's URI.
|
|
55
|
+
# For standalone DataTypes with logical_database option, constructs URI with that database.
|
|
56
|
+
# For standalone DataTypes without options, returns global Familia.uri.
|
|
57
|
+
# Explicit @uri assignment (via uri=) takes precedence.
|
|
58
|
+
#
|
|
59
|
+
# @return [URI, nil] The URI for database connections
|
|
60
|
+
#
|
|
61
|
+
def uri
|
|
62
|
+
return @uri if defined?(@uri) && @uri
|
|
63
|
+
return parent.uri if parent && parent.respond_to?(:uri)
|
|
64
|
+
|
|
65
|
+
# Check opts[:logical_database] first, then parent's logical_database
|
|
66
|
+
db_num = opts[:logical_database]
|
|
67
|
+
db_num ||= parent.logical_database if parent && parent.respond_to?(:logical_database)
|
|
68
|
+
|
|
69
|
+
if db_num
|
|
70
|
+
# Create a new URI with the database number but without custom port
|
|
71
|
+
# This ensures consistent URI representation (e.g., redis://host/db not redis://host:port/db)
|
|
72
|
+
base_uri = Familia.uri
|
|
73
|
+
URI.parse("redis://#{base_uri.host}/#{db_num}")
|
|
74
|
+
else
|
|
75
|
+
Familia.uri
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Retrieves a Database connection using the Chain of Responsibility pattern
|
|
80
|
+
#
|
|
81
|
+
# Implements connection resolution optimized for DataType usage patterns:
|
|
82
|
+
# - Fast path check for active transaction context
|
|
83
|
+
# - Full connection chain for comprehensive resolution
|
|
84
|
+
# - Parent delegation as primary behavior for owned DataTypes
|
|
85
|
+
# - Standalone connection handling for independent DataTypes
|
|
86
|
+
#
|
|
87
|
+
# Note: We don't cache the connection chain in an instance variable because
|
|
88
|
+
# DataType objects are frozen for thread safety. Building the chain is cheap
|
|
89
|
+
# (just creating handler objects), and the actual connection resolution work
|
|
90
|
+
# is done by the handlers themselves.
|
|
91
|
+
#
|
|
92
|
+
# @param uri [String, URI, Integer, nil] Optional URI for database selection
|
|
93
|
+
# @return [Redis] The Database client for the specified URI
|
|
94
|
+
#
|
|
95
|
+
# @example Getting connection from parent-owned DataType
|
|
96
|
+
# user.tags.dbclient # Delegates to user.dbclient
|
|
97
|
+
#
|
|
98
|
+
# @example Getting connection from standalone DataType
|
|
99
|
+
# cache = Familia::HashKey.new('app:cache', logical_database: 2)
|
|
100
|
+
# cache.dbclient # Uses standalone handler with db 2
|
|
101
|
+
#
|
|
102
|
+
def dbclient(uri = nil)
|
|
103
|
+
# Fast path for transaction context (highest priority)
|
|
18
104
|
return Fiber[:familia_transaction] if Fiber[:familia_transaction]
|
|
19
|
-
return @dbclient if @dbclient
|
|
20
105
|
|
|
21
|
-
#
|
|
22
|
-
|
|
106
|
+
# Build connection chain (not cached due to frozen objects)
|
|
107
|
+
build_connection_chain.handle(uri)
|
|
23
108
|
end
|
|
24
109
|
|
|
25
110
|
# Produces the full dbkey for this object.
|
|
@@ -75,8 +160,69 @@ module Familia
|
|
|
75
160
|
# Provides a structured way to "gear down" to run db commands that are
|
|
76
161
|
# not implemented in our DataType classes since we intentionally don't
|
|
77
162
|
# have a method_missing method.
|
|
163
|
+
#
|
|
164
|
+
# Enhanced to work seamlessly with transactions and pipelines. When called
|
|
165
|
+
# within a transaction or pipeline context, uses that connection automatically.
|
|
166
|
+
#
|
|
167
|
+
# @yield [Redis, String] Yields the connection and dbkey to the block
|
|
168
|
+
# @return The return value of the block
|
|
169
|
+
#
|
|
170
|
+
# @example Basic usage
|
|
171
|
+
# datatype.direct_access do |conn, key|
|
|
172
|
+
# conn.zadd(key, 100, 'member')
|
|
173
|
+
# end
|
|
174
|
+
#
|
|
175
|
+
# @example Within transaction (automatic context detection)
|
|
176
|
+
# datatype.transaction do |trans_conn|
|
|
177
|
+
# datatype.direct_access do |conn, key|
|
|
178
|
+
# # conn is the same as trans_conn
|
|
179
|
+
# conn.zadd(key, 200, 'member')
|
|
180
|
+
# end
|
|
181
|
+
# end
|
|
182
|
+
#
|
|
78
183
|
def direct_access
|
|
79
|
-
|
|
184
|
+
if Fiber[:familia_transaction]
|
|
185
|
+
# Already in transaction, use that connection
|
|
186
|
+
yield(Fiber[:familia_transaction], dbkey)
|
|
187
|
+
elsif Fiber[:familia_pipeline]
|
|
188
|
+
# Already in pipeline, use that connection
|
|
189
|
+
yield(Fiber[:familia_pipeline], dbkey)
|
|
190
|
+
else
|
|
191
|
+
yield(dbclient, dbkey)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
private
|
|
196
|
+
|
|
197
|
+
# Builds the connection chain with handlers in priority order
|
|
198
|
+
#
|
|
199
|
+
# Creates the Chain of Responsibility for connection resolution with
|
|
200
|
+
# DataType-specific handlers. Handlers are checked in order:
|
|
201
|
+
#
|
|
202
|
+
# 1. FiberTransactionHandler - Return active transaction connection
|
|
203
|
+
# 2. FiberConnectionHandler - Use fiber-local connection
|
|
204
|
+
# 3. ProviderConnectionHandler - Delegate to connection provider
|
|
205
|
+
# 4. ParentDelegationHandler - Delegate to parent's connection (primary for owned DataTypes)
|
|
206
|
+
# 5. StandaloneConnectionHandler - Handle standalone DataTypes
|
|
207
|
+
#
|
|
208
|
+
# @return [ResponsibilityChain] Configured connection chain
|
|
209
|
+
#
|
|
210
|
+
def build_connection_chain
|
|
211
|
+
# Create fresh handler instances each time since DataType objects are frozen
|
|
212
|
+
# The chain itself is cached in @connection_chain, so this only runs once
|
|
213
|
+
fiber_connection_handler = Familia::Connection::FiberConnectionHandler.new
|
|
214
|
+
provider_connection_handler = Familia::Connection::ProviderConnectionHandler.new
|
|
215
|
+
|
|
216
|
+
# DataType-specific handlers for parent delegation and standalone usage
|
|
217
|
+
parent_delegation_handler = Familia::Connection::ParentDelegationHandler.new(self)
|
|
218
|
+
standalone_connection_handler = Familia::Connection::StandaloneConnectionHandler.new(self)
|
|
219
|
+
|
|
220
|
+
Familia::Connection::ResponsibilityChain.new
|
|
221
|
+
.add_handler(Familia::Connection::FiberTransactionHandler.instance)
|
|
222
|
+
.add_handler(fiber_connection_handler)
|
|
223
|
+
.add_handler(provider_connection_handler)
|
|
224
|
+
.add_handler(parent_delegation_handler)
|
|
225
|
+
.add_handler(standalone_connection_handler)
|
|
80
226
|
end
|
|
81
227
|
end
|
|
82
228
|
end
|