familia 2.0.0.pre19 → 2.0.0.pre22
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/.talismanrc +5 -1
- data/CHANGELOG.rst +220 -112
- data/CLAUDE.md +28 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +20 -17
- data/bin/try +16 -0
- data/bin/tryouts +16 -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 +161 -117
- 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-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 +4 -3
- 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 +2 -0
- data/lib/familia/connection/handlers.rb +2 -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 -2
- data/lib/familia/connection/operations.rb +2 -0
- data/lib/familia/connection/pipelined_core.rb +3 -3
- data/lib/familia/connection/transaction_core.rb +69 -2
- data/lib/familia/connection.rb +18 -3
- data/lib/familia/data_type/class_methods.rb +3 -1
- data/lib/familia/data_type/connection.rb +2 -0
- data/lib/familia/data_type/database_commands.rb +2 -0
- data/lib/familia/data_type/serialization.rb +79 -52
- 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 +7 -5
- 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 +7 -10
- data/lib/familia/data_type/types/stringkey.rb +24 -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 +2 -0
- 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 +3 -1
- data/lib/familia/features/expiration.rb +12 -4
- data/lib/familia/features/external_identifier.rb +62 -7
- data/lib/familia/features/object_identifier.rb +49 -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 +138 -9
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +97 -21
- data/lib/familia/features/relationships/indexing.rb +3 -0
- data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
- 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 +3 -1
- data/lib/familia/horreum/connection.rb +17 -1
- data/lib/familia/horreum/database_commands.rb +8 -1
- data/lib/familia/horreum/definition.rb +16 -6
- data/lib/familia/horreum/management.rb +353 -52
- data/lib/familia/horreum/persistence.rb +179 -108
- data/lib/familia/horreum/related_fields.rb +2 -0
- data/lib/familia/horreum/serialization.rb +23 -4
- data/lib/familia/horreum/settings.rb +2 -0
- data/lib/familia/horreum/utils.rb +2 -0
- data/lib/familia/horreum.rb +15 -1
- data/lib/familia/identifier_extractor.rb +3 -1
- data/lib/familia/instrumentation.rb +156 -0
- data/lib/familia/json_serializer.rb +2 -0
- data/lib/familia/logging.rb +92 -32
- 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 +2 -0
- 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 +295 -170
- data/lib/multi_result.rb +61 -31
- 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 +2 -0
- data/try/edge_cases/ttl_side_effects_try.rb +4 -0
- data/try/features/count_any_edge_cases_try.rb +486 -0
- data/try/features/count_any_methods_try.rb +197 -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 +4 -0
- data/try/features/external_identifier/external_identifier_try.rb +305 -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 +140 -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 +606 -0
- data/try/features/relationships/indexing_try.rb +2 -0
- 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 +2 -0
- 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 +4 -0
- 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 +4 -0
- data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
- 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 +4 -0
- data/try/integration/cross_component_try.rb +4 -0
- data/try/integration/data_types/datatype_pipelines_try.rb +9 -3
- data/try/integration/data_types/datatype_transactions_try.rb +17 -7
- 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 +4 -0
- data/try/integration/models/customer_try.rb +7 -3
- data/try/integration/models/datatype_base_try.rb +4 -0
- data/try/integration/models/familia_object_try.rb +4 -0
- data/try/integration/persistence_operations_try.rb +4 -0
- 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 +39 -22
- 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 +6 -2
- 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/serialization_try.rb +386 -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 +4 -0
- 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 +5 -1
- data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
- data/try/unit/horreum/base_try.rb +4 -0
- data/try/unit/horreum/class_methods_try.rb +4 -0
- 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 +6 -1
- 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 +4 -0
- 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 +4 -0
- data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
- data/try/unit/horreum/serialization_try.rb +4 -0
- data/try/unit/horreum/settings_try.rb +4 -0
- data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
- data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -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 +69 -17
- data/.github/workflows/code-quality.yml +0 -138
- data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
- data/changelog.d/20251011_203905_delano_next.rst +0 -30
- data/changelog.d/20251011_212633_delano_next.rst +0 -13
- data/changelog.d/20251011_221253_delano_next.rst +0 -26
- 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,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# examples/datatype_standalone.rb
|
|
3
|
+
#
|
|
4
|
+
# frozen_string_literal: true
|
|
3
5
|
|
|
4
6
|
# Demonstration: Familia::StringKey for Session Storage with Atomic Transactions
|
|
5
|
-
#
|
|
6
7
|
# This example shows how to use Familia's DataType classes independently
|
|
7
8
|
# without inheriting from Familia::Horreum. It implements a Rack-compatible
|
|
8
9
|
# session store using Familia::StringKey for secure, TTL-managed storage.
|
|
@@ -146,7 +147,7 @@ class SecureSessionStore < Rack::Session::Abstract::PersistedSecure
|
|
|
146
147
|
[sid, session_data]
|
|
147
148
|
rescue Familia::PersistenceError => e
|
|
148
149
|
# Log error in development/debugging
|
|
149
|
-
Familia.
|
|
150
|
+
Familia.debug "[Session] Error reading session #{sid_string}: #{e.message}"
|
|
150
151
|
|
|
151
152
|
# Return new session on any error
|
|
152
153
|
[generate_sid, {}]
|
|
@@ -196,7 +197,7 @@ class SecureSessionStore < Rack::Session::Abstract::PersistedSecure
|
|
|
196
197
|
sid
|
|
197
198
|
rescue Familia::PersistenceError => e
|
|
198
199
|
# Log error in development/debugging
|
|
199
|
-
Familia.
|
|
200
|
+
Familia.debug "[Session] Error writing session #{sid_string}: #{e.message}"
|
|
200
201
|
|
|
201
202
|
# Return false to indicate failure
|
|
202
203
|
false
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
2
|
# examples/encrypted_fields.rb
|
|
4
3
|
#
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
5
6
|
# Demonstrates the EncryptedFields feature for protecting sensitive data.
|
|
6
7
|
# This feature provides transparent encryption/decryption of sensitive fields
|
|
7
8
|
# using strong cryptographic algorithms with field-specific key derivation.
|
data/examples/relationships.rb
CHANGED
data/examples/safe_dump.rb
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
2
|
# examples/safe_dump.rb
|
|
4
3
|
#
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
5
6
|
# Demonstrates the SafeDump feature with the new DSL methods.
|
|
6
7
|
# SafeDump allows you to control which fields are exposed when
|
|
7
8
|
# serializing objects, preventing accidental exposure of sensitive data.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# examples/sampling_demo.rb
|
|
3
|
+
#
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
# Demonstrates DatabaseLogger sampling to reduce log volume in high-traffic scenarios.
|
|
7
|
+
# Run with: bundle exec ruby examples/sampling_demo.rb
|
|
8
|
+
|
|
9
|
+
require_relative '../lib/familia'
|
|
10
|
+
require 'logger'
|
|
11
|
+
|
|
12
|
+
# Enable database command logging (middleware registered automatically)
|
|
13
|
+
Familia.enable_database_logging = true
|
|
14
|
+
DatabaseLogger.logger = Familia::FamiliaLogger.new($stdout)
|
|
15
|
+
DatabaseLogger.logger.level = Familia::FamiliaLogger::TRACE
|
|
16
|
+
|
|
17
|
+
# Scenario 1: No sampling (default) - logs every command
|
|
18
|
+
puts "\n=== Scenario 1: No Sampling (logs all 100 commands) ==="
|
|
19
|
+
DatabaseLogger.sample_rate = nil
|
|
20
|
+
100.times { |i| Familia.dbclient.set("key_#{i}", "value_#{i}") }
|
|
21
|
+
puts "Commands captured: #{DatabaseLogger.commands.size}"
|
|
22
|
+
puts "(Check output above - should see ~100 log lines)"
|
|
23
|
+
|
|
24
|
+
# Scenario 2: 10% sampling - logs ~10 commands
|
|
25
|
+
puts "\n=== Scenario 2: 10% Sampling (logs ~10 of 100 commands) ==="
|
|
26
|
+
DatabaseLogger.clear_commands
|
|
27
|
+
DatabaseLogger.sample_rate = 0.1
|
|
28
|
+
100.times { |i| Familia.dbclient.set("sampled_10_#{i}", "value_#{i}") }
|
|
29
|
+
puts "Commands captured: #{DatabaseLogger.commands.size}"
|
|
30
|
+
puts "(Check output above - should see ~10 log lines)"
|
|
31
|
+
|
|
32
|
+
# Scenario 3: 1% sampling - logs ~1 command (production-friendly)
|
|
33
|
+
puts "\n=== Scenario 3: 1% Sampling (logs ~1 of 100 commands) ==="
|
|
34
|
+
DatabaseLogger.clear_commands
|
|
35
|
+
DatabaseLogger.sample_rate = 0.01
|
|
36
|
+
100.times { |i| Familia.dbclient.set("sampled_1_#{i}", "value_#{i}") }
|
|
37
|
+
puts "Commands captured: #{DatabaseLogger.commands.size}"
|
|
38
|
+
puts "(Check output above - should see ~1 log line)"
|
|
39
|
+
|
|
40
|
+
# Scenario 4: Sampling with structured logging
|
|
41
|
+
puts "\n=== Scenario 4: 10% Sampling + Structured Logging ==="
|
|
42
|
+
DatabaseLogger.clear_commands
|
|
43
|
+
DatabaseLogger.sample_rate = 0.1
|
|
44
|
+
DatabaseLogger.structured_logging = true
|
|
45
|
+
100.times { |i| Familia.dbclient.set("structured_#{i}", "value_#{i}") }
|
|
46
|
+
puts "Commands captured: #{DatabaseLogger.commands.size}"
|
|
47
|
+
puts "(Check structured output above)"
|
|
48
|
+
|
|
49
|
+
puts "\n=== Key Insights ==="
|
|
50
|
+
puts "✓ Command capture is unaffected (always 100 commands captured)"
|
|
51
|
+
puts "✓ Only logger output is sampled (reduces log volume)"
|
|
52
|
+
puts "✓ Tests can verify commands while production logs stay clean"
|
|
53
|
+
puts "✓ Deterministic sampling (every Nth command) ensures consistency"
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# examples/single_connection_transaction_confusions.rb
|
|
3
|
+
#
|
|
4
|
+
# frozen_string_literal: true
|
|
3
5
|
|
|
4
6
|
# Redis Single Connection Mode Confusions
|
|
5
|
-
#
|
|
6
7
|
# This file demonstrates why mixing Redis operation modes on a single connection
|
|
7
8
|
# causes subtle but critical failures in production applications.
|
|
8
9
|
#
|
data/familia.gemspec
CHANGED
|
@@ -17,9 +17,10 @@ Gem::Specification.new do |spec|
|
|
|
17
17
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
18
18
|
spec.require_paths = ['lib']
|
|
19
19
|
|
|
20
|
-
spec.required_ruby_version = Gem::Requirement.new('>= 3.
|
|
20
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 3.3.6')
|
|
21
21
|
|
|
22
22
|
spec.add_dependency 'benchmark', '~> 0.4'
|
|
23
|
+
spec.add_dependency 'concurrent-ruby', '~> 1.3'
|
|
23
24
|
spec.add_dependency 'connection_pool', '~> 2.5'
|
|
24
25
|
spec.add_dependency 'csv', '~> 3.3'
|
|
25
26
|
spec.add_dependency 'logger', '~> 1.7'
|
data/lib/familia/base.rb
CHANGED
|
@@ -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
|
|
@@ -85,8 +87,7 @@ module Familia
|
|
|
85
87
|
|
|
86
88
|
# Return MultiResult format for consistency
|
|
87
89
|
results = proxy.collected_results
|
|
88
|
-
|
|
89
|
-
MultiResult.new(summary_boolean, results)
|
|
90
|
+
MultiResult.new(results)
|
|
90
91
|
end
|
|
91
92
|
end
|
|
92
93
|
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
|
|
@@ -78,9 +80,7 @@ module Familia
|
|
|
78
80
|
end
|
|
79
81
|
|
|
80
82
|
# Return same MultiResult format as other methods
|
|
81
|
-
|
|
82
|
-
summary_boolean = command_return_values.none? { |ret| ret.is_a?(Exception) }
|
|
83
|
-
MultiResult.new(summary_boolean, command_return_values)
|
|
83
|
+
MultiResult.new(command_return_values)
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
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
|
|
@@ -72,6 +130,16 @@ module Familia
|
|
|
72
130
|
# Handles the standard transaction flow including nested transaction detection,
|
|
73
131
|
# proper Fiber-local state management, and cleanup in ensure blocks.
|
|
74
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
|
+
#
|
|
75
143
|
# @param dbclient_proc [Proc] Lambda that returns the Redis connection
|
|
76
144
|
# @param block [Proc] Block containing Redis commands to execute
|
|
77
145
|
# @return [MultiResult] Result object with transaction command results
|
|
@@ -90,8 +158,7 @@ module Familia
|
|
|
90
158
|
end
|
|
91
159
|
|
|
92
160
|
# Return same MultiResult format as other methods
|
|
93
|
-
|
|
94
|
-
MultiResult.new(summary_boolean, command_return_values)
|
|
161
|
+
MultiResult.new(command_return_values)
|
|
95
162
|
end
|
|
96
163
|
end
|
|
97
164
|
end
|
data/lib/familia/connection.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/connection.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require_relative 'connection/behavior'
|
|
4
6
|
require_relative 'connection/handlers'
|
|
@@ -16,7 +18,10 @@ require_relative 'connection/pipelined_core'
|
|
|
16
18
|
module Familia
|
|
17
19
|
@uri = URI.parse 'redis://127.0.0.1:6379'
|
|
18
20
|
@middleware_registered = false
|
|
19
|
-
@
|
|
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
|
|
20
25
|
|
|
21
26
|
# The Connection module provides Database connection management for Familia.
|
|
22
27
|
# It allows easy setup and access to Database clients across different URIs
|
|
@@ -86,12 +91,22 @@ module Familia
|
|
|
86
91
|
# Retrieves a Database connection using the Chain of Responsibility pattern.
|
|
87
92
|
# Handles DB selection automatically based on the URI.
|
|
88
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
|
+
#
|
|
89
98
|
# @return [Redis] The Database client for the specified URI
|
|
90
99
|
# @example Familia.dbclient('redis://localhost:6379/1')
|
|
91
100
|
# Familia.dbclient(2) # Use DB 2 with default server
|
|
92
101
|
def dbclient(uri = nil)
|
|
93
|
-
|
|
94
|
-
@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)
|
|
95
110
|
end
|
|
96
111
|
|
|
97
112
|
# Builds the connection chain with handlers in priority order
|