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
data/lib/multi_result.rb
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
# lib/multi_result.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
|
-
# Represents the result of a Valkey/Redis transaction operation.
|
|
5
|
+
# Represents the result of a Valkey/Redis transaction or pipeline operation.
|
|
6
|
+
#
|
|
7
|
+
# This class encapsulates the outcome of a Database multi-command operation,
|
|
8
|
+
# providing access to both the command results and derived success status
|
|
9
|
+
# based on the presence of errors in the results.
|
|
4
10
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
11
|
+
# Success is determined by checking for Exception objects in the results array.
|
|
12
|
+
# When Redis commands fail within a transaction or pipeline, they return
|
|
13
|
+
# exception objects rather than raising them, allowing other commands to
|
|
14
|
+
# continue executing.
|
|
8
15
|
#
|
|
9
|
-
# @attr_reader
|
|
10
|
-
#
|
|
11
|
-
# @attr_reader results [Array<String>] Array of return values
|
|
12
|
-
# from the Database commands executed in the transaction.
|
|
16
|
+
# @attr_reader results [Array] Array of return values from the Database commands.
|
|
17
|
+
# Values can be strings, integers, booleans, or Exception objects for failed commands.
|
|
13
18
|
#
|
|
14
19
|
# @example Creating a MultiResult instance
|
|
15
|
-
# result = MultiResult.new(
|
|
20
|
+
# result = MultiResult.new(["OK", "OK", 1])
|
|
16
21
|
#
|
|
17
22
|
# @example Checking transaction success
|
|
18
23
|
# if result.successful?
|
|
19
|
-
# puts "
|
|
24
|
+
# puts "All commands completed without errors"
|
|
20
25
|
# else
|
|
21
|
-
# puts "
|
|
26
|
+
# puts "#{result.errors.size} command(s) failed"
|
|
22
27
|
# end
|
|
23
28
|
#
|
|
24
29
|
# @example Accessing individual command results
|
|
@@ -26,24 +31,55 @@
|
|
|
26
31
|
# puts "Command #{index + 1} returned: #{value}"
|
|
27
32
|
# end
|
|
28
33
|
#
|
|
34
|
+
# @example Inspecting errors
|
|
35
|
+
# if result.errors?
|
|
36
|
+
# result.errors.each do |error|
|
|
37
|
+
# puts "Error: #{error.message}"
|
|
38
|
+
# end
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
29
41
|
class MultiResult
|
|
30
|
-
# @return [
|
|
31
|
-
# false otherwise
|
|
32
|
-
attr_reader :success
|
|
33
|
-
|
|
34
|
-
# @return [Array<String>] The raw return values from the Database commands
|
|
42
|
+
# @return [Array] The raw return values from the Database commands
|
|
35
43
|
attr_reader :results
|
|
36
44
|
|
|
37
45
|
# Creates a new MultiResult instance.
|
|
38
46
|
#
|
|
39
|
-
# @param
|
|
40
|
-
#
|
|
41
|
-
def initialize(
|
|
42
|
-
@success = success
|
|
47
|
+
# @param results [Array] The raw results from Database commands.
|
|
48
|
+
# Exception objects in the array indicate command failures.
|
|
49
|
+
def initialize(results)
|
|
43
50
|
@results = results
|
|
44
51
|
end
|
|
45
52
|
|
|
46
|
-
# Returns
|
|
53
|
+
# Returns all Exception objects from the results array.
|
|
54
|
+
#
|
|
55
|
+
# This method is memoized for performance when called multiple times
|
|
56
|
+
# on the same MultiResult instance.
|
|
57
|
+
#
|
|
58
|
+
# @return [Array<Exception>] Array of exceptions that occurred during execution
|
|
59
|
+
def errors
|
|
60
|
+
@errors ||= results.select { |ret| ret.is_a?(Exception) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Checks if any errors occurred during execution.
|
|
64
|
+
#
|
|
65
|
+
# @return [Boolean] true if at least one command failed, false otherwise
|
|
66
|
+
def errors?
|
|
67
|
+
!errors.empty?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Checks if all commands completed successfully (no exceptions).
|
|
71
|
+
#
|
|
72
|
+
# This is the primary method for determining if a multi-command
|
|
73
|
+
# operation completed without errors.
|
|
74
|
+
#
|
|
75
|
+
# @return [Boolean] true if no exceptions in results, false otherwise
|
|
76
|
+
def successful?
|
|
77
|
+
errors.empty?
|
|
78
|
+
end
|
|
79
|
+
alias success? successful?
|
|
80
|
+
alias areyouhappynow? successful?
|
|
81
|
+
|
|
82
|
+
# Returns a tuple representing the result of the operation.
|
|
47
83
|
#
|
|
48
84
|
# @return [Array] A tuple containing the success status and the raw results.
|
|
49
85
|
# The success status is a boolean indicating if all commands succeeded.
|
|
@@ -59,21 +95,15 @@ class MultiResult
|
|
|
59
95
|
|
|
60
96
|
# Returns the number of results in the multi-operation.
|
|
61
97
|
#
|
|
62
|
-
# @return [Integer] The number of individual command results returned
|
|
98
|
+
# @return [Integer] The number of individual command results returned
|
|
63
99
|
def size
|
|
64
100
|
results.size
|
|
65
101
|
end
|
|
66
102
|
|
|
103
|
+
# Returns a hash representation of the result.
|
|
104
|
+
#
|
|
105
|
+
# @return [Hash] Hash with :success and :results keys
|
|
67
106
|
def to_h
|
|
68
107
|
{ success: successful?, results: results }
|
|
69
108
|
end
|
|
70
|
-
|
|
71
|
-
# Convenient method to check if the commit was successful.
|
|
72
|
-
#
|
|
73
|
-
# @return [Boolean] true if all commands succeeded, false otherwise
|
|
74
|
-
def successful?
|
|
75
|
-
@success
|
|
76
|
-
end
|
|
77
|
-
alias success? successful?
|
|
78
|
-
alias areyouhappynow? successful?
|
|
79
109
|
end
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
require_relative '../support/helpers/test_helpers'
|
|
2
|
+
|
|
3
|
+
# Test class for count/any edge case testing - synchronization issues
|
|
4
|
+
class EdgeCaseCustomer < Familia::Horreum
|
|
5
|
+
identifier_field :custid
|
|
6
|
+
field :custid
|
|
7
|
+
field :name
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Setup - clear any existing data
|
|
11
|
+
EdgeCaseCustomer.instances.clear
|
|
12
|
+
# Clear all keys matching the pattern
|
|
13
|
+
EdgeCaseCustomer.dbclient.scan_each(match: EdgeCaseCustomer.dbkey('*')) do |key|
|
|
14
|
+
EdgeCaseCustomer.dbclient.del(key)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# =============================================================================
|
|
18
|
+
# Edge Case 1: Phantom Instances (stale additions)
|
|
19
|
+
# Identifier exists in instances sorted set but object key doesn't exist
|
|
20
|
+
# =============================================================================
|
|
21
|
+
|
|
22
|
+
## EDGE: Phantom instance - count shows stale entry
|
|
23
|
+
# Manually add identifier to instances without creating object
|
|
24
|
+
EdgeCaseCustomer.instances.add('phantom1', Time.now.to_i)
|
|
25
|
+
EdgeCaseCustomer.count
|
|
26
|
+
#=> 1
|
|
27
|
+
|
|
28
|
+
## EDGE: Phantom instance - keys_count shows authoritative count (0)
|
|
29
|
+
EdgeCaseCustomer.keys_count
|
|
30
|
+
#=> 0
|
|
31
|
+
|
|
32
|
+
## EDGE: Phantom instance - scan_count shows authoritative count (0)
|
|
33
|
+
EdgeCaseCustomer.scan_count
|
|
34
|
+
#=> 0
|
|
35
|
+
|
|
36
|
+
## EDGE: Phantom instance - count! shows authoritative count (0)
|
|
37
|
+
EdgeCaseCustomer.count!
|
|
38
|
+
#=> 0
|
|
39
|
+
|
|
40
|
+
## EDGE: Phantom instance - any? returns true (stale)
|
|
41
|
+
EdgeCaseCustomer.any?
|
|
42
|
+
#=> true
|
|
43
|
+
|
|
44
|
+
## EDGE: Phantom instance - keys_any? returns false (authoritative)
|
|
45
|
+
EdgeCaseCustomer.keys_any?
|
|
46
|
+
#=> false
|
|
47
|
+
|
|
48
|
+
## EDGE: Phantom instance - scan_any? returns false (authoritative)
|
|
49
|
+
EdgeCaseCustomer.scan_any?
|
|
50
|
+
#=> false
|
|
51
|
+
|
|
52
|
+
## EDGE: Phantom instance - any! returns false (authoritative)
|
|
53
|
+
EdgeCaseCustomer.any!
|
|
54
|
+
#=> false
|
|
55
|
+
|
|
56
|
+
## EDGE: Phantom instance cleanup - clear instances
|
|
57
|
+
EdgeCaseCustomer.instances.clear
|
|
58
|
+
#=*> Integer
|
|
59
|
+
|
|
60
|
+
# =============================================================================
|
|
61
|
+
# Edge Case 2: Orphaned Objects
|
|
62
|
+
# Object exists in Redis but identifier NOT in instances sorted set
|
|
63
|
+
# =============================================================================
|
|
64
|
+
|
|
65
|
+
## EDGE: Orphaned object - create and remove from instances manually
|
|
66
|
+
@orphan = EdgeCaseCustomer.create!(custid: 'orphan1', name: 'Orphan')
|
|
67
|
+
EdgeCaseCustomer.instances.remove('orphan1')
|
|
68
|
+
# After removal, count should be 0 but object still exists
|
|
69
|
+
EdgeCaseCustomer.count
|
|
70
|
+
#=> 0
|
|
71
|
+
|
|
72
|
+
## EDGE: Orphaned object - keys_count finds the orphan
|
|
73
|
+
EdgeCaseCustomer.keys_count
|
|
74
|
+
#=> 1
|
|
75
|
+
|
|
76
|
+
## EDGE: Orphaned object - scan_count finds the orphan
|
|
77
|
+
EdgeCaseCustomer.scan_count
|
|
78
|
+
#=> 1
|
|
79
|
+
|
|
80
|
+
## EDGE: Orphaned object - count! finds the orphan
|
|
81
|
+
EdgeCaseCustomer.count!
|
|
82
|
+
#=> 1
|
|
83
|
+
|
|
84
|
+
## EDGE: Orphaned object - any? returns false (no instances entry)
|
|
85
|
+
# Since instances is empty, any? should return false
|
|
86
|
+
EdgeCaseCustomer.any?
|
|
87
|
+
#=> false
|
|
88
|
+
|
|
89
|
+
## EDGE: Orphaned object - keys_any? returns true (object exists)
|
|
90
|
+
EdgeCaseCustomer.keys_any?
|
|
91
|
+
#=> true
|
|
92
|
+
|
|
93
|
+
## EDGE: Orphaned object - scan_any? returns true (object exists)
|
|
94
|
+
EdgeCaseCustomer.scan_any?
|
|
95
|
+
#=> true
|
|
96
|
+
|
|
97
|
+
## EDGE: Orphaned object - any! returns true (object exists)
|
|
98
|
+
EdgeCaseCustomer.any!
|
|
99
|
+
#=> true
|
|
100
|
+
|
|
101
|
+
## EDGE: Orphaned object cleanup - destroy and clear
|
|
102
|
+
@orphan.destroy!
|
|
103
|
+
EdgeCaseCustomer.instances.clear
|
|
104
|
+
EdgeCaseCustomer.all.each(&:destroy!)
|
|
105
|
+
#=*> Array
|
|
106
|
+
|
|
107
|
+
# =============================================================================
|
|
108
|
+
# Edge Case 3: Duplicate Instance Entries
|
|
109
|
+
# Same identifier added multiple times to instances (shouldn't happen but test it)
|
|
110
|
+
# =============================================================================
|
|
111
|
+
|
|
112
|
+
## EDGE: Duplicate entries - create fresh customer
|
|
113
|
+
@dup = EdgeCaseCustomer.create!(custid: 'dup1', name: 'Duplicate')
|
|
114
|
+
# Manually add the same identifier again with different score
|
|
115
|
+
# ZADD should just update the score, not create duplicate
|
|
116
|
+
EdgeCaseCustomer.instances.add('dup1', Time.now.to_i + 1000)
|
|
117
|
+
EdgeCaseCustomer.count
|
|
118
|
+
#=> 1
|
|
119
|
+
|
|
120
|
+
## EDGE: Duplicate entries - keys_count also shows 1
|
|
121
|
+
EdgeCaseCustomer.keys_count
|
|
122
|
+
#=> 1
|
|
123
|
+
|
|
124
|
+
## EDGE: Duplicate entries - scan_count also shows 1
|
|
125
|
+
EdgeCaseCustomer.scan_count
|
|
126
|
+
#=> 1
|
|
127
|
+
|
|
128
|
+
## EDGE: Duplicate entries cleanup - destroy and clear
|
|
129
|
+
@dup.destroy!
|
|
130
|
+
EdgeCaseCustomer.instances.clear
|
|
131
|
+
EdgeCaseCustomer.all.each(&:destroy!)
|
|
132
|
+
#=*> Array
|
|
133
|
+
|
|
134
|
+
# =============================================================================
|
|
135
|
+
# Edge Case 4: Empty Identifier
|
|
136
|
+
# What happens with empty string identifier?
|
|
137
|
+
# =============================================================================
|
|
138
|
+
|
|
139
|
+
## EDGE: Empty identifier - instances can store empty string
|
|
140
|
+
EdgeCaseCustomer.instances.add('', Time.now.to_i)
|
|
141
|
+
EdgeCaseCustomer.count
|
|
142
|
+
#=> 1
|
|
143
|
+
|
|
144
|
+
## EDGE: Empty identifier - keys_count returns 0 (no matching keys)
|
|
145
|
+
# Empty identifier doesn't create valid keys
|
|
146
|
+
EdgeCaseCustomer.keys_count
|
|
147
|
+
#=> 0
|
|
148
|
+
|
|
149
|
+
## EDGE: Empty identifier - scan_count returns 0 (no matching keys)
|
|
150
|
+
EdgeCaseCustomer.scan_count
|
|
151
|
+
#=> 0
|
|
152
|
+
|
|
153
|
+
## EDGE: Empty identifier cleanup - clear all
|
|
154
|
+
EdgeCaseCustomer.instances.clear
|
|
155
|
+
EdgeCaseCustomer.all.each(&:destroy!)
|
|
156
|
+
#=*> Array
|
|
157
|
+
|
|
158
|
+
# =============================================================================
|
|
159
|
+
# Edge Case 5: Mixed State - Multiple Desync Scenarios
|
|
160
|
+
# Combination of phantoms, orphans, and valid objects
|
|
161
|
+
# =============================================================================
|
|
162
|
+
|
|
163
|
+
## EDGE: Mixed state - setup complex scenario
|
|
164
|
+
@valid1 = EdgeCaseCustomer.create!(custid: 'valid1', name: 'Valid 1')
|
|
165
|
+
@valid2 = EdgeCaseCustomer.create!(custid: 'valid2', name: 'Valid 2')
|
|
166
|
+
# Create phantom (in instances but no object)
|
|
167
|
+
EdgeCaseCustomer.instances.add('phantom2', Time.now.to_i)
|
|
168
|
+
# Create orphan (object but not in instances)
|
|
169
|
+
@orphan2 = EdgeCaseCustomer.create!(custid: 'orphan2', name: 'Orphan 2')
|
|
170
|
+
EdgeCaseCustomer.instances.remove('orphan2')
|
|
171
|
+
# instances has: valid1, valid2, phantom2 = 3
|
|
172
|
+
EdgeCaseCustomer.count
|
|
173
|
+
#=> 3
|
|
174
|
+
|
|
175
|
+
## EDGE: Mixed state - keys_count shows authoritative count
|
|
176
|
+
# Actual objects exist: valid1, valid2, orphan2 = 3
|
|
177
|
+
EdgeCaseCustomer.keys_count
|
|
178
|
+
#=> 3
|
|
179
|
+
|
|
180
|
+
## EDGE: Mixed state - scan_count shows authoritative count
|
|
181
|
+
EdgeCaseCustomer.scan_count
|
|
182
|
+
#=> 3
|
|
183
|
+
|
|
184
|
+
## EDGE: Mixed state - count! shows authoritative count
|
|
185
|
+
EdgeCaseCustomer.count!
|
|
186
|
+
#=> 3
|
|
187
|
+
|
|
188
|
+
## EDGE: Mixed state - both counts match in this case
|
|
189
|
+
# By coincidence, both are 3 (different reasons though)
|
|
190
|
+
EdgeCaseCustomer.keys_count == EdgeCaseCustomer.count
|
|
191
|
+
#=> true
|
|
192
|
+
|
|
193
|
+
## EDGE: Mixed state cleanup - clear all
|
|
194
|
+
EdgeCaseCustomer.instances.clear
|
|
195
|
+
EdgeCaseCustomer.all.each(&:destroy!)
|
|
196
|
+
#=*> Array
|
|
197
|
+
|
|
198
|
+
# =============================================================================
|
|
199
|
+
# Edge Case 6: Special Characters in Identifiers
|
|
200
|
+
# Unicode, special chars, patterns that might break filters
|
|
201
|
+
# =============================================================================
|
|
202
|
+
|
|
203
|
+
## EDGE: Special chars - asterisk in identifier
|
|
204
|
+
@special1 = EdgeCaseCustomer.create!(custid: 'user*123', name: 'Special')
|
|
205
|
+
EdgeCaseCustomer.count
|
|
206
|
+
#=> 1
|
|
207
|
+
|
|
208
|
+
## EDGE: Special chars - keys_count finds it
|
|
209
|
+
EdgeCaseCustomer.keys_count
|
|
210
|
+
#=> 1
|
|
211
|
+
|
|
212
|
+
## EDGE: Special chars - scan_count finds it
|
|
213
|
+
EdgeCaseCustomer.scan_count
|
|
214
|
+
#=> 1
|
|
215
|
+
|
|
216
|
+
## EDGE: Special chars - filter with asterisk pattern
|
|
217
|
+
# This tests if the literal asterisk in custid affects pattern matching
|
|
218
|
+
EdgeCaseCustomer.keys_count('user*')
|
|
219
|
+
#=> 1
|
|
220
|
+
|
|
221
|
+
## EDGE: Special chars - scan filter with asterisk pattern
|
|
222
|
+
EdgeCaseCustomer.scan_count('user*')
|
|
223
|
+
#=> 1
|
|
224
|
+
|
|
225
|
+
## EDGE: Special chars cleanup - destroy special1
|
|
226
|
+
@special1.destroy!
|
|
227
|
+
#=*> Integer
|
|
228
|
+
|
|
229
|
+
## EDGE: Special chars - question mark in identifier
|
|
230
|
+
@special2 = EdgeCaseCustomer.create!(custid: 'user?456', name: 'Special2')
|
|
231
|
+
EdgeCaseCustomer.keys_count('user?*')
|
|
232
|
+
#=> 1
|
|
233
|
+
|
|
234
|
+
## EDGE: Special chars - scan filter with question mark pattern
|
|
235
|
+
EdgeCaseCustomer.scan_count('user?*')
|
|
236
|
+
#=> 1
|
|
237
|
+
|
|
238
|
+
## EDGE: Special chars cleanup - destroy special2
|
|
239
|
+
@special2.destroy!
|
|
240
|
+
#=*> Integer
|
|
241
|
+
|
|
242
|
+
## EDGE: Special chars - brackets in identifier
|
|
243
|
+
@special3 = EdgeCaseCustomer.create!(custid: 'user[test]', name: 'Special3')
|
|
244
|
+
EdgeCaseCustomer.keys_count('user*')
|
|
245
|
+
#=> 1
|
|
246
|
+
|
|
247
|
+
## EDGE: Special chars - scan finds bracketed identifier
|
|
248
|
+
EdgeCaseCustomer.scan_count('user*')
|
|
249
|
+
#=> 1
|
|
250
|
+
|
|
251
|
+
## EDGE: Special chars cleanup - destroy special3 and clear
|
|
252
|
+
@special3.destroy!
|
|
253
|
+
EdgeCaseCustomer.instances.clear
|
|
254
|
+
#=*> Integer
|
|
255
|
+
|
|
256
|
+
# =============================================================================
|
|
257
|
+
# Edge Case 7: Score Manipulation
|
|
258
|
+
# Instances sorted set uses scores (timestamps), test score edge cases
|
|
259
|
+
# =============================================================================
|
|
260
|
+
|
|
261
|
+
## EDGE: Score manipulation - zero score
|
|
262
|
+
EdgeCaseCustomer.instances.add('score_test1', 0)
|
|
263
|
+
EdgeCaseCustomer.count
|
|
264
|
+
#=> 1
|
|
265
|
+
|
|
266
|
+
## EDGE: Score manipulation - negative score
|
|
267
|
+
EdgeCaseCustomer.instances.add('score_test2', -12345)
|
|
268
|
+
EdgeCaseCustomer.count
|
|
269
|
+
#=> 2
|
|
270
|
+
|
|
271
|
+
## EDGE: Score manipulation - very large score
|
|
272
|
+
EdgeCaseCustomer.instances.add('score_test3', 9999999999999)
|
|
273
|
+
EdgeCaseCustomer.count
|
|
274
|
+
#=> 3
|
|
275
|
+
|
|
276
|
+
## EDGE: Score manipulation - scores don't affect count
|
|
277
|
+
# Regardless of score values, count should be accurate
|
|
278
|
+
EdgeCaseCustomer.count
|
|
279
|
+
#=> 3
|
|
280
|
+
|
|
281
|
+
## EDGE: Score manipulation - keys_count ignores scores (checks actual keys)
|
|
282
|
+
EdgeCaseCustomer.keys_count
|
|
283
|
+
#=> 0
|
|
284
|
+
|
|
285
|
+
## EDGE: Score manipulation - scan_count ignores scores
|
|
286
|
+
EdgeCaseCustomer.scan_count
|
|
287
|
+
#=> 0
|
|
288
|
+
|
|
289
|
+
## EDGE: Score manipulation cleanup - clear instances
|
|
290
|
+
EdgeCaseCustomer.instances.clear
|
|
291
|
+
#=*> Integer
|
|
292
|
+
|
|
293
|
+
# =============================================================================
|
|
294
|
+
# Edge Case 8: Large Dataset - Scan Cursor Behavior
|
|
295
|
+
# Test that scan_count properly iterates through large datasets
|
|
296
|
+
# =============================================================================
|
|
297
|
+
|
|
298
|
+
## EDGE: Large dataset - create 100 instances
|
|
299
|
+
100.times do |i|
|
|
300
|
+
EdgeCaseCustomer.create!(custid: "large#{i}", name: "Large #{i}")
|
|
301
|
+
end
|
|
302
|
+
EdgeCaseCustomer.count
|
|
303
|
+
#=> 100
|
|
304
|
+
|
|
305
|
+
## EDGE: Large dataset - keys_count matches (blocks but works)
|
|
306
|
+
EdgeCaseCustomer.keys_count
|
|
307
|
+
#=> 100
|
|
308
|
+
|
|
309
|
+
## EDGE: Large dataset - scan_count matches (non-blocking, cursor iteration)
|
|
310
|
+
EdgeCaseCustomer.scan_count
|
|
311
|
+
#=> 100
|
|
312
|
+
|
|
313
|
+
## EDGE: Large dataset - count! matches
|
|
314
|
+
EdgeCaseCustomer.count!
|
|
315
|
+
#=> 100
|
|
316
|
+
|
|
317
|
+
## EDGE: Large dataset - scan_any? returns true efficiently
|
|
318
|
+
EdgeCaseCustomer.scan_any?
|
|
319
|
+
#=> true
|
|
320
|
+
|
|
321
|
+
## EDGE: Large dataset - keys_any? returns true
|
|
322
|
+
EdgeCaseCustomer.keys_any?
|
|
323
|
+
#=> true
|
|
324
|
+
|
|
325
|
+
## EDGE: Large dataset - filter scan works with many results
|
|
326
|
+
EdgeCaseCustomer.scan_count('large1*')
|
|
327
|
+
#=> 11
|
|
328
|
+
|
|
329
|
+
## EDGE: Large dataset - filter keys works with many results
|
|
330
|
+
EdgeCaseCustomer.keys_count('large1*')
|
|
331
|
+
#=> 11
|
|
332
|
+
|
|
333
|
+
## EDGE: Large dataset cleanup - clear all
|
|
334
|
+
EdgeCaseCustomer.instances.clear
|
|
335
|
+
EdgeCaseCustomer.all.each(&:destroy!)
|
|
336
|
+
#=*> Array
|
|
337
|
+
|
|
338
|
+
# =============================================================================
|
|
339
|
+
# Edge Case 9: Manual Redis Operations Breaking Consistency
|
|
340
|
+
# Direct Redis commands that bypass Familia's tracking
|
|
341
|
+
# =============================================================================
|
|
342
|
+
|
|
343
|
+
## EDGE: Manual ops - RENAME breaks dbkey pattern
|
|
344
|
+
@manual1 = EdgeCaseCustomer.create!(custid: 'manual1', name: 'Manual')
|
|
345
|
+
original_key = @manual1.dbkey
|
|
346
|
+
EdgeCaseCustomer.dbclient.rename(original_key, 'some:random:key')
|
|
347
|
+
EdgeCaseCustomer.count
|
|
348
|
+
#=> 1
|
|
349
|
+
|
|
350
|
+
## EDGE: Manual ops - keys_count doesn't find renamed key
|
|
351
|
+
EdgeCaseCustomer.keys_count
|
|
352
|
+
#=> 0
|
|
353
|
+
|
|
354
|
+
## EDGE: Manual ops - scan_count doesn't find renamed key
|
|
355
|
+
EdgeCaseCustomer.scan_count
|
|
356
|
+
#=> 0
|
|
357
|
+
|
|
358
|
+
## EDGE: Manual ops - instances still has stale entry
|
|
359
|
+
EdgeCaseCustomer.any?
|
|
360
|
+
#=> true
|
|
361
|
+
|
|
362
|
+
## EDGE: Manual ops - but keys_any? correctly returns false
|
|
363
|
+
EdgeCaseCustomer.keys_any?
|
|
364
|
+
#=> false
|
|
365
|
+
|
|
366
|
+
## EDGE: Manual ops - and scan_any? correctly returns false
|
|
367
|
+
EdgeCaseCustomer.scan_any?
|
|
368
|
+
#=> false
|
|
369
|
+
|
|
370
|
+
## EDGE: Manual ops cleanup - delete renamed key and clear instances
|
|
371
|
+
EdgeCaseCustomer.dbclient.del('some:random:key')
|
|
372
|
+
EdgeCaseCustomer.instances.clear
|
|
373
|
+
#=*> Integer
|
|
374
|
+
|
|
375
|
+
# =============================================================================
|
|
376
|
+
# Edge Case 10: Partial Transaction Failures
|
|
377
|
+
# Simulate scenarios where object exists but instances tracking failed
|
|
378
|
+
# =============================================================================
|
|
379
|
+
|
|
380
|
+
## EDGE: Partial failure - manually create object without instances entry
|
|
381
|
+
@direct_key = EdgeCaseCustomer.new(custid: 'partial1', name: 'Partial').dbkey
|
|
382
|
+
EdgeCaseCustomer.dbclient.hset(@direct_key, 'custid', 'partial1')
|
|
383
|
+
EdgeCaseCustomer.dbclient.hset(@direct_key, 'name', 'Partial')
|
|
384
|
+
EdgeCaseCustomer.count
|
|
385
|
+
#=> 0
|
|
386
|
+
|
|
387
|
+
## EDGE: Partial failure - keys_count finds the manually created object
|
|
388
|
+
EdgeCaseCustomer.keys_count
|
|
389
|
+
#=> 1
|
|
390
|
+
|
|
391
|
+
## EDGE: Partial failure - scan_count finds it too
|
|
392
|
+
EdgeCaseCustomer.scan_count
|
|
393
|
+
#=> 1
|
|
394
|
+
|
|
395
|
+
## EDGE: Partial failure - count! finds it
|
|
396
|
+
EdgeCaseCustomer.count!
|
|
397
|
+
#=> 1
|
|
398
|
+
|
|
399
|
+
## EDGE: Partial failure - any? returns false (not in instances)
|
|
400
|
+
EdgeCaseCustomer.any?
|
|
401
|
+
#=> false
|
|
402
|
+
|
|
403
|
+
## EDGE: Partial failure - keys_any? returns true (key exists)
|
|
404
|
+
EdgeCaseCustomer.keys_any?
|
|
405
|
+
#=> true
|
|
406
|
+
|
|
407
|
+
## EDGE: Partial failure - scan_any? returns true (key exists)
|
|
408
|
+
EdgeCaseCustomer.scan_any?
|
|
409
|
+
#=> true
|
|
410
|
+
|
|
411
|
+
## EDGE: Partial failure cleanup - delete direct key and clear
|
|
412
|
+
EdgeCaseCustomer.dbclient.del(@direct_key)
|
|
413
|
+
EdgeCaseCustomer.instances.clear
|
|
414
|
+
#=*> Integer
|
|
415
|
+
|
|
416
|
+
# =============================================================================
|
|
417
|
+
# Edge Case 11: Filter Pattern Edge Cases
|
|
418
|
+
# Test filter behavior with complex patterns and edge cases
|
|
419
|
+
# =============================================================================
|
|
420
|
+
|
|
421
|
+
## EDGE: Filter patterns - create first filter test object
|
|
422
|
+
@filter1 = EdgeCaseCustomer.create!(custid: 'filter1', name: 'Filter1')
|
|
423
|
+
@filter1.custid
|
|
424
|
+
#=> 'filter1'
|
|
425
|
+
|
|
426
|
+
## EDGE: Filter patterns - single character wildcard
|
|
427
|
+
EdgeCaseCustomer.keys_count('?')
|
|
428
|
+
#=> 0
|
|
429
|
+
|
|
430
|
+
## EDGE: Filter patterns - create second filter test object
|
|
431
|
+
@filter2 = EdgeCaseCustomer.create!(custid: 'filter2', name: 'Filter2')
|
|
432
|
+
@filter2.custid
|
|
433
|
+
#=> 'filter2'
|
|
434
|
+
|
|
435
|
+
## EDGE: Filter patterns - multiple wildcards count
|
|
436
|
+
EdgeCaseCustomer.keys_count('*')
|
|
437
|
+
#=*> Integer
|
|
438
|
+
|
|
439
|
+
## EDGE: Filter patterns - scan with wildcard matches all
|
|
440
|
+
EdgeCaseCustomer.scan_count('*')
|
|
441
|
+
#=*> Integer
|
|
442
|
+
|
|
443
|
+
## EDGE: Filter patterns - range pattern
|
|
444
|
+
EdgeCaseCustomer.keys_count('filter[12]')
|
|
445
|
+
#=> 2
|
|
446
|
+
|
|
447
|
+
## EDGE: Filter patterns - scan with range pattern
|
|
448
|
+
EdgeCaseCustomer.scan_count('filter[12]')
|
|
449
|
+
#=> 2
|
|
450
|
+
|
|
451
|
+
## EDGE: Empty identifier cleanup - clear all
|
|
452
|
+
EdgeCaseCustomer.instances.clear
|
|
453
|
+
EdgeCaseCustomer.all.each(&:destroy!)
|
|
454
|
+
#=*> Array
|
|
455
|
+
|
|
456
|
+
# =============================================================================
|
|
457
|
+
# Edge Case 12: Boundary Conditions
|
|
458
|
+
# Test edge cases around zero, one, and boundary values
|
|
459
|
+
# =============================================================================
|
|
460
|
+
|
|
461
|
+
## EDGE: Boundary - scan_any? short-circuits on first match
|
|
462
|
+
# Create one object and verify scan_any? doesn't iterate unnecessarily
|
|
463
|
+
@boundary1 = EdgeCaseCustomer.create!(custid: 'boundary1', name: 'Boundary')
|
|
464
|
+
EdgeCaseCustomer.scan_any?
|
|
465
|
+
#=> true
|
|
466
|
+
|
|
467
|
+
## EDGE: Boundary - scan_any? with filter short-circuits
|
|
468
|
+
EdgeCaseCustomer.scan_any?('bound*')
|
|
469
|
+
#=> true
|
|
470
|
+
|
|
471
|
+
## EDGE: Boundary - scan_any? returns false when no matches
|
|
472
|
+
EdgeCaseCustomer.scan_any?('nonexistent*')
|
|
473
|
+
#=> false
|
|
474
|
+
|
|
475
|
+
## EDGE: Boundary - keys_any? with non-matching filter
|
|
476
|
+
EdgeCaseCustomer.keys_any?('nonexistent*')
|
|
477
|
+
#=> false
|
|
478
|
+
|
|
479
|
+
## EDGE: Boundary cleanup - destroy and clear
|
|
480
|
+
@boundary1.destroy!
|
|
481
|
+
EdgeCaseCustomer.instances.clear
|
|
482
|
+
#=*> Integer
|
|
483
|
+
|
|
484
|
+
## Final cleanup - clear instances
|
|
485
|
+
EdgeCaseCustomer.instances.clear
|
|
486
|
+
#=*> Integer
|