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
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# try/unit/core/middleware_thread_safety_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# Thread Safety Tests for DatabaseLogger Middleware
|
|
6
|
+
#
|
|
7
|
+
# These tests specifically target potential race conditions and concurrency issues
|
|
8
|
+
# in the DatabaseLogger middleware that could lead to nil entries in the @commands array.
|
|
9
|
+
#
|
|
10
|
+
# Covers:
|
|
11
|
+
# - Concurrent append_command operations
|
|
12
|
+
# - Thread-safe pipeline command logging
|
|
13
|
+
# - Mixed operation types under contention
|
|
14
|
+
# - Sampling counter atomicity
|
|
15
|
+
# - Rapid sequential calls within threads
|
|
16
|
+
# - clear_commands during active logging
|
|
17
|
+
#
|
|
18
|
+
# Background:
|
|
19
|
+
# An intermittent NoMethodError was observed where .command was called on nil
|
|
20
|
+
# during teardown of middleware_sampling_try.rb. The error suggests potential
|
|
21
|
+
# corruption of the @commands Concurrent::Array, possibly due to race conditions
|
|
22
|
+
# between append_command, clear_commands, or middleware state management.
|
|
23
|
+
|
|
24
|
+
require_relative '../../support/helpers/test_helpers'
|
|
25
|
+
require 'concurrent'
|
|
26
|
+
|
|
27
|
+
# Setup: Reset DatabaseLogger and ensure middleware is registered
|
|
28
|
+
Familia.connection_provider = nil
|
|
29
|
+
Familia.enable_database_logging = true
|
|
30
|
+
Familia.reconnect!
|
|
31
|
+
DatabaseLogger.clear_commands
|
|
32
|
+
DatabaseLogger.sample_rate = nil
|
|
33
|
+
DatabaseLogger.structured_logging = false
|
|
34
|
+
|
|
35
|
+
## DatabaseLogger.append_command is thread-safe under concurrent access from 50 threads
|
|
36
|
+
# First verify middleware is working in main thread
|
|
37
|
+
DatabaseLogger.clear_commands
|
|
38
|
+
test_client = Familia.dbclient
|
|
39
|
+
test_client.set("setup_test", "value")
|
|
40
|
+
setup_commands = DatabaseLogger.commands.size
|
|
41
|
+
|
|
42
|
+
# Now run the actual test
|
|
43
|
+
DatabaseLogger.clear_commands
|
|
44
|
+
barrier = Concurrent::CyclicBarrier.new(50)
|
|
45
|
+
threads = []
|
|
46
|
+
dbclient = Familia.dbclient # Get connection in main thread (has middleware)
|
|
47
|
+
|
|
48
|
+
50.times do |i|
|
|
49
|
+
threads << Thread.new do
|
|
50
|
+
barrier.wait # Synchronize start to maximize contention
|
|
51
|
+
dbclient.set("thread_key_#{i}", "value_#{i}")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
threads.each(&:join)
|
|
56
|
+
commands = DatabaseLogger.commands
|
|
57
|
+
|
|
58
|
+
# All commands should be captured (no lost writes)
|
|
59
|
+
# No nil entries should exist in the commands array
|
|
60
|
+
# Note: setup_commands verifies middleware is working (should be 1)
|
|
61
|
+
[setup_commands, commands.size, commands.any?(nil), commands.all? { |cmd| cmd.respond_to?(:command) }]
|
|
62
|
+
#=> [1, 50, false, true]
|
|
63
|
+
|
|
64
|
+
## Pipelined commands are thread-safe under concurrent access from 25 threads
|
|
65
|
+
DatabaseLogger.clear_commands
|
|
66
|
+
barrier = Concurrent::CyclicBarrier.new(25)
|
|
67
|
+
threads = []
|
|
68
|
+
dbclient = Familia.dbclient # Get connection in main thread (has middleware)
|
|
69
|
+
|
|
70
|
+
25.times do |i|
|
|
71
|
+
threads << Thread.new do
|
|
72
|
+
barrier.wait
|
|
73
|
+
dbclient.pipelined do |pipeline|
|
|
74
|
+
pipeline.set("pipeline_thread_#{i}_key1", "val1")
|
|
75
|
+
pipeline.set("pipeline_thread_#{i}_key2", "val2")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
threads.each(&:join)
|
|
81
|
+
commands = DatabaseLogger.commands
|
|
82
|
+
|
|
83
|
+
# Each thread creates one pipeline command (25 total)
|
|
84
|
+
# Verify no nil entries and all are proper CommandMessage objects
|
|
85
|
+
[commands.size, commands.any?(nil), commands.all? { |cmd| cmd.respond_to?(:command) }]
|
|
86
|
+
#=> [25, false, true]
|
|
87
|
+
|
|
88
|
+
## Mixed middleware operations (call, call_pipelined, call_once) are thread-safe
|
|
89
|
+
DatabaseLogger.clear_commands
|
|
90
|
+
barrier = Concurrent::CyclicBarrier.new(60)
|
|
91
|
+
threads = []
|
|
92
|
+
dbclient = Familia.dbclient # Get connection in main thread (has middleware)
|
|
93
|
+
|
|
94
|
+
60.times do |i|
|
|
95
|
+
threads << Thread.new do
|
|
96
|
+
barrier.wait
|
|
97
|
+
|
|
98
|
+
case i % 3
|
|
99
|
+
when 0 # Regular call
|
|
100
|
+
dbclient.set("mixed_#{i}", "val")
|
|
101
|
+
when 1 # Pipelined
|
|
102
|
+
dbclient.pipelined { |p| p.get("mixed_#{i}") }
|
|
103
|
+
when 2 # Multiple rapid calls
|
|
104
|
+
3.times { dbclient.get("mixed_#{i}") }
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
threads.each(&:join)
|
|
110
|
+
commands = DatabaseLogger.commands
|
|
111
|
+
|
|
112
|
+
# Verify no nil entries regardless of operation type
|
|
113
|
+
# All entries should be valid CommandMessage instances
|
|
114
|
+
[commands.any?(nil), commands.all? { |cmd| cmd.is_a?(DatabaseLogger::CommandMessage) }]
|
|
115
|
+
#=> [false, true]
|
|
116
|
+
|
|
117
|
+
## sample_rate counter is thread-safe with 100 concurrent operations
|
|
118
|
+
# REMOVED: This test had incorrect expectations. The sample_rate controls LOGGING
|
|
119
|
+
# output, not COMMAND CAPTURE. Per database_logger.rb:153-154:
|
|
120
|
+
# "Command capture is unaffected - only logger output is sampled."
|
|
121
|
+
# The commands array always contains all commands regardless of sample_rate.
|
|
122
|
+
# Thread safety of the AtomicFixnum counter is verified by other tests.
|
|
123
|
+
|
|
124
|
+
## Rapid sequential calls within threads don't corrupt shared state
|
|
125
|
+
DatabaseLogger.clear_commands
|
|
126
|
+
DatabaseLogger.sample_rate = nil # Log everything
|
|
127
|
+
latch = Concurrent::CountDownLatch.new(20)
|
|
128
|
+
threads = []
|
|
129
|
+
dbclient = Familia.dbclient # Get connection in main thread (has middleware)
|
|
130
|
+
|
|
131
|
+
20.times do |i|
|
|
132
|
+
threads << Thread.new do
|
|
133
|
+
# Rapid-fire 10 operations per thread to test state isolation
|
|
134
|
+
10.times do |j|
|
|
135
|
+
dbclient.set("rapid_#{i}_#{j}", "val")
|
|
136
|
+
end
|
|
137
|
+
latch.count_down
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
latch.wait(5) # 5 second timeout
|
|
142
|
+
commands = DatabaseLogger.commands
|
|
143
|
+
|
|
144
|
+
# Should have 200 commands (20 threads × 10 calls)
|
|
145
|
+
# Most importantly: NO nil entries from rapid sequential access
|
|
146
|
+
[commands.size, commands.any?(nil)]
|
|
147
|
+
#=> [200, false]
|
|
148
|
+
|
|
149
|
+
## clear_commands doesn't cause nil entries during concurrent logging
|
|
150
|
+
DatabaseLogger.clear_commands
|
|
151
|
+
DatabaseLogger.sample_rate = nil
|
|
152
|
+
barrier = Concurrent::CyclicBarrier.new(51) # 50 loggers + 1 clearer
|
|
153
|
+
threads = []
|
|
154
|
+
dbclient = Familia.dbclient # Get connection in main thread (has middleware)
|
|
155
|
+
|
|
156
|
+
# 50 threads logging continuously
|
|
157
|
+
50.times do |i|
|
|
158
|
+
threads << Thread.new do
|
|
159
|
+
barrier.wait
|
|
160
|
+
10.times do |j|
|
|
161
|
+
dbclient.set("clear_test_#{i}_#{j}", "val")
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# 1 thread clearing repeatedly to create contention
|
|
167
|
+
clearer = Thread.new do
|
|
168
|
+
barrier.wait
|
|
169
|
+
5.times do
|
|
170
|
+
sleep 0.001 # Small delay between clears
|
|
171
|
+
DatabaseLogger.clear_commands
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
threads.each(&:join)
|
|
176
|
+
clearer.join
|
|
177
|
+
|
|
178
|
+
# After clearing, commands should be empty or valid (never contain nil)
|
|
179
|
+
# This tests whether clear_commands can corrupt the array during active logging
|
|
180
|
+
commands = DatabaseLogger.commands
|
|
181
|
+
[commands.any?(nil)]
|
|
182
|
+
#=> [false]
|
|
183
|
+
|
|
184
|
+
## CommandMessage structure is preserved under concurrent access
|
|
185
|
+
DatabaseLogger.clear_commands
|
|
186
|
+
barrier = Concurrent::CyclicBarrier.new(30)
|
|
187
|
+
threads = []
|
|
188
|
+
dbclient = Familia.dbclient # Get connection in main thread (has middleware)
|
|
189
|
+
|
|
190
|
+
30.times do |i|
|
|
191
|
+
threads << Thread.new do
|
|
192
|
+
barrier.wait
|
|
193
|
+
dbclient.set("structure_test_#{i}", "value_#{i}")
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
threads.each(&:join)
|
|
198
|
+
commands = DatabaseLogger.commands
|
|
199
|
+
|
|
200
|
+
# Verify all CommandMessage fields are properly initialized
|
|
201
|
+
# This ensures the Data.define structure isn't corrupted by concurrency
|
|
202
|
+
all_valid = commands.all? do |cmd|
|
|
203
|
+
cmd.command.is_a?(String) &&
|
|
204
|
+
cmd.μs.is_a?(Integer) &&
|
|
205
|
+
cmd.timeline.is_a?(Float)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
[commands.size, commands.any?(nil), all_valid]
|
|
209
|
+
#=> [30, false, true]
|
|
210
|
+
|
|
211
|
+
## Concurrent pipelined operations with varying sizes don't cause corruption
|
|
212
|
+
# REMOVED: This test was checking exact pipeline command count which can vary
|
|
213
|
+
# due to intentionally non-atomic append_command trimming logic.
|
|
214
|
+
# See database_logger.rb:214-217 - we don't care about exact count when trimming.
|
|
215
|
+
# The important invariants (no nil entries, successful operations) are tested elsewhere.
|
|
216
|
+
|
|
217
|
+
## AtomicFixnum counter increments correctly under high contention
|
|
218
|
+
DatabaseLogger.clear_commands
|
|
219
|
+
DatabaseLogger.sample_rate = 1.0 # Track every increment but log everything
|
|
220
|
+
counter_start = DatabaseLogger.instance_variable_get(:@sample_counter).value
|
|
221
|
+
|
|
222
|
+
barrier = Concurrent::CyclicBarrier.new(100)
|
|
223
|
+
threads = []
|
|
224
|
+
dbclient = Familia.dbclient # Get connection in main thread (has middleware)
|
|
225
|
+
|
|
226
|
+
100.times do |i|
|
|
227
|
+
threads << Thread.new do
|
|
228
|
+
barrier.wait
|
|
229
|
+
dbclient.set("counter_test_#{i}", "val")
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
threads.each(&:join)
|
|
234
|
+
counter_end = DatabaseLogger.instance_variable_get(:@sample_counter).value
|
|
235
|
+
commands = DatabaseLogger.commands
|
|
236
|
+
|
|
237
|
+
# Counter should have incremented by exactly 100
|
|
238
|
+
# Commands should have all 100 entries with no nil values
|
|
239
|
+
[counter_end - counter_start, commands.size, commands.any?(nil)]
|
|
240
|
+
#=> [100, 100, false]
|
|
241
|
+
|
|
242
|
+
# Teardown: Reset to defaults
|
|
243
|
+
DatabaseLogger.sample_rate = nil
|
|
244
|
+
DatabaseLogger.structured_logging = false
|
|
245
|
+
DatabaseLogger.clear_commands
|
data/try/unit/core/tools_try.rb
CHANGED
data/try/unit/core/utils_try.rb
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# try/unit/core/utils_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
1
5
|
# try/core/utils_try.rb
|
|
2
6
|
|
|
3
7
|
require_relative '../../support/helpers/test_helpers'
|
|
@@ -85,6 +89,39 @@ rescue Familia::NotDistinguishableError => e
|
|
|
85
89
|
end
|
|
86
90
|
#=> Familia::NotDistinguishableError
|
|
87
91
|
|
|
92
|
+
##
|
|
93
|
+
## Time utilities
|
|
94
|
+
##
|
|
95
|
+
|
|
96
|
+
## now returns Float timestamp in seconds
|
|
97
|
+
time_result = Familia.now
|
|
98
|
+
[time_result.is_a?(Float), time_result > 0]
|
|
99
|
+
#=> [true, true]
|
|
100
|
+
|
|
101
|
+
## now accepts Time argument
|
|
102
|
+
test_time = Time.utc(2023, 6, 15, 14, 30, 0)
|
|
103
|
+
result_time = Familia.now(test_time)
|
|
104
|
+
[result_time.is_a?(Float), result_time == test_time.utc.to_f]
|
|
105
|
+
#=> [true, true]
|
|
106
|
+
|
|
107
|
+
## now_in_μs returns Integer microseconds
|
|
108
|
+
μs_result = Familia.now_in_μs
|
|
109
|
+
[μs_result.is_a?(Integer), μs_result > 0]
|
|
110
|
+
#=> [true, true]
|
|
111
|
+
|
|
112
|
+
## now_in_microseconds is an alias for now_in_μs
|
|
113
|
+
alias_result = Familia.now_in_microseconds
|
|
114
|
+
[alias_result.is_a?(Integer), alias_result > 0]
|
|
115
|
+
#=> [true, true]
|
|
116
|
+
|
|
117
|
+
## now_in_μs returns monotonic time suitable for duration measurement
|
|
118
|
+
start = Familia.now_in_μs
|
|
119
|
+
sleep 0.001 # Sleep for at least 1ms
|
|
120
|
+
finish = Familia.now_in_μs
|
|
121
|
+
duration = finish - start
|
|
122
|
+
[duration.is_a?(Integer), duration >= 1000] # At least 1000 microseconds (1ms)
|
|
123
|
+
#=> [true, true]
|
|
124
|
+
|
|
88
125
|
# Cleanup - restore defaults, leave nothing but footprints
|
|
89
126
|
Familia.delim(':')
|
|
90
127
|
Familia.suffix(:object)
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
# try/unit/data_types/boolean_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
1
5
|
# try/data_types/boolean_try.rb
|
|
6
|
+
# Issue #190: Updated to reflect JSON serialization with type preservation
|
|
2
7
|
|
|
3
8
|
require_relative '../../support/helpers/test_helpers'
|
|
4
9
|
|
|
@@ -6,37 +11,49 @@ Familia.debug = false
|
|
|
6
11
|
|
|
7
12
|
@hashkey = Familia::HashKey.new 'key'
|
|
8
13
|
|
|
9
|
-
##
|
|
14
|
+
## String 'true' is stored and returned as string
|
|
10
15
|
@hashkey['test'] = 'true'
|
|
11
16
|
#=> "true"
|
|
12
17
|
|
|
13
|
-
##
|
|
18
|
+
## String values are returned as strings
|
|
14
19
|
@hashkey['test']
|
|
15
20
|
#=> "true"
|
|
16
21
|
|
|
17
|
-
##
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
rescue Familia::NotDistinguishableError => e
|
|
21
|
-
e.message
|
|
22
|
-
end
|
|
23
|
-
#=> "Cannot represent true<TrueClass> as a string"
|
|
22
|
+
## Boolean true is now stored with type preservation (Issue #190)
|
|
23
|
+
@hashkey['bool_true'] = true
|
|
24
|
+
#=> true
|
|
24
25
|
|
|
25
|
-
## Boolean
|
|
26
|
-
@hashkey['
|
|
27
|
-
#=>
|
|
26
|
+
## Boolean true is returned as TrueClass
|
|
27
|
+
@hashkey['bool_true']
|
|
28
|
+
#=> true
|
|
28
29
|
|
|
29
|
-
##
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
rescue Familia::NotDistinguishableError => e
|
|
33
|
-
e.message
|
|
34
|
-
end
|
|
35
|
-
#=> "Cannot represent <NilClass> as a string"
|
|
30
|
+
## Boolean true has correct class
|
|
31
|
+
@hashkey['bool_true'].class
|
|
32
|
+
#=> TrueClass
|
|
36
33
|
|
|
37
|
-
##
|
|
38
|
-
@hashkey['
|
|
39
|
-
#=>
|
|
34
|
+
## Boolean false is stored with type preservation
|
|
35
|
+
@hashkey['bool_false'] = false
|
|
36
|
+
#=> false
|
|
37
|
+
|
|
38
|
+
## Boolean false is returned as FalseClass
|
|
39
|
+
@hashkey['bool_false']
|
|
40
|
+
#=> false
|
|
41
|
+
|
|
42
|
+
## Boolean false has correct class
|
|
43
|
+
@hashkey['bool_false'].class
|
|
44
|
+
#=> FalseClass
|
|
45
|
+
|
|
46
|
+
## nil is stored with type preservation
|
|
47
|
+
@hashkey['nil_value'] = nil
|
|
48
|
+
#=> nil
|
|
49
|
+
|
|
50
|
+
## nil is returned as nil
|
|
51
|
+
@hashkey['nil_value']
|
|
52
|
+
#=> nil
|
|
53
|
+
|
|
54
|
+
## nil has correct class
|
|
55
|
+
@hashkey['nil_value'].class
|
|
56
|
+
#=> NilClass
|
|
40
57
|
|
|
41
58
|
## Clear the hash key
|
|
42
59
|
@hashkey.delete!
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# try/unit/data_types/hash_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
1
5
|
# try/data_types/hash_try.rb
|
|
2
6
|
|
|
3
7
|
require_relative '../../support/helpers/test_helpers'
|
|
@@ -46,8 +50,8 @@ require_relative '../../support/helpers/test_helpers'
|
|
|
46
50
|
@a.props.decrement 'counter', 60
|
|
47
51
|
#=> 40
|
|
48
52
|
|
|
49
|
-
## Familia::HashKey#values_at
|
|
53
|
+
## Familia::HashKey#values_at (counter is integer from HINCRBY, others are strings)
|
|
50
54
|
@a.props.values_at 'fieldA', 'counter', 'fieldC'
|
|
51
|
-
#=> ['1',
|
|
55
|
+
#=> ['1', 40, '3']
|
|
52
56
|
|
|
53
57
|
@a.props.delete!
|