familia 2.0.0.pre15 → 2.0.0.pre16
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/code-quality.yml +138 -0
- data/.github/workflows/code-smellage.yml +145 -0
- data/.github/workflows/docs.yml +31 -8
- data/.gitignore +1 -1
- data/.pre-commit-config.yaml +7 -1
- data/.reek.yml +98 -0
- data/.rubocop.yml +48 -10
- data/.talismanrc +9 -0
- data/.yardopts +18 -13
- data/CHANGELOG.rst +64 -4
- data/CLAUDE.md +1 -1
- data/Gemfile +6 -5
- data/Gemfile.lock +99 -23
- data/LICENSE.txt +1 -1
- data/README.md +285 -85
- data/changelog.d/README.md +2 -2
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
- data/docs/archive/FAMILIA_TECHNICAL.md +41 -41
- data/docs/archive/FAMILIA_UPDATE.md +3 -3
- data/docs/archive/README.md +3 -2
- data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
- data/docs/conf.py +29 -0
- data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
- data/docs/guides/feature-encrypted-fields.md +785 -0
- data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
- data/docs/guides/feature-external-identifiers.md +637 -0
- data/docs/guides/feature-object-identifiers.md +435 -0
- data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
- data/docs/guides/feature-relationships-methods.md +684 -0
- data/docs/guides/feature-relationships.md +200 -0
- data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
- data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
- data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
- data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
- data/docs/guides/index.md +176 -0
- data/docs/guides/{Security-Model.md → security-model.md} +1 -1
- data/docs/migrating/v2.0.0-pre.md +1 -1
- data/docs/migrating/v2.0.0-pre11.md +2 -2
- data/docs/migrating/v2.0.0-pre12.md +2 -2
- data/docs/migrating/v2.0.0-pre5.md +33 -12
- data/docs/migrating/v2.0.0-pre6.md +2 -2
- data/docs/migrating/v2.0.0-pre7.md +8 -8
- data/docs/overview.md +623 -19
- data/docs/reference/api-technical.md +1365 -0
- data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
- data/examples/autoloader/mega_customer.rb +3 -1
- data/examples/encrypted_fields.rb +378 -0
- data/examples/json_usage_patterns.rb +144 -0
- data/examples/relationships.rb +13 -13
- data/examples/safe_dump.rb +6 -6
- data/examples/single_connection_transaction_confusions.rb +379 -0
- data/lib/familia/base.rb +49 -10
- data/lib/familia/connection/handlers.rb +223 -0
- data/lib/familia/connection/individual_command_proxy.rb +64 -0
- data/lib/familia/connection/middleware.rb +75 -0
- data/lib/familia/connection/operation_core.rb +93 -0
- data/lib/familia/connection/operations.rb +277 -0
- data/lib/familia/connection/pipeline_core.rb +87 -0
- data/lib/familia/connection/transaction_core.rb +100 -0
- data/lib/familia/connection.rb +60 -186
- data/lib/familia/data_type/commands.rb +53 -51
- data/lib/familia/data_type/serialization.rb +108 -107
- data/lib/familia/data_type/types/counter.rb +1 -1
- data/lib/familia/data_type/types/hashkey.rb +13 -10
- data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
- data/lib/familia/data_type/types/lock.rb +3 -2
- data/lib/familia/data_type/types/sorted_set.rb +26 -15
- data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -5
- data/lib/familia/data_type/types/unsorted_set.rb +20 -27
- data/lib/familia/data_type.rb +75 -47
- data/lib/familia/distinguisher.rb +85 -0
- data/lib/familia/encryption/encrypted_data.rb +15 -24
- data/lib/familia/encryption/manager.rb +6 -4
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
- data/lib/familia/encryption/request_cache.rb +7 -7
- data/lib/familia/encryption.rb +2 -3
- data/lib/familia/errors.rb +9 -3
- data/lib/familia/features/autoloader.rb +30 -12
- data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
- data/lib/familia/features/encrypted_fields.rb +66 -64
- data/lib/familia/features/expiration/extensions.rb +1 -1
- data/lib/familia/features/expiration.rb +31 -26
- data/lib/familia/features/external_identifier.rb +9 -12
- data/lib/familia/features/object_identifier.rb +56 -19
- data/lib/familia/features/quantization.rb +16 -21
- data/lib/familia/features/relationships/README.md +97 -0
- data/lib/familia/features/relationships/collection_operations.rb +104 -0
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +301 -0
- data/lib/familia/features/relationships/indexing.rb +176 -256
- data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
- data/lib/familia/features/relationships/participation/participant_methods.rb +160 -0
- data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
- data/lib/familia/features/relationships/participation.rb +656 -0
- data/lib/familia/features/relationships/participation_relationship.rb +31 -0
- data/lib/familia/features/relationships/score_encoding.rb +20 -20
- data/lib/familia/features/relationships.rb +65 -266
- data/lib/familia/features/safe_dump.rb +127 -130
- data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
- data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
- data/lib/familia/features/transient_fields.rb +3 -5
- data/lib/familia/features.rb +4 -13
- data/lib/familia/field_type.rb +24 -4
- data/lib/familia/horreum/core/connection.rb +229 -26
- data/lib/familia/horreum/core/database_commands.rb +27 -17
- data/lib/familia/horreum/core/serialization.rb +40 -20
- data/lib/familia/horreum/core/utils.rb +2 -1
- data/lib/familia/horreum/shared/settings.rb +2 -1
- data/lib/familia/horreum/subclass/definition.rb +33 -45
- data/lib/familia/horreum/subclass/management.rb +72 -24
- data/lib/familia/horreum/subclass/related_fields_management.rb +82 -21
- data/lib/familia/horreum.rb +196 -114
- data/lib/familia/json_serializer.rb +0 -1
- data/lib/familia/logging.rb +11 -114
- data/lib/familia/refinements/dear_json.rb +122 -0
- data/lib/familia/refinements/logger_trace.rb +20 -17
- data/lib/familia/refinements/stylize_words.rb +65 -0
- data/lib/familia/refinements/time_literals.rb +60 -52
- data/lib/familia/refinements.rb +2 -1
- data/lib/familia/secure_identifier.rb +60 -28
- data/lib/familia/settings.rb +83 -7
- data/lib/familia/utils.rb +5 -87
- data/lib/familia/verifiable_identifier.rb +4 -4
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +72 -14
- data/lib/middleware/database_middleware.rb +56 -14
- data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
- data/try/configuration/scenarios_try.rb +1 -1
- data/try/connection/fiber_context_preservation_try.rb +250 -0
- data/try/connection/handler_constraints_try.rb +59 -0
- data/try/connection/operation_mode_guards_try.rb +208 -0
- data/try/connection/pipeline_fallback_integration_try.rb +128 -0
- data/try/connection/responsibility_chain_tracking_try.rb +72 -0
- data/try/connection/transaction_fallback_integration_try.rb +288 -0
- data/try/connection/transaction_mode_permissive_try.rb +153 -0
- data/try/connection/transaction_mode_strict_try.rb +98 -0
- data/try/connection/transaction_mode_warn_try.rb +131 -0
- data/try/connection/transaction_modes_try.rb +249 -0
- data/try/core/autoloader_try.rb +120 -2
- data/try/core/connection_try.rb +7 -7
- data/try/core/conventional_inheritance_try.rb +130 -0
- data/try/core/create_method_try.rb +15 -23
- data/try/core/database_consistency_try.rb +10 -10
- data/try/core/errors_try.rb +8 -11
- data/try/core/familia_extended_try.rb +2 -2
- data/try/core/familia_members_methods_try.rb +76 -0
- data/try/core/isolated_dbclient_try.rb +165 -0
- data/try/core/middleware_try.rb +16 -16
- data/try/core/persistence_operations_try.rb +4 -4
- data/try/core/pools_try.rb +42 -26
- data/try/core/secure_identifier_try.rb +28 -24
- data/try/core/time_utils_try.rb +10 -10
- data/try/core/tools_try.rb +1 -1
- data/try/core/utils_try.rb +2 -2
- data/try/data_types/boolean_try.rb +4 -4
- data/try/data_types/datatype_base_try.rb +0 -2
- data/try/data_types/list_try.rb +10 -10
- data/try/data_types/sorted_set_try.rb +5 -5
- data/try/data_types/string_try.rb +12 -12
- data/try/data_types/unsortedset_try.rb +33 -0
- data/try/debugging/cache_behavior_tracer.rb +7 -7
- data/try/debugging/debug_aad_process.rb +1 -1
- data/try/debugging/debug_concealed_internal.rb +1 -1
- data/try/debugging/debug_cross_context.rb +1 -1
- data/try/debugging/debug_fresh_cross_context.rb +1 -1
- data/try/debugging/encryption_method_tracer.rb +10 -10
- data/try/edge_cases/hash_symbolization_try.rb +1 -1
- data/try/edge_cases/ttl_side_effects_try.rb +1 -1
- data/try/encryption/config_persistence_try.rb +2 -2
- data/try/encryption/encryption_core_try.rb +19 -19
- data/try/encryption/instance_variable_scope_try.rb +1 -1
- data/try/encryption/module_loading_try.rb +2 -2
- data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
- data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
- data/try/encryption/secure_memory_handling_try.rb +1 -1
- data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
- data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
- data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
- data/try/features/external_identifier/external_identifier_try.rb +1 -1
- data/try/features/feature_dependencies_try.rb +3 -3
- data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
- data/try/features/object_identifier/object_identifier_try.rb +10 -0
- data/try/features/quantization/quantization_try.rb +1 -1
- data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
- data/try/features/relationships/indexing_try.rb +433 -0
- data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
- data/try/features/relationships/participation_commands_verification_try.rb +105 -0
- data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
- data/try/features/relationships/participation_reverse_index_try.rb +196 -0
- data/try/features/relationships/relationships_api_changes_try.rb +72 -71
- data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
- data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
- data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
- data/try/features/relationships/relationships_performance_try.rb +20 -20
- data/try/features/relationships/relationships_try.rb +27 -38
- data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
- data/try/features/transient_fields/refresh_reset_try.rb +1 -1
- data/try/features/transient_fields/simple_refresh_test.rb +1 -1
- data/try/helpers/test_cleanup.rb +86 -0
- data/try/helpers/test_helpers.rb +3 -3
- data/try/horreum/base_try.rb +3 -2
- data/try/horreum/commands_try.rb +1 -1
- data/try/horreum/destroy_related_fields_cleanup_try.rb +330 -0
- data/try/horreum/initialization_try.rb +11 -7
- data/try/horreum/relations_try.rb +21 -13
- data/try/horreum/serialization_try.rb +12 -11
- data/try/integration/cross_component_try.rb +3 -3
- data/try/memory/memory_basic_test.rb +1 -1
- data/try/memory/memory_docker_ruby_dump.sh +1 -1
- data/try/models/customer_safe_dump_try.rb +1 -1
- data/try/models/customer_try.rb +8 -10
- data/try/models/datatype_base_try.rb +3 -3
- data/try/models/familia_object_try.rb +9 -8
- data/try/performance/benchmarks_try.rb +2 -2
- data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
- data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
- data/try/prototypes/atomic_saves_v4.rb +1 -1
- data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
- data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
- data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
- data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
- data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
- data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
- data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
- data/try/prototypes/pooling/pool_siege.rb +11 -11
- data/try/prototypes/pooling/run_stress_tests.rb +7 -7
- data/try/refinements/dear_json_array_methods_try.rb +53 -0
- data/try/refinements/dear_json_hash_methods_try.rb +54 -0
- data/try/refinements/logger_trace_methods_try.rb +44 -0
- data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
- data/try/refinements/time_literals_string_methods_try.rb +80 -0
- metadata +75 -43
- data/.rubocop_todo.yml +0 -208
- data/docs/connection_pooling.md +0 -192
- data/docs/guides/Connection-Pooling-Guide.md +0 -437
- data/docs/guides/Encrypted-Fields-Overview.md +0 -101
- data/docs/guides/Feature-System-Autoloading.md +0 -198
- data/docs/guides/Home.md +0 -116
- data/docs/guides/Relationships-Guide.md +0 -737
- data/docs/guides/relationships-methods.md +0 -266
- data/docs/reference/auditing_database_commands.rb +0 -228
- data/examples/permissions.rb +0 -240
- data/lib/familia/features/relationships/cascading.rb +0 -437
- data/lib/familia/features/relationships/membership.rb +0 -497
- data/lib/familia/features/relationships/permission_management.rb +0 -264
- data/lib/familia/features/relationships/querying.rb +0 -615
- data/lib/familia/features/relationships/redis_operations.rb +0 -274
- data/lib/familia/features/relationships/tracking.rb +0 -418
- data/lib/familia/refinements/snake_case.rb +0 -40
- data/lib/familia/validation/command_recorder.rb +0 -336
- data/lib/familia/validation/expectations.rb +0 -519
- data/lib/familia/validation/validation_helpers.rb +0 -443
- data/lib/familia/validation/validator.rb +0 -412
- data/lib/familia/validation.rb +0 -140
- data/try/data_types/set_try.rb +0 -33
- data/try/features/relationships/categorical_permissions_try.rb +0 -515
- data/try/features/safe_dump/module_based_extensions_try.rb +0 -100
- data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -107
- data/try/validation/atomic_operations_try.rb.disabled +0 -320
- data/try/validation/command_validation_try.rb.disabled +0 -207
- data/try/validation/performance_validation_try.rb.disabled +0 -324
- data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -16,7 +16,7 @@ end
|
|
16
16
|
# Clean up any existing test data
|
17
17
|
cleanup_keys = []
|
18
18
|
begin
|
19
|
-
existing_test_keys = Familia.dbclient.keys('
|
19
|
+
existing_test_keys = Familia.dbclient.keys('create_test_model:*')
|
20
20
|
cleanup_keys.concat(existing_test_keys)
|
21
21
|
Familia.dbclient.del(*existing_test_keys) if existing_test_keys.any?
|
22
22
|
rescue => e
|
@@ -26,16 +26,18 @@ end
|
|
26
26
|
@test_id_counter = 0
|
27
27
|
def next_test_id
|
28
28
|
@test_id_counter += 1
|
29
|
-
"create-test-#{
|
29
|
+
identifier = "create-test-#{Familia.now.to_i}-#{@test_id_counter}"
|
30
|
+
identifier
|
30
31
|
end
|
31
32
|
|
33
|
+
@first_test_id = next_test_id
|
34
|
+
|
32
35
|
# =============================================
|
33
36
|
# 1. Basic create method functionality
|
34
37
|
# =============================================
|
35
38
|
|
36
39
|
## create method successfully creates new object
|
37
|
-
@
|
38
|
-
@created_obj = CreateTestModel.create(id: @test_id, name: 'Created Object', value: 'test_value')
|
40
|
+
@created_obj = CreateTestModel.create(id: @first_test_id, name: 'Created Object', value: 'test_value')
|
39
41
|
[@created_obj.class, @created_obj.exists?, @created_obj.name]
|
40
42
|
#=> [CreateTestModel, true, 'Created Object']
|
41
43
|
|
@@ -53,27 +55,17 @@ end
|
|
53
55
|
# =============================================
|
54
56
|
|
55
57
|
## create method raises RecordExistsError for duplicate
|
56
|
-
|
57
|
-
|
58
|
-
false # Should not reach here
|
59
|
-
rescue => e
|
60
|
-
e.class
|
61
|
-
end
|
62
|
-
#=> Familia::RecordExistsError
|
58
|
+
CreateTestModel.create(id: @first_test_id, name: 'Duplicate Attempt')
|
59
|
+
#=!> Familia::RecordExistsError
|
63
60
|
|
64
61
|
## RecordExistsError includes the dbkey in the message
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
rescue Familia::RecordExistsError => e
|
69
|
-
expected_dbkey = "createtestmodel:#{@test_id}:object"
|
70
|
-
e.message.include?(expected_dbkey)
|
71
|
-
end
|
72
|
-
#=> true
|
62
|
+
CreateTestModel.create(id: @first_test_id, name: 'Another Duplicate')
|
63
|
+
#=!> Familia::RecordExistsError
|
64
|
+
#==> !!error.message.match(/create_test_model:#{@first_test_id}:object/)
|
73
65
|
|
74
66
|
## RecordExistsError message follows consistent format
|
75
67
|
begin
|
76
|
-
CreateTestModel.create(id: @
|
68
|
+
CreateTestModel.create(id: @first_test_id, name: 'Yet Another Duplicate')
|
77
69
|
false # Should not reach here
|
78
70
|
rescue Familia::RecordExistsError => e
|
79
71
|
e.message.start_with?('Key already exists:')
|
@@ -140,12 +132,12 @@ end
|
|
140
132
|
#=> true
|
141
133
|
|
142
134
|
## create failure doesn't leave partial data
|
143
|
-
before_failed_create = Familia.dbclient.keys("
|
135
|
+
before_failed_create = Familia.dbclient.keys("create_test_model:#{@concurrent_id}:*").length
|
144
136
|
begin
|
145
137
|
CreateTestModel.create(id: @concurrent_id, name: 'Should Fail')
|
146
138
|
rescue Familia::RecordExistsError
|
147
139
|
# Should not create any additional keys
|
148
|
-
after_failed_create = Familia.dbclient.keys("
|
140
|
+
after_failed_create = Familia.dbclient.keys("create_test_model:#{@concurrent_id}:*").length
|
149
141
|
after_failed_create == before_failed_create
|
150
142
|
end
|
151
143
|
#=> true
|
@@ -236,5 +228,5 @@ instance_sees_exists = @class_created.exists?
|
|
236
228
|
# =============================================
|
237
229
|
|
238
230
|
# Clean up all test data
|
239
|
-
test_keys = Familia.dbclient.keys('
|
231
|
+
test_keys = Familia.dbclient.keys('create_test_model:*')
|
240
232
|
Familia.dbclient.del(*test_keys) if test_keys.any?
|
@@ -28,14 +28,14 @@ end
|
|
28
28
|
@test_id_counter = 0
|
29
29
|
def next_test_id
|
30
30
|
@test_id_counter += 1
|
31
|
-
"consistency-#{
|
31
|
+
"consistency-#{Familia.now.to_i}-#{@test_id_counter}"
|
32
32
|
end
|
33
33
|
|
34
34
|
# =============================================
|
35
35
|
# 1. Database Consistency Verification
|
36
36
|
# =============================================
|
37
37
|
|
38
|
-
## Redis key structure follows expected pattern
|
38
|
+
## Valkey/Redis key structure follows expected pattern
|
39
39
|
@key_test = ConsistencyTestModel.new(id: next_test_id, name: 'Key Test')
|
40
40
|
@key_test.save
|
41
41
|
dbkey = @key_test.dbkey
|
@@ -139,7 +139,7 @@ end
|
|
139
139
|
@empty_hash.save
|
140
140
|
|
141
141
|
# Manually remove all fields to create an empty hash
|
142
|
-
# First add a temp field then remove it, which creates empty hash in some Redis versions
|
142
|
+
# First add a temp field then remove it, which creates empty hash in some Valkey/Redis versions
|
143
143
|
Familia.dbclient.hset(@empty_hash.dbkey, 'temp_field', 'temp_value')
|
144
144
|
Familia.dbclient.hdel(@empty_hash.dbkey, 'temp_field')
|
145
145
|
# Now remove all remaining fields to create truly empty hash
|
@@ -159,17 +159,17 @@ obj_exists_without_check = @empty_hash.exists?(check_size: false)
|
|
159
159
|
@tx_test = ConsistencyTestModel.new(id: next_test_id, name: 'Transaction Test')
|
160
160
|
@tx_test.save
|
161
161
|
|
162
|
-
# Verify transaction doesn't
|
163
|
-
|
164
|
-
|
165
|
-
|
162
|
+
# Verify transaction works and doesn't corrupt object state
|
163
|
+
exists_before_tx = @tx_test.exists?
|
164
|
+
multi_result = @tx_test.transaction do |conn|
|
165
|
+
# Update a field within transaction
|
166
166
|
conn.hset(@tx_test.dbkey, 'active', 'true')
|
167
|
-
|
167
|
+
conn.hset(@tx_test.dbkey, 'processed', 'true')
|
168
168
|
end
|
169
169
|
|
170
170
|
exists_after_tx = @tx_test.exists?
|
171
|
-
[
|
172
|
-
#=> [
|
171
|
+
[exists_before_tx, multi_result.successful?, exists_after_tx]
|
172
|
+
#=> [true, true, true]
|
173
173
|
|
174
174
|
# =============================================
|
175
175
|
# 4. Performance Consistency
|
data/try/core/errors_try.rb
CHANGED
@@ -26,21 +26,18 @@ rescue Familia::NonUniqueKey => e
|
|
26
26
|
end
|
27
27
|
#=> Familia::NonUniqueKey
|
28
28
|
|
29
|
-
##
|
29
|
+
## NotDistinguishableError error stores value
|
30
30
|
begin
|
31
|
-
raise Familia::
|
32
|
-
rescue Familia::
|
31
|
+
raise Familia::NotDistinguishableError.new('dangerous_value')
|
32
|
+
rescue Familia::NotDistinguishableError => e
|
33
33
|
e.value
|
34
34
|
end
|
35
35
|
#=> "dangerous_value"
|
36
36
|
|
37
|
-
##
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
e.message.include?('High risk factor')
|
42
|
-
end
|
43
|
-
#=> true
|
37
|
+
## NotDistinguishableError error has custom message
|
38
|
+
raise Familia::NotDistinguishableError, 'A customized message'
|
39
|
+
#=:> Familia::NotDistinguishableError
|
40
|
+
#=~> /A customized message/
|
44
41
|
|
45
42
|
## NotConnected error stores URI
|
46
43
|
test_uri = URI.parse('redis://localhost:6379')
|
@@ -105,7 +102,7 @@ Familia::RecordExistsError.superclass
|
|
105
102
|
[
|
106
103
|
Familia::NoIdentifier,
|
107
104
|
Familia::NonUniqueKey,
|
108
|
-
Familia::
|
105
|
+
Familia::NotDistinguishableError,
|
109
106
|
Familia::NotConnected,
|
110
107
|
Familia::KeyNotFoundError,
|
111
108
|
Familia::RecordExistsError
|
@@ -7,7 +7,7 @@ require_relative '../helpers/test_helpers'
|
|
7
7
|
## Has all datatype relativess
|
8
8
|
registered_types = Familia::DataType.registered_types.keys
|
9
9
|
registered_types.collect(&:to_s).sort
|
10
|
-
#=> ["counter", "hash", "hashkey", "list", "lock", "set", "sorted_set", "string", "zset"]
|
10
|
+
#=> ["counter", "hash", "hashkey", "list", "listkey", "lock", "set", "sorted_set", "string", "stringkey", "unsorted_set", "zset"]
|
11
11
|
|
12
12
|
## Familia created class methods for datatype list class
|
13
13
|
Familia::Horreum::DefinitionMethods.public_method_defined? :list?
|
@@ -36,7 +36,7 @@ Bone.list? :owners
|
|
36
36
|
## A Familia object can get a specific datatype relatives def
|
37
37
|
definition = Bone.list :owners
|
38
38
|
definition.klass
|
39
|
-
#=> Familia::
|
39
|
+
#=> Familia::ListKey
|
40
40
|
|
41
41
|
## Familia.now
|
42
42
|
parsed_time = Familia.now(Time.parse('2011-04-10 20:56:20 UTC').utc)
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# try/core/familia_members_methods_try.rb
|
2
|
+
|
3
|
+
require_relative '../helpers/test_helpers'
|
4
|
+
|
5
|
+
# Tests for new methods: demodularize, familia_name, and resolve_class
|
6
|
+
|
7
|
+
# Use the testable methods from the refactored module
|
8
|
+
String.include(Familia::Refinements::StylizeWordsMethods)
|
9
|
+
|
10
|
+
## demodularize removes module namespace from simple class name
|
11
|
+
'Customer'.demodularize
|
12
|
+
#=> 'Customer'
|
13
|
+
|
14
|
+
## demodularize removes module namespace from nested class name
|
15
|
+
'V2::Customer'.demodularize
|
16
|
+
#=> 'Customer'
|
17
|
+
|
18
|
+
## demodularize handles deep nesting
|
19
|
+
'My::Deep::Nested::Module::Customer'.demodularize
|
20
|
+
#=> 'Customer'
|
21
|
+
|
22
|
+
## demodularize handles single colon edge case
|
23
|
+
'::Customer'.demodularize
|
24
|
+
#=> 'Customer'
|
25
|
+
|
26
|
+
## demodularize returns original string when no modules
|
27
|
+
'SimpleClass'.demodularize
|
28
|
+
#=> 'SimpleClass'
|
29
|
+
|
30
|
+
## familia_name returns demodularized class name for Customer
|
31
|
+
Customer.familia_name
|
32
|
+
#=> 'Customer'
|
33
|
+
|
34
|
+
## familia_name returns demodularized class name for Session
|
35
|
+
Session.familia_name
|
36
|
+
#=> 'Session'
|
37
|
+
|
38
|
+
## familia_name returns demodularized class name for Bone
|
39
|
+
Bone.familia_name
|
40
|
+
#=> 'Bone'
|
41
|
+
|
42
|
+
## resolve_class returns the same class when given a Class
|
43
|
+
Familia.resolve_class(Customer)
|
44
|
+
#=> Customer
|
45
|
+
|
46
|
+
## resolve_class finds class by string name
|
47
|
+
Familia.resolve_class('Customer')
|
48
|
+
#=> Customer
|
49
|
+
|
50
|
+
## resolve_class finds class by symbol name
|
51
|
+
Familia.resolve_class(:Customer)
|
52
|
+
#=> Customer
|
53
|
+
|
54
|
+
## resolve_class handles CamelCase string conversion
|
55
|
+
Familia.resolve_class('CustomDomain')
|
56
|
+
#=> CustomDomain
|
57
|
+
|
58
|
+
## resolve_class handles snake_case symbol conversion
|
59
|
+
Familia.resolve_class(:CustomDomain)
|
60
|
+
#=> CustomDomain
|
61
|
+
|
62
|
+
## resolve_class raises error for invalid input
|
63
|
+
begin
|
64
|
+
Familia.resolve_class(123)
|
65
|
+
rescue ArgumentError => e
|
66
|
+
e.message
|
67
|
+
end
|
68
|
+
#=> "Expected Class, String, or Symbol, got Integer"
|
69
|
+
|
70
|
+
## resolve_class returns nil for unknown class name
|
71
|
+
Familia.resolve_class('NonExistentClass')
|
72
|
+
#=> nil
|
73
|
+
|
74
|
+
## resolve_class returns nil for unknown symbol
|
75
|
+
Familia.resolve_class(:NonExistentClass)
|
76
|
+
#=> nil
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# try/core/isolated_dbclient_try.rb
|
2
|
+
|
3
|
+
# Tryouts: Isolated connection functionality
|
4
|
+
#
|
5
|
+
# Tests for isolated database connections that don't interfere
|
6
|
+
# with the cached connection pool or existing model connections.
|
7
|
+
|
8
|
+
require_relative '../helpers/test_helpers'
|
9
|
+
|
10
|
+
# Clean up any existing test data in all test databases
|
11
|
+
(0..15).each do |db|
|
12
|
+
Familia.with_isolated_dbclient(db) do |client|
|
13
|
+
client.flushdb
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
## isolated_dbclient creates a new uncached connection
|
18
|
+
client1 = Familia.isolated_dbclient(0)
|
19
|
+
client2 = Familia.isolated_dbclient(0)
|
20
|
+
different_objects = client1.object_id != client2.object_id
|
21
|
+
client1.close
|
22
|
+
client2.close
|
23
|
+
different_objects
|
24
|
+
#=> true
|
25
|
+
|
26
|
+
## isolated_dbclient connects to the correct database
|
27
|
+
Familia.with_isolated_dbclient(5) do |client|
|
28
|
+
client.set("test_key", "test_value")
|
29
|
+
end
|
30
|
+
|
31
|
+
# Verify the key was set in database 5
|
32
|
+
found_in_db5 = Familia.with_isolated_dbclient(5) do |client|
|
33
|
+
client.get("test_key") == "test_value"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Verify the key is NOT in database 0
|
37
|
+
not_found_in_db0 = Familia.with_isolated_dbclient(0) do |client|
|
38
|
+
client.get("test_key").nil?
|
39
|
+
end
|
40
|
+
|
41
|
+
found_in_db5 && not_found_in_db0
|
42
|
+
#=> true
|
43
|
+
|
44
|
+
## isolated_dbclient doesn't affect cached connections
|
45
|
+
# Set up a cached connection
|
46
|
+
regular_client = Familia.dbclient(0)
|
47
|
+
regular_client.set("cached_key", "cached_value")
|
48
|
+
|
49
|
+
# Use isolated connection on same database
|
50
|
+
isolated_result = Familia.with_isolated_dbclient(0) do |client|
|
51
|
+
client.set("isolated_key", "isolated_value")
|
52
|
+
client.get("cached_key")
|
53
|
+
end
|
54
|
+
|
55
|
+
# Both keys should be accessible
|
56
|
+
cached_accessible = regular_client.get("cached_key") == "cached_value"
|
57
|
+
isolated_accessible = regular_client.get("isolated_key") == "isolated_value"
|
58
|
+
|
59
|
+
cached_accessible && isolated_accessible && isolated_result == "cached_value"
|
60
|
+
#=> true
|
61
|
+
|
62
|
+
## with_isolated_dbclient properly manages connection lifecycle
|
63
|
+
# Test by verifying functionality rather than relying on GC/ObjectSpace
|
64
|
+
captured_clients = []
|
65
|
+
|
66
|
+
5.times do |i|
|
67
|
+
Familia.with_isolated_dbclient(i) do |client|
|
68
|
+
captured_clients << client
|
69
|
+
client.set("temp_key_#{i}", "temp_value_#{i}")
|
70
|
+
# Verify connection works inside block
|
71
|
+
client.ping
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Verify all connections worked and created distinct objects
|
76
|
+
all_worked = captured_clients.size == 5
|
77
|
+
all_distinct = captured_clients.map(&:object_id).uniq.size == 5
|
78
|
+
keys_set = Familia.with_isolated_dbclient(0) { |c| c.exists?("temp_key_0") }
|
79
|
+
|
80
|
+
all_worked && all_distinct && keys_set
|
81
|
+
#=> true
|
82
|
+
|
83
|
+
## with_isolated_dbclient handles exceptions gracefully
|
84
|
+
exception_raised = false
|
85
|
+
database_state_correct = false
|
86
|
+
|
87
|
+
begin
|
88
|
+
Familia.with_isolated_dbclient(0) do |client|
|
89
|
+
client.set("before_error", "value")
|
90
|
+
raise "Test exception"
|
91
|
+
# This line should not be reached
|
92
|
+
client.set("after_error", "should_not_be_set")
|
93
|
+
end
|
94
|
+
rescue => e
|
95
|
+
exception_raised = (e.message == "Test exception")
|
96
|
+
end
|
97
|
+
|
98
|
+
# Verify the database state after the exception was caught
|
99
|
+
if exception_raised
|
100
|
+
database_state_correct = Familia.with_isolated_dbclient(0) do |client|
|
101
|
+
client.get("before_error") == "value" && client.get("after_error").nil?
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
exception_raised && database_state_correct
|
106
|
+
#=> true
|
107
|
+
|
108
|
+
## isolated connections don't interfere with model connections
|
109
|
+
class TestModel < Familia::Horreum
|
110
|
+
logical_database 3
|
111
|
+
identifier_field :name
|
112
|
+
field :name
|
113
|
+
end
|
114
|
+
|
115
|
+
# Create a model instance
|
116
|
+
test_model = TestModel.new(name: "test")
|
117
|
+
test_model.save
|
118
|
+
|
119
|
+
# Use isolated connection to scan a different database
|
120
|
+
scan_result = Familia.with_isolated_dbclient(5) do |client|
|
121
|
+
client.keys("*")
|
122
|
+
end
|
123
|
+
|
124
|
+
# Model should still work correctly
|
125
|
+
model_accessible = test_model.exists? && test_model.name == "test"
|
126
|
+
# Don't rely on database 5 being empty since previous tests may have written to it
|
127
|
+
# Just verify the model still works correctly
|
128
|
+
scan_result_valid = scan_result.is_a?(Array)
|
129
|
+
|
130
|
+
model_accessible && scan_result_valid
|
131
|
+
#=> true
|
132
|
+
|
133
|
+
# Clean up test model and class
|
134
|
+
test_model.delete!
|
135
|
+
Familia.unload_member(TestModel)
|
136
|
+
|
137
|
+
## isolated_dbclient with Integer argument
|
138
|
+
client = Familia.isolated_dbclient(7)
|
139
|
+
client.set("db_test", "seven")
|
140
|
+
result = client.get("db_test")
|
141
|
+
client.close
|
142
|
+
result
|
143
|
+
#=> "seven"
|
144
|
+
|
145
|
+
## isolated_dbclient with String URI argument
|
146
|
+
client = Familia.isolated_dbclient("redis://localhost:6379/8")
|
147
|
+
client.set("uri_test", "eight")
|
148
|
+
result = client.get("uri_test")
|
149
|
+
client.close
|
150
|
+
result
|
151
|
+
#=> "eight"
|
152
|
+
|
153
|
+
## isolated_dbclient with nil uses default
|
154
|
+
default_db = Familia.uri.db || 0
|
155
|
+
client = Familia.isolated_dbclient(nil)
|
156
|
+
client.set("default_test", "default_value")
|
157
|
+
|
158
|
+
# Verify it's in the expected database
|
159
|
+
verification = Familia.with_isolated_dbclient(default_db) do |verify_client|
|
160
|
+
verify_client.get("default_test")
|
161
|
+
end
|
162
|
+
|
163
|
+
client.close
|
164
|
+
verification
|
165
|
+
#=> "default_value"
|
data/try/core/middleware_try.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# try/core/middleware_try.rb
|
2
2
|
|
3
|
-
# Test Redis middleware components
|
4
|
-
# Mock Redis client with middleware for testing
|
3
|
+
# Test Valkey/Redis middleware components
|
4
|
+
# Mock Valkey/Redis client with middleware for testing
|
5
5
|
|
6
6
|
require_relative '../helpers/test_helpers'
|
7
7
|
|
8
|
-
class
|
8
|
+
class MockDatabase
|
9
9
|
attr_reader :logged_commands
|
10
10
|
|
11
11
|
def initialize
|
@@ -19,40 +19,40 @@ class MockRedis
|
|
19
19
|
private
|
20
20
|
|
21
21
|
def log_command(cmd, *args)
|
22
|
-
start_time =
|
22
|
+
start_time = Familia.now
|
23
23
|
result = yield
|
24
|
-
duration =
|
24
|
+
duration = Familia.now - start_time
|
25
25
|
@logged_commands << { command: cmd, args: args, duration: duration }
|
26
26
|
result
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
##
|
31
|
-
|
32
|
-
result =
|
33
|
-
[result,
|
30
|
+
## MockDatabase can log commands with timing
|
31
|
+
dbclient = MockDatabase.new
|
32
|
+
result = dbclient.get("test_key")
|
33
|
+
[result, dbclient.logged_commands.length, dbclient.logged_commands.first[:command]]
|
34
34
|
#=> ["test_value", 1, "GET"]
|
35
35
|
|
36
|
-
##
|
36
|
+
## DatabaseCommandCounter tracks command metrics (if available)
|
37
37
|
begin
|
38
|
-
counter =
|
38
|
+
counter = DatabaseCommandCounter.new
|
39
39
|
counter.increment("GET")
|
40
40
|
counter.increment("SET")
|
41
41
|
counter.increment("GET")
|
42
42
|
[counter.count("GET"), counter.count("SET"), counter.total]
|
43
43
|
rescue NameError
|
44
|
-
# Skip if
|
44
|
+
# Skip if DatabaseCommandCounter not available
|
45
45
|
[2, 1, 3]
|
46
46
|
end
|
47
47
|
#=> [2, 1, 3]
|
48
48
|
|
49
49
|
## Command counting utility works (if available)
|
50
50
|
begin
|
51
|
-
|
51
|
+
dbclient = Familia.dbclient
|
52
52
|
count = count_commands do
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
dbclient.set("test_key", "value")
|
54
|
+
dbclient.get("test_key")
|
55
|
+
dbclient.del("test_key")
|
56
56
|
end
|
57
57
|
count >= 3
|
58
58
|
rescue NameError, NoMethodError
|
@@ -26,7 +26,7 @@ end
|
|
26
26
|
@test_id_counter = 0
|
27
27
|
def next_test_id
|
28
28
|
@test_id_counter += 1
|
29
|
-
"test-#{
|
29
|
+
"test-#{Familia.now.to_i}-#{@test_id_counter}"
|
30
30
|
end
|
31
31
|
|
32
32
|
# =============================================
|
@@ -95,11 +95,11 @@ second_save = @idempotent_obj.save
|
|
95
95
|
|
96
96
|
## Save with partial field data
|
97
97
|
@partial_obj = PersistenceTestModel.new(id: next_test_id)
|
98
|
-
@partial_obj.name = 'Only Name
|
98
|
+
@partial_obj.name = 'Only Name UnsortedSet'
|
99
99
|
# value field is nil/unset
|
100
100
|
result = @partial_obj.save
|
101
101
|
[result, @partial_obj.exists?, @partial_obj.name]
|
102
|
-
#=> [true, true, 'Only Name
|
102
|
+
#=> [true, true, 'Only Name UnsortedSet']
|
103
103
|
|
104
104
|
# =============================================
|
105
105
|
# 3. save_if_not_exists Method Coverage
|
@@ -141,7 +141,7 @@ end
|
|
141
141
|
# 4. create Method Coverage (MISSING from current tests)
|
142
142
|
# =============================================
|
143
143
|
|
144
|
-
# NOTE: create method tests disabled due to Redis::Future bug
|
144
|
+
# NOTE: create method tests disabled due to Valkey/Redis::Future bug
|
145
145
|
# This would be high-priority coverage but needs the create method bug fixed first
|
146
146
|
|
147
147
|
## create method alternative: manual creation simulation
|
data/try/core/pools_try.rb
CHANGED
@@ -51,10 +51,28 @@ class PoolTestSession < Familia::Horreum
|
|
51
51
|
|
52
52
|
def init
|
53
53
|
@session_id ||= SecureRandom.hex(8)
|
54
|
-
@created_at ||=
|
54
|
+
@created_at ||= Familia.now.to_i
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
+
class PoolTestAccountDB1 < Familia::Horreum
|
59
|
+
logical_database 1
|
60
|
+
identifier_field :account_id
|
61
|
+
field :account_id
|
62
|
+
field :balance, on_conflict: :skip
|
63
|
+
field :holder_name
|
64
|
+
|
65
|
+
def init
|
66
|
+
@account_id ||= SecureRandom.hex(6)
|
67
|
+
@balance = @balance.to_f if @balance
|
68
|
+
end
|
69
|
+
|
70
|
+
def balance
|
71
|
+
@balance&.to_f
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
58
76
|
## Clean up before tests
|
59
77
|
PoolTestAccount.dbclient.flushdb
|
60
78
|
#=> "OK"
|
@@ -79,23 +97,6 @@ Familia.connection_provider.is_a?(Proc)
|
|
79
97
|
#=> true
|
80
98
|
|
81
99
|
## Test 5: Account in DB 1 via class configuration
|
82
|
-
class PoolTestAccountDB1 < Familia::Horreum
|
83
|
-
self.logical_database = 1
|
84
|
-
identifier_field :account_id
|
85
|
-
field :account_id
|
86
|
-
field :balance, on_conflict: :skip
|
87
|
-
field :holder_name
|
88
|
-
|
89
|
-
def init
|
90
|
-
@account_id ||= SecureRandom.hex(6)
|
91
|
-
@balance = @balance.to_f if @balance
|
92
|
-
end
|
93
|
-
|
94
|
-
def balance
|
95
|
-
@balance&.to_f
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
100
|
@account_db1 = PoolTestAccountDB1.new(balance: 750, holder_name: "Charlie")
|
100
101
|
@account_db1.save
|
101
102
|
#=> true
|
@@ -140,8 +141,8 @@ threads.each(&:join)
|
|
140
141
|
# Test that transaction connection is available
|
141
142
|
conn.ping
|
142
143
|
end
|
143
|
-
# Transaction returns
|
144
|
-
@transfer_result.first
|
144
|
+
# Transaction returns MultiResult with success status and results
|
145
|
+
@transfer_result.results.first
|
145
146
|
#=> "PONG"
|
146
147
|
|
147
148
|
## Test 12: Transaction block executes properly
|
@@ -149,19 +150,19 @@ end
|
|
149
150
|
[@account_a.balance, @account_b.balance]
|
150
151
|
#=> [1000.0, 500.0]
|
151
152
|
|
152
|
-
## Test 13:
|
153
|
-
@connection_test_result = Familia.
|
153
|
+
## Test 13: with_dbclient method
|
154
|
+
@connection_test_result = Familia.with_dbclient do |conn|
|
154
155
|
conn.set("test_key_#{SecureRandom.hex(4)}", "test_value")
|
155
156
|
end
|
156
157
|
@connection_test_result
|
157
158
|
#=> "OK"
|
158
159
|
|
159
160
|
## Test 14: Pipeline operations with connection pool
|
160
|
-
@pipeline_results = Familia.
|
161
|
+
@pipeline_results = Familia.pipelined do |conn|
|
161
162
|
conn.ping
|
162
163
|
end
|
163
164
|
# Pipeline executes successfully
|
164
|
-
@pipeline_results.first
|
165
|
+
@pipeline_results.results.first
|
165
166
|
#=> "PONG"
|
166
167
|
|
167
168
|
## Test 15: Multi/EXEC operations with connection pool
|
@@ -169,7 +170,7 @@ end
|
|
169
170
|
conn.ping
|
170
171
|
end
|
171
172
|
# Multi/EXEC executes successfully
|
172
|
-
@multi_results.first
|
173
|
+
@multi_results.results.first
|
173
174
|
#=> "PONG"
|
174
175
|
|
175
176
|
## Test 16: Error handling in transactions
|
@@ -208,7 +209,7 @@ timeout_mutex = Mutex.new
|
|
208
209
|
3.times do |i|
|
209
210
|
timeout_threads << Thread.new do
|
210
211
|
begin
|
211
|
-
result = Familia.
|
212
|
+
result = Familia.with_dbclient do |conn|
|
212
213
|
sleep(0.1) # Brief hold
|
213
214
|
conn.ping
|
214
215
|
end
|
@@ -270,4 +271,19 @@ Familia.connection_provider = original_provider
|
|
270
271
|
@captured_uris.any? { |uri| uri.include?('redis://') }
|
271
272
|
#=> true
|
272
273
|
|
274
|
+
## Check PoolTestAccountDB1 config name
|
275
|
+
PoolTestAccountDB1.config_name
|
276
|
+
#=> 'pool_test_account_db1'
|
277
|
+
|
278
|
+
|
273
279
|
puts "Connection pool tests completed successfully!"
|
280
|
+
|
281
|
+
# Teardown
|
282
|
+
Familia.connection_provider = nil
|
283
|
+
Fiber[:familia_connection] = nil
|
284
|
+
Fiber[:familia_connection_handler_class] = nil
|
285
|
+
Fiber[:familia_transaction] = nil
|
286
|
+
Fiber[:familia_pipeline] = nil
|
287
|
+
Fiber[:familia_key_cache] = nil
|
288
|
+
Fiber[:familia_request_cache] = nil
|
289
|
+
Fiber[:familia_request_cache_enabled] = nil
|