familia 2.0.0.pre19 → 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 +177 -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/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 +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 +2 -0
- data/lib/familia/connection/operations.rb +2 -0
- data/lib/familia/connection/pipelined_core.rb +2 -0
- data/lib/familia/connection/transaction_core.rb +68 -0
- 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 +6 -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 +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 +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 +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 +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 +138 -9
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +89 -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 +2 -0
- data/lib/familia/horreum/definition.rb +16 -6
- data/lib/familia/horreum/management.rb +212 -42
- data/lib/familia/horreum/persistence.rb +176 -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 +2 -0
- 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 +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 +2 -0
- 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 +4 -0
- 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 +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 +4 -0
- data/try/integration/data_types/datatype_transactions_try.rb +4 -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 +4 -0
- 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 +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 +4 -0
- 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 +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 +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 +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 +72 -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,156 @@
|
|
|
1
|
+
# try/unit/horreum/optimized_loading_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# Test optimized loading methods with check_exists parameter and pipelined bulk loading
|
|
6
|
+
|
|
7
|
+
require_relative '../../support/helpers/test_helpers'
|
|
8
|
+
|
|
9
|
+
OptimizedUser = Class.new(Familia::Horreum) do
|
|
10
|
+
identifier_field :user_id
|
|
11
|
+
field :user_id
|
|
12
|
+
field :name
|
|
13
|
+
field :email
|
|
14
|
+
field :age
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Setup: Create test users
|
|
18
|
+
setup_user1 = OptimizedUser.new(user_id: 'opt_user_1', name: 'Alice', email: 'alice@example.com', age: 30)
|
|
19
|
+
setup_user1.save
|
|
20
|
+
|
|
21
|
+
setup_user2 = OptimizedUser.new(user_id: 'opt_user_2', name: 'Bob', email: 'bob@example.com', age: 25)
|
|
22
|
+
setup_user2.save
|
|
23
|
+
|
|
24
|
+
setup_user3 = OptimizedUser.new(user_id: 'opt_user_3', name: 'Charlie', email: 'charlie@example.com', age: 35)
|
|
25
|
+
setup_user3.save
|
|
26
|
+
|
|
27
|
+
## find_by_dbkey with check_exists: true (default) returns object for existing key
|
|
28
|
+
found_user = OptimizedUser.find_by_dbkey(OptimizedUser.dbkey('opt_user_1'))
|
|
29
|
+
found_user.name
|
|
30
|
+
#=> 'Alice'
|
|
31
|
+
|
|
32
|
+
## find_by_dbkey with check_exists: true (default) returns nil for non-existent key
|
|
33
|
+
OptimizedUser.find_by_dbkey(OptimizedUser.dbkey('nonexistent'))
|
|
34
|
+
#=> nil
|
|
35
|
+
|
|
36
|
+
## find_by_dbkey with check_exists: false returns object for existing key
|
|
37
|
+
found_user_fast = OptimizedUser.find_by_dbkey(OptimizedUser.dbkey('opt_user_1'), check_exists: false)
|
|
38
|
+
found_user_fast.name
|
|
39
|
+
#=> 'Alice'
|
|
40
|
+
|
|
41
|
+
## find_by_dbkey with check_exists: false returns nil for non-existent key
|
|
42
|
+
OptimizedUser.find_by_dbkey(OptimizedUser.dbkey('nonexistent'), check_exists: false)
|
|
43
|
+
#=> nil
|
|
44
|
+
|
|
45
|
+
## find_by_dbkey with check_exists: false correctly deserializes all fields
|
|
46
|
+
fast_loaded = OptimizedUser.find_by_dbkey(OptimizedUser.dbkey('opt_user_2'), check_exists: false)
|
|
47
|
+
[fast_loaded.user_id, fast_loaded.name, fast_loaded.email, fast_loaded.age]
|
|
48
|
+
#=> ['opt_user_2', 'Bob', 'bob@example.com', 25]
|
|
49
|
+
|
|
50
|
+
## find_by_identifier with check_exists: true (default) returns object
|
|
51
|
+
OptimizedUser.find_by_identifier('opt_user_1').name
|
|
52
|
+
#=> 'Alice'
|
|
53
|
+
|
|
54
|
+
## find_by_identifier with check_exists: false returns object
|
|
55
|
+
OptimizedUser.find_by_identifier('opt_user_1', check_exists: false).name
|
|
56
|
+
#=> 'Alice'
|
|
57
|
+
|
|
58
|
+
## find_by_identifier with check_exists: false returns nil for non-existent
|
|
59
|
+
OptimizedUser.find_by_identifier('nonexistent', check_exists: false)
|
|
60
|
+
#=> nil
|
|
61
|
+
|
|
62
|
+
## find_by_id alias works with check_exists parameter
|
|
63
|
+
OptimizedUser.find_by_id('opt_user_2', check_exists: false).email
|
|
64
|
+
#=> 'bob@example.com'
|
|
65
|
+
|
|
66
|
+
## find alias works with check_exists parameter
|
|
67
|
+
OptimizedUser.find('opt_user_3', check_exists: false).age
|
|
68
|
+
#=> 35
|
|
69
|
+
|
|
70
|
+
## load alias works with check_exists parameter
|
|
71
|
+
OptimizedUser.load('opt_user_1', check_exists: false).user_id
|
|
72
|
+
#=> 'opt_user_1'
|
|
73
|
+
|
|
74
|
+
## load_multi loads multiple existing objects
|
|
75
|
+
users = OptimizedUser.load_multi(['opt_user_1', 'opt_user_2', 'opt_user_3'])
|
|
76
|
+
users.map(&:name)
|
|
77
|
+
#=> ['Alice', 'Bob', 'Charlie']
|
|
78
|
+
|
|
79
|
+
## load_multi returns nils for non-existent objects in correct positions
|
|
80
|
+
users_mixed = OptimizedUser.load_multi(['opt_user_1', 'nonexistent', 'opt_user_3'])
|
|
81
|
+
[users_mixed[0]&.name, users_mixed[1], users_mixed[2]&.name]
|
|
82
|
+
#=> ['Alice', nil, 'Charlie']
|
|
83
|
+
|
|
84
|
+
## load_multi with compact filters out nils
|
|
85
|
+
users_compact = OptimizedUser.load_multi(['opt_user_1', 'nonexistent', 'opt_user_2']).compact
|
|
86
|
+
users_compact.map(&:name)
|
|
87
|
+
#=> ['Alice', 'Bob']
|
|
88
|
+
|
|
89
|
+
## load_multi preserves order of identifiers
|
|
90
|
+
users_ordered = OptimizedUser.load_multi(['opt_user_3', 'opt_user_1', 'opt_user_2'])
|
|
91
|
+
users_ordered.map(&:user_id)
|
|
92
|
+
#=> ['opt_user_3', 'opt_user_1', 'opt_user_2']
|
|
93
|
+
|
|
94
|
+
## load_multi handles empty array
|
|
95
|
+
OptimizedUser.load_multi([])
|
|
96
|
+
#=> []
|
|
97
|
+
|
|
98
|
+
## load_multi handles all non-existent identifiers
|
|
99
|
+
all_missing = OptimizedUser.load_multi(['missing1', 'missing2'])
|
|
100
|
+
all_missing.compact
|
|
101
|
+
#=> []
|
|
102
|
+
|
|
103
|
+
## load_multi correctly deserializes all field types
|
|
104
|
+
multi_loaded = OptimizedUser.load_multi(['opt_user_2']).first
|
|
105
|
+
[multi_loaded.user_id, multi_loaded.name, multi_loaded.email, multi_loaded.age]
|
|
106
|
+
#=> ['opt_user_2', 'Bob', 'bob@example.com', 25]
|
|
107
|
+
|
|
108
|
+
## load_batch alias works
|
|
109
|
+
batch_users = OptimizedUser.load_batch(['opt_user_1', 'opt_user_2'])
|
|
110
|
+
batch_users.map(&:name)
|
|
111
|
+
#=> ['Alice', 'Bob']
|
|
112
|
+
|
|
113
|
+
## load_multi_by_keys loads by full dbkeys
|
|
114
|
+
keys = [OptimizedUser.dbkey('opt_user_1'), OptimizedUser.dbkey('opt_user_2')]
|
|
115
|
+
keyed_users = OptimizedUser.load_multi_by_keys(keys)
|
|
116
|
+
keyed_users.map(&:name)
|
|
117
|
+
#=> ['Alice', 'Bob']
|
|
118
|
+
|
|
119
|
+
## load_multi_by_keys returns nil for non-existent keys
|
|
120
|
+
keys_mixed = [OptimizedUser.dbkey('opt_user_1'), OptimizedUser.dbkey('nonexistent')]
|
|
121
|
+
keyed_mixed = OptimizedUser.load_multi_by_keys(keys_mixed)
|
|
122
|
+
[keyed_mixed[0]&.name, keyed_mixed[1]]
|
|
123
|
+
#=> ['Alice', nil]
|
|
124
|
+
|
|
125
|
+
## load_multi_by_keys handles empty array
|
|
126
|
+
OptimizedUser.load_multi_by_keys([])
|
|
127
|
+
#=> []
|
|
128
|
+
|
|
129
|
+
## load_multi_by_keys handles empty/nil keys and maintains position alignment
|
|
130
|
+
keys_with_empty = [OptimizedUser.dbkey('opt_user_1'), '', OptimizedUser.dbkey('opt_user_2'), nil]
|
|
131
|
+
mixed_keys = OptimizedUser.load_multi_by_keys(keys_with_empty)
|
|
132
|
+
[mixed_keys[0]&.name, mixed_keys[1], mixed_keys[2]&.name, mixed_keys[3]]
|
|
133
|
+
#=> ['Alice', nil, 'Bob', nil]
|
|
134
|
+
|
|
135
|
+
## load_multi handles nil identifiers gracefully
|
|
136
|
+
users_with_nils = OptimizedUser.load_multi(['opt_user_1', nil, 'opt_user_2'])
|
|
137
|
+
[users_with_nils[0]&.name, users_with_nils[1], users_with_nils[2]&.name]
|
|
138
|
+
#=> ['Alice', nil, 'Bob']
|
|
139
|
+
|
|
140
|
+
## load_multi handles empty string identifiers
|
|
141
|
+
users_with_empty = OptimizedUser.load_multi(['opt_user_1', '', 'opt_user_2'])
|
|
142
|
+
[users_with_empty[0]&.name, users_with_empty[1], users_with_empty[2]&.name]
|
|
143
|
+
#=> ['Alice', nil, 'Bob']
|
|
144
|
+
|
|
145
|
+
## find_by_identifier works with suffix as keyword parameter
|
|
146
|
+
OptimizedUser.find_by_identifier('opt_user_1', suffix: :object)&.name
|
|
147
|
+
#=> 'Alice'
|
|
148
|
+
|
|
149
|
+
## find_by_identifier works with both keyword parameters
|
|
150
|
+
OptimizedUser.find_by_identifier('opt_user_1', suffix: :object, check_exists: false)&.name
|
|
151
|
+
#=> 'Alice'
|
|
152
|
+
|
|
153
|
+
# Teardown: Clean up test data
|
|
154
|
+
OptimizedUser.destroy!('opt_user_1')
|
|
155
|
+
OptimizedUser.destroy!('opt_user_2')
|
|
156
|
+
OptimizedUser.destroy!('opt_user_3')
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# try/unit/middleware/database_command_counter_methods_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# Test DatabaseCommandCounter non-command methods and utilities
|
|
6
|
+
#
|
|
7
|
+
# This test file covers utility methods, configuration methods,
|
|
8
|
+
# and state management methods that don't involve command execution.
|
|
9
|
+
#
|
|
10
|
+
# Covers:
|
|
11
|
+
# - Counter methods (count, reset, increment, count_commands)
|
|
12
|
+
# - Configuration (skip_commands)
|
|
13
|
+
# - Utility methods (skip_command?)
|
|
14
|
+
# - Thread safety of atomic counter
|
|
15
|
+
|
|
16
|
+
require_relative '../../support/helpers/test_helpers'
|
|
17
|
+
require 'concurrent-ruby'
|
|
18
|
+
|
|
19
|
+
@original_count = DatabaseCommandCounter.count
|
|
20
|
+
|
|
21
|
+
# Clear initial state
|
|
22
|
+
DatabaseCommandCounter.reset
|
|
23
|
+
|
|
24
|
+
## count returns the current command count
|
|
25
|
+
DatabaseCommandCounter.reset
|
|
26
|
+
DatabaseCommandCounter.count
|
|
27
|
+
#=> 0
|
|
28
|
+
|
|
29
|
+
## reset sets count to zero
|
|
30
|
+
DatabaseCommandCounter.increment
|
|
31
|
+
DatabaseCommandCounter.increment
|
|
32
|
+
DatabaseCommandCounter.reset
|
|
33
|
+
DatabaseCommandCounter.count
|
|
34
|
+
#=> 0
|
|
35
|
+
|
|
36
|
+
## reset returns zero
|
|
37
|
+
result = DatabaseCommandCounter.reset
|
|
38
|
+
result
|
|
39
|
+
#=> 0
|
|
40
|
+
|
|
41
|
+
## increment increases the count by 1
|
|
42
|
+
DatabaseCommandCounter.reset
|
|
43
|
+
DatabaseCommandCounter.increment
|
|
44
|
+
DatabaseCommandCounter.count
|
|
45
|
+
#=> 1
|
|
46
|
+
|
|
47
|
+
## increment returns the new count
|
|
48
|
+
DatabaseCommandCounter.reset
|
|
49
|
+
result = DatabaseCommandCounter.increment
|
|
50
|
+
result
|
|
51
|
+
#=> 1
|
|
52
|
+
|
|
53
|
+
## increment can be called multiple times
|
|
54
|
+
DatabaseCommandCounter.reset
|
|
55
|
+
3.times { DatabaseCommandCounter.increment }
|
|
56
|
+
DatabaseCommandCounter.count
|
|
57
|
+
#=> 3
|
|
58
|
+
|
|
59
|
+
## skip_commands returns a Set with default skipped commands
|
|
60
|
+
DatabaseCommandCounter.skip_commands.class
|
|
61
|
+
#=> Set
|
|
62
|
+
|
|
63
|
+
## skip_commands includes SELECT by default
|
|
64
|
+
DatabaseCommandCounter.skip_commands.include?("SELECT")
|
|
65
|
+
#=> true
|
|
66
|
+
|
|
67
|
+
## skip_commands is frozen for immutability
|
|
68
|
+
DatabaseCommandCounter.skip_commands.frozen?
|
|
69
|
+
#=> true
|
|
70
|
+
|
|
71
|
+
## skip_command? returns true for commands in skip_commands
|
|
72
|
+
DatabaseCommandCounter.skip_command?(["SELECT", "0"])
|
|
73
|
+
#=> true
|
|
74
|
+
|
|
75
|
+
## skip_command? returns false for commands not in skip_commands
|
|
76
|
+
DatabaseCommandCounter.skip_command?(["SET", "key", "value"])
|
|
77
|
+
#=> false
|
|
78
|
+
|
|
79
|
+
## skip_command? handles command array properly
|
|
80
|
+
DatabaseCommandCounter.skip_command?(["GET", "key"])
|
|
81
|
+
#=> false
|
|
82
|
+
|
|
83
|
+
## skip_command? is case insensitive
|
|
84
|
+
DatabaseCommandCounter.skip_command?(["select", "0"])
|
|
85
|
+
#=> true
|
|
86
|
+
|
|
87
|
+
## count_commands captures count difference in block
|
|
88
|
+
DatabaseCommandCounter.reset
|
|
89
|
+
initial_count = DatabaseCommandCounter.count
|
|
90
|
+
commands_executed = DatabaseCommandCounter.count_commands do
|
|
91
|
+
5.times { DatabaseCommandCounter.increment }
|
|
92
|
+
end
|
|
93
|
+
commands_executed
|
|
94
|
+
#=> 5
|
|
95
|
+
|
|
96
|
+
## count_commands works with empty block
|
|
97
|
+
DatabaseCommandCounter.reset
|
|
98
|
+
commands_executed = DatabaseCommandCounter.count_commands do
|
|
99
|
+
# No commands
|
|
100
|
+
end
|
|
101
|
+
commands_executed
|
|
102
|
+
#=> 0
|
|
103
|
+
|
|
104
|
+
## count_commands captures count difference with existing count
|
|
105
|
+
DatabaseCommandCounter.reset
|
|
106
|
+
5.times { DatabaseCommandCounter.increment } # Existing commands
|
|
107
|
+
commands_executed = DatabaseCommandCounter.count_commands do
|
|
108
|
+
3.times { DatabaseCommandCounter.increment } # New commands in block
|
|
109
|
+
end
|
|
110
|
+
commands_executed
|
|
111
|
+
#=> 3
|
|
112
|
+
|
|
113
|
+
## Thread safety: increment is thread-safe with concurrent access
|
|
114
|
+
DatabaseCommandCounter.reset
|
|
115
|
+
threads = 10.times.map do |i|
|
|
116
|
+
Thread.new do
|
|
117
|
+
10.times { DatabaseCommandCounter.increment }
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
threads.each(&:join)
|
|
122
|
+
DatabaseCommandCounter.count
|
|
123
|
+
#=> 100
|
|
124
|
+
|
|
125
|
+
## Thread safety: count_commands is thread-safe
|
|
126
|
+
DatabaseCommandCounter.reset
|
|
127
|
+
result = DatabaseCommandCounter.count_commands do
|
|
128
|
+
threads = 5.times.map do
|
|
129
|
+
Thread.new do
|
|
130
|
+
10.times { DatabaseCommandCounter.increment }
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
threads.each(&:join)
|
|
134
|
+
end
|
|
135
|
+
result
|
|
136
|
+
#=> 50
|
|
137
|
+
|
|
138
|
+
# Restore original state (if needed)
|
|
139
|
+
DatabaseCommandCounter.reset
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# try/unit/middleware/database_logger_methods_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# Test DatabaseLogger non-command methods and utilities
|
|
6
|
+
#
|
|
7
|
+
# This test file covers utility methods, configuration methods,
|
|
8
|
+
# and state management methods that don't involve command execution.
|
|
9
|
+
#
|
|
10
|
+
# Covers:
|
|
11
|
+
# - Configuration getters/setters (logger, max_commands, structured_logging, sample_rate)
|
|
12
|
+
# - Command storage methods (clear_commands, append_command, capture_commands)
|
|
13
|
+
# - Utility methods (index, now_in_μs, should_log?)
|
|
14
|
+
# - State introspection (commands, process_start)
|
|
15
|
+
# - Thread safety of command storage
|
|
16
|
+
|
|
17
|
+
require_relative '../../support/helpers/test_helpers'
|
|
18
|
+
require 'logger'
|
|
19
|
+
require 'stringio'
|
|
20
|
+
require 'concurrent-ruby'
|
|
21
|
+
|
|
22
|
+
@original_logger = DatabaseLogger.logger
|
|
23
|
+
@original_max_commands = DatabaseLogger.max_commands
|
|
24
|
+
@original_structured_logging = DatabaseLogger.structured_logging
|
|
25
|
+
@original_sample_rate = DatabaseLogger.sample_rate
|
|
26
|
+
|
|
27
|
+
# Clear initial state
|
|
28
|
+
DatabaseLogger.clear_commands
|
|
29
|
+
DatabaseLogger.logger = nil
|
|
30
|
+
DatabaseLogger.max_commands = 10_000
|
|
31
|
+
DatabaseLogger.structured_logging = false
|
|
32
|
+
DatabaseLogger.sample_rate = nil
|
|
33
|
+
|
|
34
|
+
## logger getter returns the current logger instance
|
|
35
|
+
test_logger = Logger.new(StringIO.new)
|
|
36
|
+
DatabaseLogger.logger = test_logger
|
|
37
|
+
DatabaseLogger.logger == test_logger
|
|
38
|
+
#=> true
|
|
39
|
+
|
|
40
|
+
## logger can be set to nil
|
|
41
|
+
DatabaseLogger.logger = nil
|
|
42
|
+
DatabaseLogger.logger
|
|
43
|
+
#=> nil
|
|
44
|
+
|
|
45
|
+
## max_commands getter returns the current max commands value
|
|
46
|
+
DatabaseLogger.max_commands = 5_000
|
|
47
|
+
DatabaseLogger.max_commands
|
|
48
|
+
#=> 5000
|
|
49
|
+
|
|
50
|
+
## max_commands can be set to different values
|
|
51
|
+
DatabaseLogger.max_commands = 1_000
|
|
52
|
+
DatabaseLogger.max_commands
|
|
53
|
+
#=> 1000
|
|
54
|
+
|
|
55
|
+
## structured_logging getter returns the current structured logging mode
|
|
56
|
+
DatabaseLogger.structured_logging = true
|
|
57
|
+
DatabaseLogger.structured_logging
|
|
58
|
+
#=> true
|
|
59
|
+
|
|
60
|
+
## structured_logging can be toggled
|
|
61
|
+
DatabaseLogger.structured_logging = false
|
|
62
|
+
DatabaseLogger.structured_logging
|
|
63
|
+
#=> false
|
|
64
|
+
|
|
65
|
+
## sample_rate getter returns the current sample rate
|
|
66
|
+
DatabaseLogger.sample_rate = 0.5
|
|
67
|
+
DatabaseLogger.sample_rate
|
|
68
|
+
#=> 0.5
|
|
69
|
+
|
|
70
|
+
## sample_rate can be set to nil
|
|
71
|
+
DatabaseLogger.sample_rate = nil
|
|
72
|
+
DatabaseLogger.sample_rate
|
|
73
|
+
#=> nil
|
|
74
|
+
|
|
75
|
+
## commands getter returns the captured commands array
|
|
76
|
+
DatabaseLogger.clear_commands
|
|
77
|
+
commands = DatabaseLogger.commands
|
|
78
|
+
commands.class
|
|
79
|
+
#=> Concurrent::Array
|
|
80
|
+
|
|
81
|
+
## commands array is initially empty after clear
|
|
82
|
+
DatabaseLogger.clear_commands
|
|
83
|
+
DatabaseLogger.commands.empty?
|
|
84
|
+
#=> true
|
|
85
|
+
|
|
86
|
+
## process_start returns a float timestamp
|
|
87
|
+
DatabaseLogger.process_start.class
|
|
88
|
+
#=> Float
|
|
89
|
+
|
|
90
|
+
## process_start is frozen
|
|
91
|
+
DatabaseLogger.process_start.frozen?
|
|
92
|
+
#=> true
|
|
93
|
+
|
|
94
|
+
## clear_commands returns nil and empties the commands array
|
|
95
|
+
DatabaseLogger.instance_variable_get(:@commands) << "test"
|
|
96
|
+
result = DatabaseLogger.clear_commands
|
|
97
|
+
[result, DatabaseLogger.commands.empty?]
|
|
98
|
+
#=> [nil, true]
|
|
99
|
+
|
|
100
|
+
## index returns the current count of commands
|
|
101
|
+
DatabaseLogger.clear_commands
|
|
102
|
+
DatabaseLogger.index
|
|
103
|
+
#=> 0
|
|
104
|
+
|
|
105
|
+
## index increases as commands are added
|
|
106
|
+
DatabaseLogger.clear_commands
|
|
107
|
+
msg = DatabaseLogger::CommandMessage.new("TEST", 100, 0.001)
|
|
108
|
+
DatabaseLogger.append_command(msg)
|
|
109
|
+
DatabaseLogger.index
|
|
110
|
+
#=> 1
|
|
111
|
+
|
|
112
|
+
## append_command adds a message to the commands array
|
|
113
|
+
DatabaseLogger.clear_commands
|
|
114
|
+
msg = DatabaseLogger::CommandMessage.new("SET key value", 500, 0.002)
|
|
115
|
+
result = DatabaseLogger.append_command(msg)
|
|
116
|
+
[DatabaseLogger.commands.size, result.last == msg]
|
|
117
|
+
#=> [1, true]
|
|
118
|
+
|
|
119
|
+
## append_command respects max_commands limit by shifting oldest
|
|
120
|
+
DatabaseLogger.max_commands = 3
|
|
121
|
+
DatabaseLogger.clear_commands
|
|
122
|
+
msg1 = DatabaseLogger::CommandMessage.new("CMD1", 100, 0.001)
|
|
123
|
+
msg2 = DatabaseLogger::CommandMessage.new("CMD2", 200, 0.002)
|
|
124
|
+
msg3 = DatabaseLogger::CommandMessage.new("CMD3", 300, 0.003)
|
|
125
|
+
msg4 = DatabaseLogger::CommandMessage.new("CMD4", 400, 0.004)
|
|
126
|
+
|
|
127
|
+
DatabaseLogger.append_command(msg1)
|
|
128
|
+
DatabaseLogger.append_command(msg2)
|
|
129
|
+
DatabaseLogger.append_command(msg3)
|
|
130
|
+
DatabaseLogger.append_command(msg4)
|
|
131
|
+
|
|
132
|
+
[DatabaseLogger.commands.size, DatabaseLogger.commands.first == msg2]
|
|
133
|
+
#=> [3, true]
|
|
134
|
+
|
|
135
|
+
## now_in_μs returns current time in microseconds
|
|
136
|
+
time1 = DatabaseLogger.now_in_μs
|
|
137
|
+
sleep(0.001)
|
|
138
|
+
time2 = DatabaseLogger.now_in_μs
|
|
139
|
+
time2 > time1
|
|
140
|
+
#=> true
|
|
141
|
+
|
|
142
|
+
## now_in_microseconds is an alias for now_in_μs
|
|
143
|
+
DatabaseLogger.method(:now_in_microseconds) == DatabaseLogger.method(:now_in_μs)
|
|
144
|
+
#=> true
|
|
145
|
+
|
|
146
|
+
## capture_commands yields and returns captured commands
|
|
147
|
+
DatabaseLogger.clear_commands
|
|
148
|
+
commands = DatabaseLogger.capture_commands do
|
|
149
|
+
msg = DatabaseLogger::CommandMessage.new("GET key", 150, 0.001)
|
|
150
|
+
DatabaseLogger.append_command(msg)
|
|
151
|
+
msg2 = DatabaseLogger::CommandMessage.new("SET key2 value", 200, 0.002)
|
|
152
|
+
DatabaseLogger.append_command(msg2)
|
|
153
|
+
end
|
|
154
|
+
commands.size
|
|
155
|
+
#=> 2
|
|
156
|
+
|
|
157
|
+
## capture_commands clears commands before capturing
|
|
158
|
+
DatabaseLogger.clear_commands
|
|
159
|
+
msg_before = DatabaseLogger::CommandMessage.new("BEFORE", 100, 0.001)
|
|
160
|
+
DatabaseLogger.append_command(msg_before)
|
|
161
|
+
|
|
162
|
+
commands = DatabaseLogger.capture_commands do
|
|
163
|
+
msg = DatabaseLogger::CommandMessage.new("DURING", 150, 0.002)
|
|
164
|
+
DatabaseLogger.append_command(msg)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
[commands.size, commands.first.command]
|
|
168
|
+
#=> [1, "DURING"]
|
|
169
|
+
|
|
170
|
+
## capture_commands returns array snapshot, not live reference
|
|
171
|
+
DatabaseLogger.clear_commands
|
|
172
|
+
commands = DatabaseLogger.capture_commands do
|
|
173
|
+
msg = DatabaseLogger::CommandMessage.new("TEST", 100, 0.001)
|
|
174
|
+
DatabaseLogger.append_command(msg)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Add another command after capture
|
|
178
|
+
msg2 = DatabaseLogger::CommandMessage.new("AFTER", 200, 0.002)
|
|
179
|
+
DatabaseLogger.append_command(msg2)
|
|
180
|
+
|
|
181
|
+
[commands.size, DatabaseLogger.commands.size]
|
|
182
|
+
#=> [1, 2]
|
|
183
|
+
|
|
184
|
+
## should_log? returns true when sample_rate is nil
|
|
185
|
+
DatabaseLogger.sample_rate = nil
|
|
186
|
+
DatabaseLogger.logger = Logger.new(StringIO.new)
|
|
187
|
+
DatabaseLogger.should_log?
|
|
188
|
+
#=> true
|
|
189
|
+
|
|
190
|
+
## should_log? returns false when logger is nil regardless of sample_rate
|
|
191
|
+
DatabaseLogger.sample_rate = 1.0
|
|
192
|
+
DatabaseLogger.logger = nil
|
|
193
|
+
DatabaseLogger.should_log?
|
|
194
|
+
#=> false
|
|
195
|
+
|
|
196
|
+
## should_log? uses atomic counter for thread-safe sampling
|
|
197
|
+
DatabaseLogger.sample_rate = 0.5 # 50% sampling
|
|
198
|
+
DatabaseLogger.logger = Logger.new(StringIO.new)
|
|
199
|
+
DatabaseLogger.instance_variable_set(:@sample_counter, Concurrent::AtomicFixnum.new(0))
|
|
200
|
+
|
|
201
|
+
# Test deterministic sampling pattern
|
|
202
|
+
results = 10.times.map { DatabaseLogger.should_log? }
|
|
203
|
+
results.count(true)
|
|
204
|
+
#=> 5
|
|
205
|
+
|
|
206
|
+
## should_log? sampling is deterministic and consistent
|
|
207
|
+
DatabaseLogger.sample_rate = 0.25 # 25% sampling (every 4th)
|
|
208
|
+
DatabaseLogger.instance_variable_set(:@sample_counter, Concurrent::AtomicFixnum.new(0))
|
|
209
|
+
|
|
210
|
+
results = 12.times.map { DatabaseLogger.should_log? }
|
|
211
|
+
results.count(true)
|
|
212
|
+
#=> 3
|
|
213
|
+
|
|
214
|
+
## CommandMessage can be created with command, duration, timeline
|
|
215
|
+
msg = DatabaseLogger::CommandMessage.new("SET key value", 1500, 0.123456)
|
|
216
|
+
[msg.command, msg.μs, msg.timeline]
|
|
217
|
+
#=> ["SET key value", 1500, 0.123456]
|
|
218
|
+
|
|
219
|
+
## CommandMessage.inspect formats nicely
|
|
220
|
+
msg = DatabaseLogger::CommandMessage.new("GET key", 2500, 1.234567)
|
|
221
|
+
msg.inspect
|
|
222
|
+
#=> "1.234567 2500μs > GET key"
|
|
223
|
+
|
|
224
|
+
## CommandMessage.to_a returns deconstructed array
|
|
225
|
+
msg = DatabaseLogger::CommandMessage.new("DEL key1 key2", 750, 2.345678)
|
|
226
|
+
msg.to_a
|
|
227
|
+
#=> ["DEL key1 key2", 750, 2.345678]
|
|
228
|
+
|
|
229
|
+
## Thread safety: append_command is thread-safe with concurrent access
|
|
230
|
+
# Reset max_commands to allow all 100 commands
|
|
231
|
+
DatabaseLogger.max_commands = 1000
|
|
232
|
+
DatabaseLogger.clear_commands
|
|
233
|
+
threads = 10.times.map do |i|
|
|
234
|
+
Thread.new do
|
|
235
|
+
10.times do |j|
|
|
236
|
+
msg = DatabaseLogger::CommandMessage.new("THREAD#{i}_CMD#{j}", 100, 0.001)
|
|
237
|
+
DatabaseLogger.append_command(msg)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
threads.each(&:join)
|
|
243
|
+
DatabaseLogger.commands.size
|
|
244
|
+
#=> 100
|
|
245
|
+
|
|
246
|
+
# Restore original state
|
|
247
|
+
DatabaseLogger.logger = @original_logger
|
|
248
|
+
DatabaseLogger.max_commands = @original_max_commands
|
|
249
|
+
DatabaseLogger.structured_logging = @original_structured_logging
|
|
250
|
+
DatabaseLogger.sample_rate = @original_sample_rate
|
|
251
|
+
DatabaseLogger.clear_commands
|