familia 2.0.0.pre15 → 2.0.0.pre17
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/ci.yml +2 -2
- data/.github/workflows/code-quality.yml +138 -0
- data/.github/workflows/code-smells.yml +85 -0
- data/.github/workflows/docs.yml +31 -8
- data/.gitignore +3 -1
- data/.pre-commit-config.yaml +7 -1
- data/.reek.yml +98 -0
- data/.rubocop.yml +54 -10
- data/.talismanrc +9 -0
- data/.yardopts +18 -13
- data/CHANGELOG.rst +86 -4
- data/CLAUDE.md +39 -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 +42 -42
- 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 +624 -20
- 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 +7 -7
- data/examples/single_connection_transaction_confusions.rb +379 -0
- data/lib/familia/base.rb +51 -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/class_methods.rb +63 -0
- data/lib/familia/data_type/commands.rb +53 -51
- data/lib/familia/data_type/connection.rb +83 -0
- data/lib/familia/data_type/serialization.rb +108 -107
- data/lib/familia/data_type/settings.rb +96 -0
- data/lib/familia/data_type/types/counter.rb +1 -1
- data/lib/familia/data_type/types/hashkey.rb +15 -11
- 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 +128 -14
- data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -9
- data/lib/familia/data_type/types/unsorted_set.rb +20 -27
- data/lib/familia/data_type.rb +12 -171
- 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 +71 -66
- data/lib/familia/features/expiration/extensions.rb +1 -1
- data/lib/familia/features/expiration.rb +31 -26
- data/lib/familia/features/external_identifier.rb +57 -19
- data/lib/familia/features/object_identifier.rb +134 -25
- 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 +306 -0
- data/lib/familia/features/relationships/indexing.rb +182 -256
- data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
- data/lib/familia/features/relationships/participation/participant_methods.rb +164 -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 +10 -7
- data/lib/familia/features.rb +10 -14
- data/lib/familia/field_type.rb +6 -4
- data/lib/familia/horreum/connection.rb +297 -0
- data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +27 -17
- data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +139 -74
- data/lib/familia/horreum/{subclass/management.rb → management.rb} +73 -27
- data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +108 -185
- data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +104 -23
- data/lib/familia/horreum/serialization.rb +172 -0
- data/lib/familia/horreum/{shared/settings.rb → settings.rb} +2 -1
- data/lib/familia/horreum/{core/utils.rb → utils.rb} +2 -1
- data/lib/familia/horreum.rb +222 -119
- 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 +2 -2
- 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 +10 -10
- 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 +11 -10
- data/try/core/errors_try.rb +11 -14
- data/try/core/familia_extended_try.rb +2 -2
- data/try/core/familia_members_methods_try.rb +76 -0
- data/try/core/familia_try.rb +1 -1
- 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 +3 -3
- 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/sorted_set_zadd_options_try.rb +625 -0
- 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/field_groups_try.rb +244 -0
- 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 +443 -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 +3 -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 +6 -7
- data/try/horreum/auto_indexing_on_save_try.rb +212 -0
- data/try/horreum/base_try.rb +3 -2
- data/try/horreum/commands_try.rb +3 -1
- data/try/horreum/defensive_initialization_try.rb +86 -0
- data/try/horreum/destroy_related_fields_cleanup_try.rb +332 -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/horreum/settings_try.rb +2 -0
- 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 +2 -2
- data/try/models/customer_safe_dump_try.rb +1 -1
- data/try/models/customer_try.rb +13 -15
- 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
- data/try/valkey.conf +26 -0
- metadata +92 -52
- 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/horreum/core/connection.rb +0 -73
- data/lib/familia/horreum/core.rb +0 -21
- 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
data/lib/familia/connection.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
# lib/familia/connection.rb
|
2
2
|
|
3
|
-
require_relative '
|
4
|
-
require_relative '
|
3
|
+
require_relative 'connection/handlers'
|
4
|
+
require_relative 'connection/middleware'
|
5
|
+
require_relative 'connection/operations'
|
6
|
+
require_relative 'connection/individual_command_proxy'
|
7
|
+
require_relative 'connection/operation_core'
|
8
|
+
require_relative 'connection/transaction_core'
|
9
|
+
require_relative 'connection/pipeline_core'
|
5
10
|
|
6
11
|
# Familia
|
7
12
|
#
|
@@ -9,7 +14,8 @@ require_relative 'multi_result'
|
|
9
14
|
#
|
10
15
|
module Familia
|
11
16
|
@uri = URI.parse 'redis://127.0.0.1:6379'
|
12
|
-
@
|
17
|
+
@middleware_registered = false
|
18
|
+
@middleware_version = 0
|
13
19
|
|
14
20
|
# The Connection module provides Database connection management for Familia.
|
15
21
|
# It allows easy setup and access to Database clients across different URIs
|
@@ -18,222 +24,86 @@ module Familia
|
|
18
24
|
# @return [URI] The default URI for Database connections
|
19
25
|
attr_reader :uri
|
20
26
|
|
21
|
-
# @return [Hash] A hash of Database clients, keyed by server ID
|
22
|
-
attr_reader :database_clients
|
23
|
-
|
24
|
-
# @return [Boolean] Whether Database command logging is enabled
|
25
|
-
attr_accessor :enable_database_logging
|
26
|
-
|
27
|
-
# @return [Boolean] Whether Database command counter is enabled
|
28
|
-
attr_accessor :enable_database_counter
|
29
|
-
|
30
27
|
# @return [Proc] A callable that provides Database connections
|
31
|
-
|
28
|
+
# The provider should accept a URI string and return a Redis connection
|
29
|
+
# already connected to the correct database specified in the URI.
|
30
|
+
#
|
31
|
+
# @example Setting a connection provider
|
32
|
+
# Familia.connection_provider = ->(uri) do
|
33
|
+
# pool = ConnectionPool.new { Redis.new(url: uri) }
|
34
|
+
# pool.with { |conn| conn }
|
35
|
+
# end
|
36
|
+
attr_reader :connection_provider
|
32
37
|
|
33
|
-
#
|
34
|
-
|
38
|
+
# Sets the connection provider and bumps middleware version
|
39
|
+
def connection_provider=(provider)
|
40
|
+
@connection_provider = provider
|
41
|
+
increment_middleware_version! if provider
|
42
|
+
@connection_chain = nil # Force rebuild of chain
|
43
|
+
end
|
35
44
|
|
36
45
|
# Sets the default URI for Database connections.
|
37
46
|
#
|
38
47
|
# NOTE: uri is not a property of the Settings module b/c it's not
|
39
48
|
# configured in class defintions like default_expiration or logical DB index.
|
40
49
|
#
|
41
|
-
# @param
|
42
|
-
# @example
|
43
|
-
# Familia.uri = 'redis://localhost:6379'
|
50
|
+
# @param uri [String, URI] The new default URI
|
51
|
+
# @example Familia.uri = 'redis://localhost:6379'
|
44
52
|
def uri=(uri)
|
45
53
|
@uri = normalize_uri(uri)
|
46
54
|
end
|
47
55
|
alias url uri
|
48
56
|
alias url= uri=
|
49
57
|
|
50
|
-
#
|
58
|
+
# Creates a new Database connection instance.
|
59
|
+
#
|
60
|
+
# This method always creates a fresh connection and does not use caching.
|
61
|
+
# Each call returns a new Redis client instance that you are responsible
|
62
|
+
# for managing and closing when done.
|
51
63
|
#
|
52
64
|
# @param uri [String, URI, nil] The URI of the Database server to connect to.
|
53
|
-
# If nil, uses the default URI from
|
54
|
-
# @return [Redis]
|
65
|
+
# If nil, uses the default URI from Familia.uri.
|
66
|
+
# @return [Redis] A new Database client connection.
|
55
67
|
# @raise [ArgumentError] If no URI is specified.
|
56
|
-
#
|
57
|
-
#
|
58
|
-
|
68
|
+
#
|
69
|
+
# @example Creating a new connection
|
70
|
+
# client = Familia.create_dbclient('redis://localhost:6379')
|
71
|
+
# client.ping
|
72
|
+
# client.close
|
73
|
+
#
|
74
|
+
def create_dbclient(uri = nil)
|
59
75
|
parsed_uri = normalize_uri(uri)
|
60
76
|
|
61
|
-
|
62
|
-
|
63
|
-
RedisClient.register(DatabaseLogger)
|
64
|
-
end
|
65
|
-
|
66
|
-
if Familia.enable_database_counter
|
67
|
-
# NOTE: This middleware uses AtommicFixnum from concurrent-ruby which is
|
68
|
-
# less contentious than Mutex-based counters. Safe for
|
69
|
-
RedisClient.register(DatabaseCommandCounter)
|
70
|
-
end
|
77
|
+
# Register middleware only once, globally
|
78
|
+
register_middleware_once
|
71
79
|
|
72
80
|
Redis.new(parsed_uri.conf)
|
73
81
|
end
|
82
|
+
alias connect create_dbclient # backwards compatibility
|
83
|
+
alias isolated_dbclient create_dbclient # matches with_isolated_dbclient api
|
74
84
|
|
75
|
-
|
76
|
-
parsed_uri = normalize_uri(uri)
|
77
|
-
serverid = parsed_uri.serverid
|
78
|
-
|
79
|
-
# Close the existing connection if it exists
|
80
|
-
@database_clients[serverid].close if @database_clients.key?(serverid)
|
81
|
-
@database_clients.delete(serverid)
|
82
|
-
|
83
|
-
connect(parsed_uri)
|
84
|
-
end
|
85
|
-
|
86
|
-
# Retrieves a Database connection from the appropriate pool.
|
85
|
+
# Retrieves a Database connection using the Chain of Responsibility pattern.
|
87
86
|
# Handles DB selection automatically based on the URI.
|
88
87
|
#
|
89
88
|
# @return [Redis] The Database client for the specified URI
|
90
|
-
# @example
|
91
|
-
# Familia.dbclient('redis://localhost:6379/1')
|
89
|
+
# @example Familia.dbclient('redis://localhost:6379/1')
|
92
90
|
# Familia.dbclient(2) # Use DB 2 with default server
|
93
91
|
def dbclient(uri = nil)
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
# Second priority: Connection provider
|
98
|
-
if connection_provider
|
99
|
-
# Always pass normalized URI with database to provider
|
100
|
-
# Provider MUST return connection already on the correct database
|
101
|
-
parsed_uri = normalize_uri(uri)
|
102
|
-
client = connection_provider.call(parsed_uri.to_s)
|
103
|
-
|
104
|
-
# In debug mode, verify the provider honored the contract
|
105
|
-
if Familia.debug? && client.respond_to?(:client)
|
106
|
-
current_db = client.connection[:db]
|
107
|
-
expected_db = parsed_uri.db || 0
|
108
|
-
Familia.ld "Connection provider returned client on DB #{current_db}, expected #{expected_db}"
|
109
|
-
if current_db != expected_db
|
110
|
-
Familia.warn "Connection provider returned client on DB #{current_db}, expected #{expected_db}"
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
return client
|
115
|
-
end
|
116
|
-
|
117
|
-
# Third priority: Fallback behavior or error
|
118
|
-
raise Familia::NoConnectionAvailable, 'No connection available.' if connection_required
|
119
|
-
|
120
|
-
# Legacy behavior: create connection
|
121
|
-
parsed_uri = normalize_uri(uri)
|
122
|
-
serverid = parsed_uri.serverid
|
123
|
-
|
124
|
-
@database_clients[serverid] ||= connect(parsed_uri)
|
125
|
-
end
|
126
|
-
|
127
|
-
# Executes Database commands atomically within a transaction (MULTI/EXEC).
|
128
|
-
#
|
129
|
-
# Database transactions queue commands and execute them atomically as a single unit.
|
130
|
-
# All commands succeed together or all fail together, ensuring data consistency.
|
131
|
-
#
|
132
|
-
# @yield [Redis] The Database transaction connection
|
133
|
-
# @return [Array] Results of all commands executed in the transaction
|
134
|
-
#
|
135
|
-
# @example Basic transaction usage
|
136
|
-
# Familia.transaction do |trans|
|
137
|
-
# trans.set("key1", "value1")
|
138
|
-
# trans.incr("counter")
|
139
|
-
# trans.lpush("list", "item")
|
140
|
-
# end
|
141
|
-
# # Returns: ["OK", 2, 1] - results of all commands
|
142
|
-
#
|
143
|
-
# @note **Comparison of Database batch operations:**
|
144
|
-
#
|
145
|
-
# | Feature | Multi/Exec | Pipeline |
|
146
|
-
# |-----------------|-----------------|-----------------|
|
147
|
-
# | Atomicity | Yes | No |
|
148
|
-
# | Performance | Good | Better |
|
149
|
-
# | Error handling | All-or-nothing | Per-command |
|
150
|
-
# | Use case | Data consistency| Bulk operations |
|
151
|
-
#
|
152
|
-
def transaction(&)
|
153
|
-
block_result = nil
|
154
|
-
result = dbclient.multi do |conn|
|
155
|
-
Fiber[:familia_transaction] = conn
|
156
|
-
begin
|
157
|
-
block_result = yield(conn)
|
158
|
-
ensure
|
159
|
-
Fiber[:familia_transaction] = nil # cleanup reference
|
160
|
-
end
|
161
|
-
end
|
162
|
-
# Return the multi result which contains the transaction results
|
163
|
-
result
|
92
|
+
@connection_chain ||= build_connection_chain
|
93
|
+
@connection_chain.handle(uri)
|
164
94
|
end
|
165
|
-
alias multi transaction
|
166
95
|
|
167
|
-
#
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
# @return [Array] Results of all commands executed in the pipeline
|
175
|
-
#
|
176
|
-
# @example Basic pipeline usage
|
177
|
-
# Familia.pipeline do |pipe|
|
178
|
-
# pipe.set("key1", "value1")
|
179
|
-
# pipe.incr("counter")
|
180
|
-
# pipe.lpush("list", "item")
|
181
|
-
# end
|
182
|
-
# # Returns: ["OK", 2, 1] - results of all commands
|
183
|
-
#
|
184
|
-
# @example Error handling - commands succeed/fail independently
|
185
|
-
# results = Familia.pipeline do |conn|
|
186
|
-
# conn.set("valid_key", "value") # This will succeed
|
187
|
-
# conn.incr("string_key") # This will fail (wrong type)
|
188
|
-
# conn.set("another_key", "value2") # This will still succeed
|
189
|
-
# end
|
190
|
-
# # Returns: ["OK", Redis::CommandError, "OK"]
|
191
|
-
# # Notice how the error doesn't prevent other commands from executing
|
192
|
-
#
|
193
|
-
# @example Contrast with transaction behavior
|
194
|
-
# results = Familia.transaction do |conn|
|
195
|
-
# conn.set("inventory:item1", 100)
|
196
|
-
# conn.incr("invalid_key") # Fails, rolls back everything
|
197
|
-
# conn.set("inventory:item2", 200) # Won't be applied
|
198
|
-
# end
|
199
|
-
# # Result: neither item1 nor item2 are set due to the error
|
200
|
-
#
|
201
|
-
def pipeline(&)
|
202
|
-
block_result = nil
|
203
|
-
result = dbclient.pipelined do |conn|
|
204
|
-
Fiber[:familia_pipeline] = conn
|
205
|
-
begin
|
206
|
-
block_result = yield(conn)
|
207
|
-
ensure
|
208
|
-
Fiber[:familia_pipeline] = nil # cleanup reference
|
209
|
-
end
|
210
|
-
end
|
211
|
-
# Return the pipeline result which contains the command results
|
212
|
-
result
|
96
|
+
# Builds the connection chain with handlers in priority order
|
97
|
+
def build_connection_chain
|
98
|
+
ResponsibilityChain.new
|
99
|
+
.add_handler(Familia::Connection::FiberTransactionHandler.new)
|
100
|
+
.add_handler(FiberConnectionHandler.new)
|
101
|
+
.add_handler(ProviderConnectionHandler.new)
|
102
|
+
.add_handler(CreateConnectionHandler.new)
|
213
103
|
end
|
214
104
|
|
215
|
-
# Provides explicit access to a Database connection.
|
216
|
-
#
|
217
|
-
# This method is useful when you need direct access to a connection
|
218
|
-
# for operations not covered by other methods. The connection is
|
219
|
-
# properly managed and returned to the pool (if using connection_provider).
|
220
|
-
#
|
221
|
-
# @yield [Redis] A Database connection
|
222
|
-
# @return The result of the block
|
223
|
-
#
|
224
|
-
# @example Using with_connection for custom operations
|
225
|
-
# Familia.with_connection do |conn|
|
226
|
-
# conn.set("custom_key", "value")
|
227
|
-
# conn.expire("custom_key", 3600)
|
228
|
-
# end
|
229
|
-
#
|
230
|
-
def with_connection(&)
|
231
|
-
yield dbclient
|
232
|
-
end
|
233
|
-
|
234
|
-
private
|
235
|
-
|
236
105
|
# Normalizes various URI formats to a consistent URI object
|
106
|
+
# Made public so handlers can use it
|
237
107
|
def normalize_uri(uri)
|
238
108
|
case uri
|
239
109
|
when Integer
|
@@ -250,5 +120,9 @@ module Familia
|
|
250
120
|
raise ArgumentError, "Invalid URI type: #{uri.class.name}"
|
251
121
|
end
|
252
122
|
end
|
123
|
+
|
124
|
+
# Extend self with submodules to make their methods available as module methods
|
125
|
+
include Familia::Connection::Middleware
|
126
|
+
include Familia::Connection::Operations
|
253
127
|
end
|
254
128
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# lib/familia/data_type/definition.rb
|
2
|
+
|
3
|
+
module Familia
|
4
|
+
class DataType
|
5
|
+
# ClassMethods - Class-level DSL methods for defining DataType behavior
|
6
|
+
#
|
7
|
+
# This module is extended into classes that inherit from Familia::DataType,
|
8
|
+
# providing class methods for type registration, configuration, and inheritance.
|
9
|
+
#
|
10
|
+
# Key features:
|
11
|
+
# * Type registration system for creating DataType subclasses
|
12
|
+
# * Database and connection configuration
|
13
|
+
# * Inheritance hooks for propagating settings
|
14
|
+
# * Option validation and filtering
|
15
|
+
#
|
16
|
+
module ClassMethods
|
17
|
+
attr_accessor :parent, :suffix, :prefix, :uri
|
18
|
+
attr_writer :logical_database
|
19
|
+
|
20
|
+
# To be called inside every class that inherits DataType
|
21
|
+
# +methname+ is the term used for the class and instance methods
|
22
|
+
# that are created for the given +klass+ (e.g. set, list, etc)
|
23
|
+
def register(klass, methname)
|
24
|
+
Familia.trace :REGISTER, nil, "[#{self}] Registering #{klass} as #{methname.inspect}" if Familia.debug?
|
25
|
+
|
26
|
+
@registered_types[methname] = klass
|
27
|
+
end
|
28
|
+
|
29
|
+
# Get the registered type class from a given method name
|
30
|
+
# +methname+ is the method name used to register the class (e.g. :set, :list, etc)
|
31
|
+
# Returns the registered class or nil if not found
|
32
|
+
def registered_type(methname)
|
33
|
+
@registered_types[methname]
|
34
|
+
end
|
35
|
+
|
36
|
+
def logical_database(val = nil)
|
37
|
+
@logical_database = val unless val.nil?
|
38
|
+
@logical_database || parent&.logical_database
|
39
|
+
end
|
40
|
+
|
41
|
+
def uri(val = nil)
|
42
|
+
@uri = val unless val.nil?
|
43
|
+
@uri || (parent ? parent.uri : Familia.uri)
|
44
|
+
end
|
45
|
+
|
46
|
+
def inherited(obj)
|
47
|
+
Familia.trace :DATATYPE, nil, "#{obj} is my kinda type" if Familia.debug?
|
48
|
+
obj.logical_database = logical_database
|
49
|
+
obj.default_expiration = default_expiration # method added via Features::Expiration
|
50
|
+
obj.uri = uri
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
def valid_keys_only(opts)
|
55
|
+
opts.slice(*DataType.valid_options)
|
56
|
+
end
|
57
|
+
|
58
|
+
def relations?
|
59
|
+
@has_related_fields ||= false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -1,56 +1,58 @@
|
|
1
1
|
# lib/familia/data_type/commands.rb
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
3
|
+
module Familia
|
4
|
+
class DataType
|
5
|
+
# Must be included in all DataType classes to provide Valkey/Redis
|
6
|
+
# commands. The class must have a dbkey method.
|
7
|
+
module Commands
|
8
|
+
def move(logical_database)
|
9
|
+
dbclient.move dbkey, logical_database
|
10
|
+
end
|
11
|
+
|
12
|
+
def rename(newkey)
|
13
|
+
dbclient.rename dbkey, newkey
|
14
|
+
end
|
15
|
+
|
16
|
+
def renamenx(newkey)
|
17
|
+
dbclient.renamenx dbkey, newkey
|
18
|
+
end
|
19
|
+
|
20
|
+
def type
|
21
|
+
dbclient.type dbkey
|
22
|
+
end
|
23
|
+
|
24
|
+
# Deletes the entire dbkey
|
25
|
+
# @return [Boolean] true if the key was deleted, false otherwise
|
26
|
+
def delete!
|
27
|
+
Familia.trace :DELETE!, nil, uri if Familia.debug?
|
28
|
+
ret = dbclient.del dbkey
|
29
|
+
ret.positive?
|
30
|
+
end
|
31
|
+
alias clear delete!
|
32
|
+
|
33
|
+
def exists?
|
34
|
+
dbclient.exists(dbkey) && !size.zero?
|
35
|
+
end
|
36
|
+
|
37
|
+
def current_expiration
|
38
|
+
dbclient.ttl dbkey
|
39
|
+
end
|
40
|
+
|
41
|
+
def expire(sec)
|
42
|
+
dbclient.expire dbkey, sec.to_i
|
43
|
+
end
|
44
|
+
|
45
|
+
def expireat(unixtime)
|
46
|
+
dbclient.expireat dbkey, unixtime
|
47
|
+
end
|
48
|
+
|
49
|
+
def persist
|
50
|
+
dbclient.persist dbkey
|
51
|
+
end
|
52
|
+
|
53
|
+
def echo(*args)
|
54
|
+
dbclient.echo "[#{self.class}] #{args.join(' ')} (#{opts&.fetch(:class, '<no opts>')})"
|
55
|
+
end
|
54
56
|
end
|
55
57
|
end
|
56
58
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# lib/familia/data_type/connection.rb
|
2
|
+
|
3
|
+
module Familia
|
4
|
+
class DataType
|
5
|
+
# Connection - Instance-level connection and key generation methods
|
6
|
+
#
|
7
|
+
# This module provides instance methods for database connection resolution
|
8
|
+
# and Redis key generation for DataType objects.
|
9
|
+
#
|
10
|
+
# Key features:
|
11
|
+
# * Database connection resolution with Chain of Responsibility pattern
|
12
|
+
# * Redis key generation based on parent context
|
13
|
+
# * Direct database access for advanced operations
|
14
|
+
#
|
15
|
+
module Connection
|
16
|
+
# TODO: Replace with Chain of Responsibility pattern
|
17
|
+
def dbclient
|
18
|
+
return Fiber[:familia_transaction] if Fiber[:familia_transaction]
|
19
|
+
return @dbclient if @dbclient
|
20
|
+
|
21
|
+
# Delegate to parent if present, otherwise fall back to Familia
|
22
|
+
parent ? parent.dbclient : Familia.dbclient(opts[:logical_database])
|
23
|
+
end
|
24
|
+
|
25
|
+
# Produces the full dbkey for this object.
|
26
|
+
#
|
27
|
+
# @return [String] The full dbkey.
|
28
|
+
#
|
29
|
+
# This method determines the appropriate dbkey based on the context of the DataType object:
|
30
|
+
#
|
31
|
+
# 1. If a hardcoded key is set in the options, it returns that key.
|
32
|
+
# 2. For instance-level DataType objects, it uses the parent instance's dbkey method.
|
33
|
+
# 3. For class-level DataType objects, it uses the parent class's dbkey method.
|
34
|
+
# 4. For standalone DataType objects, it uses the keystring as the full dbkey.
|
35
|
+
#
|
36
|
+
# For class-level DataType objects (parent_class? == true):
|
37
|
+
# - The suffix is optional and used to differentiate between different types of objects.
|
38
|
+
# - If no suffix is provided, the class's default suffix is used (via the self.suffix method).
|
39
|
+
# - If a nil suffix is explicitly passed, it won't appear in the resulting dbkey.
|
40
|
+
# - Passing nil as the suffix is how class-level DataType objects are created without
|
41
|
+
# the global default 'object' suffix.
|
42
|
+
#
|
43
|
+
# @example Instance-level DataType
|
44
|
+
# user_instance.some_datatype.dbkey # => "user:123:some_datatype"
|
45
|
+
#
|
46
|
+
# @example Class-level DataType
|
47
|
+
# User.some_datatype.dbkey # => "user:some_datatype"
|
48
|
+
#
|
49
|
+
# @example Standalone DataType
|
50
|
+
# DataType.new("mykey").dbkey # => "mykey"
|
51
|
+
#
|
52
|
+
# @example Class-level DataType with explicit nil suffix
|
53
|
+
# User.dbkey("123", nil) # => "user:123"
|
54
|
+
#
|
55
|
+
def dbkey
|
56
|
+
# Return the hardcoded key if it's set. This is useful for
|
57
|
+
# support legacy keys that aren't derived in the same way.
|
58
|
+
return opts[:dbkey] if opts[:dbkey]
|
59
|
+
|
60
|
+
if parent_instance?
|
61
|
+
# This is an instance-level datatype object so the parent instance's
|
62
|
+
# dbkey method is defined in Familia::Horreum::InstanceMethods.
|
63
|
+
parent.dbkey(keystring)
|
64
|
+
elsif parent_class?
|
65
|
+
# This is a class-level datatype object so the parent class' dbkey
|
66
|
+
# method is defined in Familia::Horreum::DefinitionMethods.
|
67
|
+
parent.dbkey(keystring, nil)
|
68
|
+
else
|
69
|
+
# This is a standalone DataType object where it's keystring
|
70
|
+
# is the full database key (dbkey).
|
71
|
+
keystring
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Provides a structured way to "gear down" to run db commands that are
|
76
|
+
# not implemented in our DataType classes since we intentionally don't
|
77
|
+
# have a method_missing method.
|
78
|
+
def direct_access
|
79
|
+
yield(dbclient, dbkey)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|