familia 2.0.0.pre14 → 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 +66 -6
- 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 +4 -4
- data/docs/migrating/v2.0.0-pre12.md +2 -2
- data/docs/migrating/v2.0.0-pre13.md +1 -1
- 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/{autoloader.rb → features/autoloader.rb} +49 -23
- 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 +68 -66
- data/lib/familia/features/expiration/extensions.rb +61 -0
- data/lib/familia/features/expiration.rb +35 -87
- data/lib/familia/features/external_identifier.rb +11 -12
- data/lib/familia/features/object_identifier.rb +58 -20
- data/lib/familia/features/quantization.rb +17 -22
- 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 +69 -271
- data/lib/familia/features/safe_dump.rb +127 -132
- 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 +5 -5
- data/lib/familia/features.rb +21 -21
- 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 -15
- 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 +129 -11
- 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 +77 -45
- 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 -228
- 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/autoloadable.rb +0 -113
- 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/autoloadable/autoloadable_try.rb +0 -61
- data/try/features/relationships/categorical_permissions_try.rb +0 -515
- data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -111
- 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
@@ -8,6 +8,8 @@ module Familia
|
|
8
8
|
dbclient.hlen dbkey
|
9
9
|
end
|
10
10
|
alias size field_count
|
11
|
+
alias length field_count
|
12
|
+
alias count field_count
|
11
13
|
|
12
14
|
def empty?
|
13
15
|
field_count.zero?
|
@@ -22,13 +24,14 @@ module Familia
|
|
22
24
|
rescue TypeError => e
|
23
25
|
Familia.le "[hset]= #{e.message}"
|
24
26
|
Familia.ld "[hset]= #{dbkey} #{field}=#{val}" if Familia.debug
|
25
|
-
echo :hset,
|
27
|
+
echo :hset, Familia.pretty_stack(limit: 1) if Familia.debug # logs via echo to the db and back
|
26
28
|
klass = val.class
|
27
29
|
msg = "Cannot store #{field} => #{val.inspect} (#{klass}) in #{dbkey}"
|
28
30
|
raise e.class, msg
|
29
31
|
end
|
30
32
|
alias put []=
|
31
33
|
alias store []=
|
34
|
+
alias add []=
|
32
35
|
|
33
36
|
def [](field)
|
34
37
|
deserialize_value dbclient.hget(dbkey, field.to_s)
|
@@ -55,8 +58,8 @@ module Familia
|
|
55
58
|
end
|
56
59
|
|
57
60
|
def hgetall
|
58
|
-
dbclient.hgetall(dbkey).
|
59
|
-
|
61
|
+
dbclient.hgetall(dbkey).transform_values do |v|
|
62
|
+
deserialize_value v
|
60
63
|
end
|
61
64
|
end
|
62
65
|
alias all hgetall
|
@@ -73,7 +76,7 @@ module Familia
|
|
73
76
|
rescue TypeError => e
|
74
77
|
Familia.le "[hsetnx] #{e.message}"
|
75
78
|
Familia.ld "[hsetnx] #{dbkey} #{field}=#{val}" if Familia.debug
|
76
|
-
echo :hsetnx,
|
79
|
+
echo :hsetnx, Familia.pretty_stack(limit: 1) if Familia.debug # logs via echo to the db and back
|
77
80
|
klass = val.class
|
78
81
|
msg = "Cannot store #{field} => #{val.inspect} (#{klass}) in #{dbkey}"
|
79
82
|
raise e.class, msg
|
@@ -125,15 +128,15 @@ module Familia
|
|
125
128
|
|
126
129
|
# The Great Database Refresh-o-matic 3000 for HashKey!
|
127
130
|
#
|
128
|
-
# This method performs a complete refresh of the hash's state from
|
131
|
+
# This method performs a complete refresh of the hash's state from the database.
|
129
132
|
# It's like giving your hash a memory transfusion - out with the old state,
|
130
|
-
# in with the fresh data straight from Redis!
|
133
|
+
# in with the fresh data straight from Valkey/Redis!
|
131
134
|
#
|
132
135
|
# @note This operation is atomic - it either succeeds completely or fails
|
133
136
|
# safely. Any unsaved changes to the hash will be overwritten.
|
134
137
|
#
|
135
138
|
# @return [void] Returns nothing, but your hash will be sparkling clean
|
136
|
-
# with all its fields synchronized with
|
139
|
+
# with all its fields synchronized with the database.
|
137
140
|
#
|
138
141
|
# @raise [Familia::KeyNotFoundError] If the dbkey for this hash no
|
139
142
|
# longer exists. Time travelers beware!
|
@@ -148,7 +151,7 @@ module Familia
|
|
148
151
|
# puts "Oops! Our hash seems to have vanished into the Database void!"
|
149
152
|
# end
|
150
153
|
def refresh!
|
151
|
-
Familia.trace :REFRESH,
|
154
|
+
Familia.trace :REFRESH, nil, uri if Familia.debug?
|
152
155
|
raise Familia::KeyNotFoundError, dbkey unless dbclient.exists(dbkey)
|
153
156
|
|
154
157
|
fields = hgetall
|
@@ -167,7 +170,7 @@ module Familia
|
|
167
170
|
# @return [self] Returns the refreshed hash, ready for more adventures!
|
168
171
|
#
|
169
172
|
# @raise [Familia::KeyNotFoundError] If the dbkey does not exist.
|
170
|
-
# The hash must exist in Redis-land for this to work!
|
173
|
+
# The hash must exist in Valkey/Redis-land for this to work!
|
171
174
|
#
|
172
175
|
# @example Refresh and chain
|
173
176
|
# my_hash.refresh.keys # Refresh and get all keys
|
@@ -179,7 +182,7 @@ module Familia
|
|
179
182
|
self
|
180
183
|
end
|
181
184
|
|
182
|
-
Familia::DataType.register self, :hash
|
185
|
+
Familia::DataType.register self, :hash
|
183
186
|
Familia::DataType.register self, :hashkey
|
184
187
|
end
|
185
188
|
end
|
@@ -1,20 +1,22 @@
|
|
1
|
-
# lib/familia/data_type/types/
|
1
|
+
# lib/familia/data_type/types/listkey.rb
|
2
2
|
|
3
3
|
module Familia
|
4
|
-
class
|
4
|
+
class ListKey < DataType
|
5
5
|
# Returns the number of elements in the list
|
6
6
|
# @return [Integer] number of elements
|
7
7
|
def element_count
|
8
8
|
dbclient.llen dbkey
|
9
9
|
end
|
10
10
|
alias size element_count
|
11
|
+
alias length element_count
|
12
|
+
alias count element_count
|
11
13
|
|
12
14
|
def empty?
|
13
15
|
element_count.zero?
|
14
16
|
end
|
15
17
|
|
16
18
|
def push *values
|
17
|
-
echo :push,
|
19
|
+
echo :push, Familia.pretty_stack(limit: 1) if Familia.debug
|
18
20
|
values.flatten.compact.each { |v| dbclient.rpush dbkey, serialize_value(v) }
|
19
21
|
dbclient.ltrim dbkey, -@opts[:maxlength], -1 if @opts[:maxlength]
|
20
22
|
update_expiration
|
@@ -23,8 +25,9 @@ module Familia
|
|
23
25
|
alias append push
|
24
26
|
|
25
27
|
def <<(val)
|
26
|
-
push
|
28
|
+
push(val)
|
27
29
|
end
|
30
|
+
alias add_element <<
|
28
31
|
alias add <<
|
29
32
|
|
30
33
|
def unshift *values
|
@@ -59,6 +62,10 @@ module Familia
|
|
59
62
|
end
|
60
63
|
alias slice []
|
61
64
|
|
65
|
+
def member?(value)
|
66
|
+
!dbclient.lpos(dbkey, serialize_value(value)).nil?
|
67
|
+
end
|
68
|
+
|
62
69
|
# Removes elements equal to value from the list
|
63
70
|
# @param value The value to remove
|
64
71
|
# @param count [Integer] Number of elements to remove (0 means all)
|
@@ -78,7 +85,7 @@ module Familia
|
|
78
85
|
end
|
79
86
|
|
80
87
|
def members(count = -1)
|
81
|
-
echo :members,
|
88
|
+
echo :members, Familia.pretty_stack(limit: 1) if Familia.debug
|
82
89
|
count -= 1 if count.positive?
|
83
90
|
range 0, count
|
84
91
|
end
|
@@ -157,5 +164,6 @@ module Familia
|
|
157
164
|
# end
|
158
165
|
|
159
166
|
Familia::DataType.register self, :list
|
167
|
+
Familia::DataType.register self, :listkey
|
160
168
|
end
|
161
169
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# lib/familia/data_type/types/lock.rb
|
2
2
|
|
3
3
|
module Familia
|
4
|
-
class Lock <
|
4
|
+
class Lock < StringKey
|
5
5
|
def initialize(*args)
|
6
6
|
super
|
7
7
|
@opts[:default] = nil
|
@@ -14,9 +14,10 @@ module Familia
|
|
14
14
|
def acquire(token = SecureRandom.uuid, ttl: 10)
|
15
15
|
success = setnx(token)
|
16
16
|
# Handle both integer (1/0) and boolean (true/false) return values
|
17
|
-
return false unless
|
17
|
+
return false unless [1, true].include?(success)
|
18
18
|
return del && false if ttl&.<=(0)
|
19
19
|
return del && false if ttl&.positive? && !expire(ttl)
|
20
|
+
|
20
21
|
token
|
21
22
|
end
|
22
23
|
|
@@ -8,6 +8,8 @@ module Familia
|
|
8
8
|
dbclient.zcard dbkey
|
9
9
|
end
|
10
10
|
alias size element_count
|
11
|
+
alias length element_count
|
12
|
+
alias count element_count
|
11
13
|
|
12
14
|
def empty?
|
13
15
|
element_count.zero?
|
@@ -24,14 +26,13 @@ module Familia
|
|
24
26
|
# @return [Integer] Returns 1 if the element is new and added, 0 if the
|
25
27
|
# element already existed and the score was updated.
|
26
28
|
#
|
27
|
-
# @example
|
28
|
-
# sorted_set << "new_element"
|
29
|
+
# @example sorted_set << "new_element"
|
29
30
|
#
|
30
31
|
# @note This is a non-standard operation for sorted sets as it doesn't allow
|
31
32
|
# specifying a custom score. Use `add` or `[]=` for more control.
|
32
33
|
#
|
33
34
|
def <<(val)
|
34
|
-
add(
|
35
|
+
add(val)
|
35
36
|
end
|
36
37
|
|
37
38
|
# NOTE: The argument order is the reverse of #add. We do this to
|
@@ -42,14 +43,24 @@ module Familia
|
|
42
43
|
# obj.metrics[VALUE] # => SCORE
|
43
44
|
#
|
44
45
|
def []=(val, score)
|
45
|
-
add
|
46
|
-
end
|
47
|
-
|
48
|
-
def add(
|
46
|
+
add val, score
|
47
|
+
end
|
48
|
+
|
49
|
+
def add(val, score = nil)
|
50
|
+
# TODO: Support some or all of the ZADD options.
|
51
|
+
# XX: Only update existing elements. Don't add new ones.
|
52
|
+
# NX: Only add new elements. Don't update existing ones.
|
53
|
+
# LT: Only update if new score < current score. Doesn't prevent adding.
|
54
|
+
# GT: Only update if new score > current score. Doesn't prevent adding.
|
55
|
+
# CH: Return total changed elements (new + updated) instead of just new.
|
56
|
+
# INCR: Acts like ZINCRBY. Only one score-element pair allowed.
|
57
|
+
# Note: GT, LT and NX options are mutually exclusive.
|
58
|
+
score ||= Familia.now
|
49
59
|
ret = dbclient.zadd dbkey, score, serialize_value(val)
|
50
60
|
update_expiration
|
51
61
|
ret
|
52
62
|
end
|
63
|
+
alias add_element add
|
53
64
|
|
54
65
|
def score(val)
|
55
66
|
ret = dbclient.zscore dbkey, serialize_value(val, strict_values: false)
|
@@ -58,7 +69,7 @@ module Familia
|
|
58
69
|
alias [] score
|
59
70
|
|
60
71
|
def member?(val)
|
61
|
-
Familia.trace :MEMBER,
|
72
|
+
Familia.trace :MEMBER, nil, "#{val}<#{val.class}>" if Familia.debug?
|
62
73
|
!rank(val).nil?
|
63
74
|
end
|
64
75
|
alias include? member?
|
@@ -132,7 +143,7 @@ module Familia
|
|
132
143
|
end
|
133
144
|
|
134
145
|
def range(sidx, eidx, opts = {})
|
135
|
-
echo :range,
|
146
|
+
echo :range, Familia.pretty_stack(limit: 1) if Familia.debug
|
136
147
|
elements = rangeraw(sidx, eidx, opts)
|
137
148
|
deserialize_values(*elements)
|
138
149
|
end
|
@@ -149,7 +160,7 @@ module Familia
|
|
149
160
|
end
|
150
161
|
|
151
162
|
def revrange(sidx, eidx, opts = {})
|
152
|
-
echo :revrange,
|
163
|
+
echo :revrange, Familia.pretty_stack(limit: 1) if Familia.debug
|
153
164
|
elements = revrangeraw(sidx, eidx, opts)
|
154
165
|
deserialize_values(*elements)
|
155
166
|
end
|
@@ -160,25 +171,25 @@ module Familia
|
|
160
171
|
|
161
172
|
# e.g. obj.metrics.rangebyscore (now-12.hours), now, :limit => [0, 10]
|
162
173
|
def rangebyscore(sscore, escore, opts = {})
|
163
|
-
echo :rangebyscore,
|
174
|
+
echo :rangebyscore, Familia.pretty_stack(limit: 1) if Familia.debug
|
164
175
|
elements = rangebyscoreraw(sscore, escore, opts)
|
165
176
|
deserialize_values(*elements)
|
166
177
|
end
|
167
178
|
|
168
179
|
def rangebyscoreraw(sscore, escore, opts = {})
|
169
|
-
echo :rangebyscoreraw,
|
180
|
+
echo :rangebyscoreraw, Familia.pretty_stack(limit: 1) if Familia.debug
|
170
181
|
dbclient.zrangebyscore(dbkey, sscore, escore, **opts)
|
171
182
|
end
|
172
183
|
|
173
184
|
# e.g. obj.metrics.revrangebyscore (now-12.hours), now, :limit => [0, 10]
|
174
185
|
def revrangebyscore(sscore, escore, opts = {})
|
175
|
-
echo :revrangebyscore,
|
186
|
+
echo :revrangebyscore, Familia.pretty_stack(limit: 1) if Familia.debug
|
176
187
|
elements = revrangebyscoreraw(sscore, escore, opts)
|
177
188
|
deserialize_values(*elements)
|
178
189
|
end
|
179
190
|
|
180
191
|
def revrangebyscoreraw(sscore, escore, opts = {})
|
181
|
-
echo :revrangebyscoreraw,
|
192
|
+
echo :revrangebyscoreraw, Familia.pretty_stack(limit: 1) if Familia.debug
|
182
193
|
opts[:with_scores] = true if opts[:withscores]
|
183
194
|
dbclient.zrevrangebyscore(dbkey, sscore, escore, opts)
|
184
195
|
end
|
@@ -207,7 +218,7 @@ module Familia
|
|
207
218
|
# @param value The value to remove from the sorted set
|
208
219
|
# @return [Integer] The number of members that were removed (0 or 1)
|
209
220
|
def remove_element(value)
|
210
|
-
Familia.trace :REMOVE_ELEMENT,
|
221
|
+
Familia.trace :REMOVE_ELEMENT, nil, "#{value}<#{value.class}>" if Familia.debug?
|
211
222
|
# We use `strict_values: false` here to allow for the deletion of values
|
212
223
|
# that are in the sorted set. If it's a horreum object, the value is
|
213
224
|
# the identifier and not a serialized version of the object. So either
|
@@ -1,7 +1,7 @@
|
|
1
|
-
# lib/familia/data_type/types/
|
1
|
+
# lib/familia/data_type/types/stringkey.rb
|
2
2
|
|
3
3
|
module Familia
|
4
|
-
class
|
4
|
+
class StringKey < DataType
|
5
5
|
def init; end
|
6
6
|
|
7
7
|
# Returns the number of elements in the list
|
@@ -10,13 +10,14 @@ module Familia
|
|
10
10
|
to_s.size
|
11
11
|
end
|
12
12
|
alias size char_count
|
13
|
+
alias length char_count
|
13
14
|
|
14
15
|
def empty?
|
15
16
|
char_count.zero?
|
16
17
|
end
|
17
18
|
|
18
19
|
def value
|
19
|
-
echo :value,
|
20
|
+
echo :value, Familia.pretty_stack(limit: 1) if Familia.debug
|
20
21
|
dbclient.setnx dbkey, @opts[:default] if @opts[:default]
|
21
22
|
deserialize_value dbclient.get(dbkey)
|
22
23
|
end
|
@@ -30,7 +31,7 @@ module Familia
|
|
30
31
|
end
|
31
32
|
|
32
33
|
def to_i
|
33
|
-
value
|
34
|
+
value.to_i
|
34
35
|
end
|
35
36
|
|
36
37
|
def value=(val)
|
@@ -118,9 +119,10 @@ module Familia
|
|
118
119
|
end
|
119
120
|
|
120
121
|
Familia::DataType.register self, :string
|
122
|
+
Familia::DataType.register self, :stringkey
|
121
123
|
end
|
122
124
|
end
|
123
125
|
|
124
|
-
# Both subclass
|
126
|
+
# Both subclass StringKey
|
125
127
|
require_relative 'lock'
|
126
128
|
require_relative 'counter'
|
@@ -1,13 +1,17 @@
|
|
1
1
|
# lib/familia/data_type/types/unsorted_set.rb
|
2
2
|
|
3
3
|
module Familia
|
4
|
-
|
4
|
+
# Familia::UnsortedSet
|
5
|
+
#
|
6
|
+
class UnsortedSet < DataType
|
5
7
|
# Returns the number of elements in the unsorted set
|
6
8
|
# @return [Integer] number of elements
|
7
9
|
def element_count
|
8
10
|
dbclient.scard dbkey
|
9
11
|
end
|
10
12
|
alias size element_count
|
13
|
+
alias length element_count
|
14
|
+
alias count element_count
|
11
15
|
|
12
16
|
def empty?
|
13
17
|
element_count.zero?
|
@@ -18,13 +22,14 @@ module Familia
|
|
18
22
|
update_expiration
|
19
23
|
self
|
20
24
|
end
|
25
|
+
alias add_element add
|
21
26
|
|
22
27
|
def <<(v)
|
23
28
|
add v
|
24
29
|
end
|
25
30
|
|
26
31
|
def members
|
27
|
-
echo :members,
|
32
|
+
echo :members, Familia.pretty_stack(limit: 1) if Familia.debug
|
28
33
|
elements = membersraw
|
29
34
|
deserialize_values(*elements)
|
30
35
|
end
|
@@ -92,35 +97,23 @@ module Familia
|
|
92
97
|
dbclient.smove dbkey, dstkey, val
|
93
98
|
end
|
94
99
|
|
95
|
-
|
96
|
-
|
100
|
+
# Get one or more random members from the set
|
101
|
+
# @param count [Integer] Number of random members to return (default: 1)
|
102
|
+
# @return [Array] Array of deserialized random members
|
103
|
+
def sample(count = 1)
|
104
|
+
deserialize_values(*sampleraw(count))
|
97
105
|
end
|
106
|
+
alias random sample
|
98
107
|
|
99
|
-
|
100
|
-
|
108
|
+
# Get one or more random members from the set without deserialization
|
109
|
+
# @param count [Integer] Number of random members to return (default: 1)
|
110
|
+
# @return [Array] Array of raw random members
|
111
|
+
def sampleraw(count = 1)
|
112
|
+
dbclient.srandmember(dbkey, count) || []
|
101
113
|
end
|
102
|
-
|
103
|
-
## Make the value stored at KEY identical to the given list
|
104
|
-
# define_method :"#{name}_sync" do |*latest|
|
105
|
-
# latest = latest.flatten.compact
|
106
|
-
# # Do nothing if we're given an empty Array.
|
107
|
-
# # Otherwise this would clear all current values
|
108
|
-
# if latest.empty?
|
109
|
-
# false
|
110
|
-
# else
|
111
|
-
# # Convert to a list of index values if we got the actual objects
|
112
|
-
# latest = latest.collect { |obj| obj.index } if klass === latest.first
|
113
|
-
# current = send("#{name_plural}raw")
|
114
|
-
# added = latest-current
|
115
|
-
# removed = current-latest
|
116
|
-
# #Familia.info "#{self.index}: adding: #{added}"
|
117
|
-
# added.each { |v| self.send("add_#{name_singular}", v) }
|
118
|
-
# #Familia.info "#{self.index}: removing: #{removed}"
|
119
|
-
# removed.each { |v| self.send("remove_#{name_singular}", v) }
|
120
|
-
# true
|
121
|
-
# end
|
122
|
-
# end
|
114
|
+
alias random sampleraw
|
123
115
|
|
124
116
|
Familia::DataType.register self, :set
|
117
|
+
Familia::DataType.register self, :unsorted_set
|
125
118
|
end
|
126
119
|
end
|
data/lib/familia/data_type.rb
CHANGED
@@ -9,7 +9,7 @@ module Familia
|
|
9
9
|
# DataType - Base class for Database data type wrappers
|
10
10
|
#
|
11
11
|
# This class provides common functionality for various Database data types
|
12
|
-
# such as String, List,
|
12
|
+
# such as String, List, UnsortedSet, SortedSet, and HashKey.
|
13
13
|
#
|
14
14
|
# @abstract Subclass and implement Database data type specific methods
|
15
15
|
class DataType
|
@@ -19,30 +19,38 @@ module Familia
|
|
19
19
|
using Familia::Refinements::TimeLiterals
|
20
20
|
|
21
21
|
@registered_types = {}
|
22
|
-
@valid_options = %i[class parent default_expiration default logical_database dbkey dbclient suffix prefix]
|
22
|
+
@valid_options = %i[class parent default_expiration default logical_database dbkey dbclient suffix prefix].freeze
|
23
23
|
@logical_database = nil
|
24
24
|
|
25
25
|
feature :expiration
|
26
26
|
feature :quantization
|
27
27
|
|
28
28
|
class << self
|
29
|
-
attr_reader :registered_types, :valid_options, :
|
30
|
-
attr_accessor :parent
|
31
|
-
attr_writer :logical_database, :uri
|
29
|
+
attr_reader :registered_types, :valid_options, :has_related_fields
|
32
30
|
end
|
33
31
|
|
34
32
|
# DataType::ClassMethods
|
35
33
|
#
|
36
34
|
module ClassMethods
|
35
|
+
attr_accessor :parent, :suffix, :prefix, :uri
|
36
|
+
attr_writer :logical_database
|
37
|
+
|
37
38
|
# To be called inside every class that inherits DataType
|
38
39
|
# +methname+ is the term used for the class and instance methods
|
39
40
|
# that are created for the given +klass+ (e.g. set, list, etc)
|
40
41
|
def register(klass, methname)
|
41
|
-
Familia.trace :REGISTER, nil, "[#{self}] Registering #{klass} as #{methname.inspect}"
|
42
|
+
Familia.trace :REGISTER, nil, "[#{self}] Registering #{klass} as #{methname.inspect}" if Familia.debug?
|
42
43
|
|
43
44
|
@registered_types[methname] = klass
|
44
45
|
end
|
45
46
|
|
47
|
+
# Get the registered type class from a given method name
|
48
|
+
# +methname+ is the method name used to register the class (e.g. :set, :list, etc)
|
49
|
+
# Returns the registered class or nil if not found
|
50
|
+
def registered_type(methname)
|
51
|
+
@registered_types[methname]
|
52
|
+
end
|
53
|
+
|
46
54
|
def logical_database(val = nil)
|
47
55
|
@logical_database = val unless val.nil?
|
48
56
|
@logical_database || parent&.logical_database
|
@@ -54,11 +62,10 @@ module Familia
|
|
54
62
|
end
|
55
63
|
|
56
64
|
def inherited(obj)
|
57
|
-
Familia.trace :DATATYPE, nil, "#{obj} is my kinda type"
|
65
|
+
Familia.trace :DATATYPE, nil, "#{obj} is my kinda type" if Familia.debug?
|
58
66
|
obj.logical_database = logical_database
|
59
67
|
obj.default_expiration = default_expiration # method added via Features::Expiration
|
60
68
|
obj.uri = uri
|
61
|
-
obj.parent = self
|
62
69
|
super
|
63
70
|
end
|
64
71
|
|
@@ -67,13 +74,14 @@ module Familia
|
|
67
74
|
end
|
68
75
|
|
69
76
|
def relations?
|
70
|
-
@
|
77
|
+
@has_related_fields ||= false
|
71
78
|
end
|
72
79
|
end
|
73
80
|
extend ClassMethods
|
74
81
|
|
75
|
-
attr_reader :keystring, :opts
|
76
|
-
|
82
|
+
attr_reader :keystring, :opts, :uri, :logical_database
|
83
|
+
|
84
|
+
alias url uri
|
77
85
|
|
78
86
|
# +keystring+: If parent is set, this will be used as the suffix
|
79
87
|
# for dbkey. Otherwise this becomes the value of the key.
|
@@ -94,10 +102,6 @@ module Familia
|
|
94
102
|
#
|
95
103
|
# :default => the default value (String-only)
|
96
104
|
#
|
97
|
-
# :logical_database => the logical database index to use (ignored if :dbclient is used).
|
98
|
-
#
|
99
|
-
# :dbclient => an instance of database client.
|
100
|
-
#
|
101
105
|
# :dbkey => a hardcoded key to use instead of the deriving the from
|
102
106
|
# the name and parent (e.g. a derived key: customer:custid:secret_counter).
|
103
107
|
#
|
@@ -111,28 +115,23 @@ module Familia
|
|
111
115
|
@keystring = @keystring.join(Familia.delim) if @keystring.is_a?(Array)
|
112
116
|
|
113
117
|
# Remove all keys from the opts that are not in the allowed list
|
114
|
-
@opts = opts || {}
|
115
|
-
@opts = DataType.valid_keys_only(@opts)
|
118
|
+
@opts = DataType.valid_keys_only(opts || {})
|
116
119
|
|
117
120
|
# Apply the options to instance method setters of the same name
|
118
121
|
@opts.each do |k, v|
|
119
|
-
# Bewarde logging :parent instance here implicitly calls #to_s which for
|
120
|
-
# some classes could include the identifier which could still be nil at
|
121
|
-
# this point. This would result in a Familia::Problem being raised. So
|
122
|
-
# to be on the safe-side here until we have a better understanding of
|
123
|
-
# the issue, we'll just log the class name for each key-value pair.
|
124
|
-
Familia.trace :SETTING, nil, " [setting] #{k} #{v.class}", caller(1..1) if Familia.debug?
|
125
122
|
send(:"#{k}=", v) if respond_to? :"#{k}="
|
126
123
|
end
|
127
124
|
|
128
125
|
init if respond_to? :init
|
129
126
|
end
|
130
127
|
|
128
|
+
# TODO: Replace with Chain of Responsibility pattern
|
131
129
|
def dbclient
|
132
130
|
return Fiber[:familia_transaction] if Fiber[:familia_transaction]
|
133
131
|
return @dbclient if @dbclient
|
134
132
|
|
135
|
-
|
133
|
+
# Delegate to parent if present, otherwise fall back to Familia
|
134
|
+
parent ? parent.dbclient : Familia.dbclient(opts[:logical_database])
|
136
135
|
end
|
137
136
|
|
138
137
|
# Produces the full dbkey for this object.
|
@@ -189,12 +188,19 @@ module Familia
|
|
189
188
|
!@opts[:class].to_s.empty? && @opts[:class].is_a?(Familia)
|
190
189
|
end
|
191
190
|
|
191
|
+
# Provides a structured way to "gear down" to run db commands that are
|
192
|
+
# not implemented in our DataType classes since we intentionally don't
|
193
|
+
# have a method_missing method.
|
194
|
+
def direct_access
|
195
|
+
yield(dbclient, dbkey)
|
196
|
+
end
|
197
|
+
|
192
198
|
def parent_instance?
|
193
|
-
parent
|
199
|
+
parent&.is_a?(Horreum::ParentDefinition)
|
194
200
|
end
|
195
201
|
|
196
202
|
def parent_class?
|
197
|
-
parent.is_a?(Class) && parent
|
203
|
+
parent.is_a?(Class) && parent.ancestors.include?(Familia::Horreum)
|
198
204
|
end
|
199
205
|
|
200
206
|
def parent?
|
@@ -202,47 +208,69 @@ module Familia
|
|
202
208
|
end
|
203
209
|
|
204
210
|
def parent
|
205
|
-
|
206
|
-
|
211
|
+
# Return cached ParentDefinition if available
|
212
|
+
return @parent if @parent
|
207
213
|
|
214
|
+
# Return class-level parent if no instance parent
|
215
|
+
return self.class.parent unless @parent_ref
|
208
216
|
|
209
|
-
|
210
|
-
|
217
|
+
# Create ParentDefinition dynamically from stored reference.
|
218
|
+
# This ensures we get the current identifier value (available after initialization)
|
219
|
+
# rather than a stale nil value from initialization time. Cannot cache due to frozen object.
|
220
|
+
Horreum::ParentDefinition.from_parent(@parent_ref)
|
221
|
+
end
|
222
|
+
|
223
|
+
def parent=(value)
|
224
|
+
case value
|
225
|
+
when Horreum::ParentDefinition
|
226
|
+
@parent = value
|
227
|
+
when nil
|
228
|
+
@parent = nil
|
229
|
+
@parent_ref = nil
|
230
|
+
else
|
231
|
+
# Store parent instance reference for lazy ParentDefinition creation.
|
232
|
+
# During initialization, the parent's identifier may not be available yet,
|
233
|
+
# so we defer ParentDefinition creation until first access for memory efficiency.
|
234
|
+
# Note: @parent_ref is not cleared after use because DataType objects are frozen.
|
235
|
+
@parent_ref = value
|
236
|
+
@parent = nil # Will be created dynamically in parent method
|
237
|
+
end
|
211
238
|
end
|
212
239
|
|
213
240
|
def uri
|
214
|
-
#
|
215
|
-
return @
|
216
|
-
|
217
|
-
# If
|
218
|
-
if parent
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
241
|
+
# Return explicit instance URI if set
|
242
|
+
return @uri if @uri
|
243
|
+
|
244
|
+
# If we have a parent with logical_database, build URI with that database
|
245
|
+
if parent && parent.respond_to?(:logical_database) && parent.logical_database
|
246
|
+
new_uri = (self.class.uri || Familia.uri).dup
|
247
|
+
new_uri.db = parent.logical_database
|
248
|
+
new_uri
|
249
|
+
else
|
250
|
+
# Fall back to class-level URI or global Familia.uri
|
251
|
+
self.class.uri || Familia.uri
|
225
252
|
end
|
253
|
+
end
|
226
254
|
|
227
|
-
|
228
|
-
|
255
|
+
def uri=(value)
|
256
|
+
@uri = value
|
229
257
|
end
|
230
258
|
|
231
259
|
def dump_method
|
232
|
-
|
260
|
+
self.class.dump_method
|
233
261
|
end
|
234
262
|
|
235
263
|
def load_method
|
236
|
-
|
264
|
+
self.class.load_method
|
237
265
|
end
|
238
266
|
|
239
267
|
include Commands
|
240
268
|
include Serialization
|
241
269
|
end
|
242
270
|
|
243
|
-
require_relative 'data_type/types/
|
271
|
+
require_relative 'data_type/types/listkey'
|
244
272
|
require_relative 'data_type/types/unsorted_set'
|
245
273
|
require_relative 'data_type/types/sorted_set'
|
246
274
|
require_relative 'data_type/types/hashkey'
|
247
|
-
require_relative 'data_type/types/
|
275
|
+
require_relative 'data_type/types/stringkey'
|
248
276
|
end
|