familia 1.2.0 → 2.0.0.pre.pre

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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +68 -0
  3. data/.github/workflows/docs.yml +64 -0
  4. data/.gitignore +4 -0
  5. data/.pre-commit-config.yaml +3 -1
  6. data/.rubocop.yml +16 -9
  7. data/.rubocop_todo.yml +177 -31
  8. data/.yardopts +9 -0
  9. data/CLAUDE.md +141 -0
  10. data/Gemfile +15 -2
  11. data/Gemfile.lock +76 -34
  12. data/README.md +39 -23
  13. data/bin/irb +3 -0
  14. data/docs/connection_pooling.md +317 -0
  15. data/familia.gemspec +9 -5
  16. data/lib/familia/base.rb +19 -9
  17. data/lib/familia/connection.rb +232 -65
  18. data/lib/familia/core_ext.rb +1 -1
  19. data/lib/familia/datatype/commands.rb +59 -0
  20. data/lib/familia/{redistype → datatype}/serialization.rb +9 -13
  21. data/lib/familia/{redistype → datatype}/types/hashkey.rb +25 -25
  22. data/lib/familia/{redistype → datatype}/types/list.rb +13 -13
  23. data/lib/familia/{redistype → datatype}/types/sorted_set.rb +20 -20
  24. data/lib/familia/{redistype → datatype}/types/string.rb +22 -21
  25. data/lib/familia/{redistype → datatype}/types/unsorted_set.rb +11 -11
  26. data/lib/familia/datatype.rb +243 -0
  27. data/lib/familia/errors.rb +5 -2
  28. data/lib/familia/features/expiration.rb +33 -34
  29. data/lib/familia/features/quantization.rb +9 -3
  30. data/lib/familia/features/safe_dump.rb +2 -3
  31. data/lib/familia/features.rb +2 -2
  32. data/lib/familia/horreum/class_methods.rb +97 -110
  33. data/lib/familia/horreum/commands.rb +46 -51
  34. data/lib/familia/horreum/connection.rb +82 -0
  35. data/lib/familia/horreum/{relations_management.rb → related_fields_management.rb} +37 -35
  36. data/lib/familia/horreum/serialization.rb +61 -198
  37. data/lib/familia/horreum/settings.rb +6 -17
  38. data/lib/familia/horreum/utils.rb +11 -10
  39. data/lib/familia/horreum.rb +69 -60
  40. data/lib/familia/logging.rb +12 -12
  41. data/lib/familia/multi_result.rb +72 -0
  42. data/lib/familia/refinements.rb +7 -44
  43. data/lib/familia/settings.rb +11 -11
  44. data/lib/familia/utils.rb +123 -90
  45. data/lib/familia/version.rb +4 -21
  46. data/lib/familia.rb +17 -12
  47. data/lib/middleware/database_middleware.rb +150 -0
  48. data/try/configuration/scenarios_try.rb +65 -0
  49. data/try/core/connection_try.rb +58 -0
  50. data/try/core/errors_try.rb +93 -0
  51. data/try/core/extensions_try.rb +26 -0
  52. data/try/{10_familia_try.rb → core/familia_extended_try.rb} +11 -10
  53. data/try/{00_familia_try.rb → core/familia_try.rb} +5 -3
  54. data/try/core/middleware_try.rb +68 -0
  55. data/try/core/refinements_try.rb +39 -0
  56. data/try/core/settings_try.rb +76 -0
  57. data/try/core/tools_try.rb +54 -0
  58. data/try/core/utils_try.rb +189 -0
  59. data/try/{26_redis_bool_try.rb → datatypes/boolean_try.rb} +4 -2
  60. data/try/datatypes/datatype_base_try.rb +69 -0
  61. data/try/{25_redis_type_hash_try.rb → datatypes/hash_try.rb} +5 -3
  62. data/try/{23_redis_type_list_try.rb → datatypes/list_try.rb} +5 -3
  63. data/try/{22_redis_type_set_try.rb → datatypes/set_try.rb} +5 -3
  64. data/try/{21_redis_type_zset_try.rb → datatypes/sorted_set_try.rb} +6 -4
  65. data/try/{24_redis_type_string_try.rb → datatypes/string_try.rb} +8 -8
  66. data/try/edge_cases/empty_identifiers_try.rb +48 -0
  67. data/try/{92_symbolize_try.rb → edge_cases/hash_symbolization_try.rb} +12 -7
  68. data/try/edge_cases/json_serialization_try.rb +85 -0
  69. data/try/edge_cases/race_conditions_try.rb +60 -0
  70. data/try/edge_cases/reserved_keywords_try.rb +59 -0
  71. data/try/{93_string_coercion_try.rb → edge_cases/string_coercion_try.rb} +60 -59
  72. data/try/edge_cases/ttl_side_effects_try.rb +51 -0
  73. data/try/features/expiration_try.rb +86 -0
  74. data/try/features/quantization_try.rb +90 -0
  75. data/try/{35_feature_safedump_try.rb → features/safe_dump_advanced_try.rb} +7 -6
  76. data/try/features/safe_dump_try.rb +137 -0
  77. data/try/{test_helpers.rb → helpers/test_helpers.rb} +25 -60
  78. data/try/{27_redis_horreum_try.rb → horreum/base_try.rb} +39 -14
  79. data/try/horreum/class_methods_try.rb +41 -0
  80. data/try/horreum/commands_try.rb +49 -0
  81. data/try/{29_redis_horreum_initialization_try.rb → horreum/initialization_try.rb} +9 -7
  82. data/try/horreum/relations_try.rb +146 -0
  83. data/try/{28_redis_horreum_serialization_try.rb → horreum/serialization_try.rb} +13 -11
  84. data/try/horreum/settings_try.rb +43 -0
  85. data/try/integration/cross_component_try.rb +46 -0
  86. data/try/{41_customer_safedump_try.rb → models/customer_safe_dump_try.rb} +9 -7
  87. data/try/{40_customer_try.rb → models/customer_try.rb} +20 -17
  88. data/try/models/datatype_base_try.rb +101 -0
  89. data/try/{30_familia_object_try.rb → models/familia_object_try.rb} +18 -16
  90. data/try/performance/benchmarks_try.rb +55 -0
  91. data/try/pooling/README.md +20 -0
  92. data/try/pooling/configurable_stress_test_try.rb +435 -0
  93. data/try/pooling/connection_pool_test_try.rb +273 -0
  94. data/try/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
  95. data/try/pooling/lib/connection_pool_metrics.rb +372 -0
  96. data/try/pooling/lib/connection_pool_stress_test.rb +959 -0
  97. data/try/pooling/lib/connection_pool_threading_models.rb +421 -0
  98. data/try/pooling/lib/visualize_stress_results.rb +434 -0
  99. data/try/pooling/pool_siege_try.rb +509 -0
  100. data/try/pooling/run_stress_tests_try.rb +482 -0
  101. data/try/prototypes/atomic_saves_v1_context_proxy.rb +121 -0
  102. data/try/prototypes/atomic_saves_v2_connection_switching.rb +161 -0
  103. data/try/prototypes/atomic_saves_v3_connection_pool.rb +189 -0
  104. data/try/prototypes/atomic_saves_v4.rb +105 -0
  105. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +124 -0
  106. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
  107. metadata +140 -43
  108. data/.github/workflows/ruby.yml +0 -71
  109. data/VERSION.yml +0 -4
  110. data/lib/familia/redistype/commands.rb +0 -59
  111. data/lib/familia/redistype.rb +0 -228
  112. data/lib/familia/tools.rb +0 -68
  113. data/lib/redis_middleware.rb +0 -109
  114. data/try/20_redis_type_try.rb +0 -70
  115. data/try/91_json_bug_try.rb +0 -86
@@ -0,0 +1,82 @@
1
+ # lib/familia/horreum/connection.rb
2
+
3
+ module Familia
4
+ class Horreum
5
+
6
+ # Familia::Horreum::Connection
7
+ #
8
+ module Connection
9
+ attr_reader :uri
10
+
11
+ # Returns the Database connection for the class.
12
+ #
13
+ # This method retrieves the Database connection instance for the class. If no
14
+ # connection is set, it initializes a new connection using the provided URI
15
+ # or database configuration.
16
+ #
17
+ # @return [Redis] the Database connection instance.
18
+ #
19
+ def dbclient
20
+ Fiber[:familia_transaction] || @dbclient || Familia.dbclient(uri || logical_database)
21
+ end
22
+
23
+ def connect(*)
24
+ Familia.connect(*)
25
+ end
26
+
27
+ def uri=(uri)
28
+ @uri = normalize_uri(uri)
29
+ end
30
+ alias url uri
31
+ alias url= uri=
32
+
33
+ # Perform a sacred Database transaction ritual.
34
+ #
35
+ # This method creates a protective circle around your Database operations,
36
+ # ensuring they all succeed or fail together. It's like a group hug for your
37
+ # data operations, but with more ACID properties.
38
+ #
39
+ # @yield [conn] A block where you can perform your Database incantations.
40
+ # @yieldparam conn [Redis] A Database connection in multi mode.
41
+ #
42
+ # @example Performing a Database rain dance
43
+ # transaction do |conn|
44
+ # conn.set("weather", "rainy")
45
+ # conn.set("mood", "melancholic")
46
+ # end
47
+ #
48
+ # @note This method works with the global Familia.transaction context when available
49
+ #
50
+ def transaction
51
+ # If we're already in a Familia.transaction context, just yield the multi connection
52
+ if Fiber[:familia_transaction]
53
+ yield(Fiber[:familia_transaction])
54
+ else
55
+ # Otherwise, create a local transaction
56
+ block_result = dbclient.multi do |conn|
57
+ yield(conn)
58
+ end
59
+ end
60
+ block_result
61
+ end
62
+ alias multi transaction
63
+
64
+ def pipeline
65
+ # If we're already in a Familia.pipeline context, just yield the pipeline connection
66
+ if Fiber[:familia_pipeline]
67
+ yield(Fiber[:familia_pipeline])
68
+ else
69
+ # Otherwise, create a local transaction
70
+ block_result = dbclient.pipeline do |conn|
71
+ yield(conn)
72
+ end
73
+ end
74
+ block_result
75
+ end
76
+
77
+ end
78
+
79
+ # Include Connection module for instance methods after it's loaded
80
+ include Familia::Horreum::Connection
81
+ end
82
+ end
@@ -1,21 +1,23 @@
1
+ # lib/familia/horreum/related_fields_management.rb
2
+
1
3
  module Familia
2
4
  class Horreum
3
5
  #
4
- # RelationsManagement: Manages Redis-type fields and relations
6
+ # RelatedFieldsManagement: Manages DataType fields and relations
5
7
  #
6
8
  # This module uses metaprogramming to dynamically create methods
7
- # for managing different types of Redis objects (e.g., sets, lists, hashes).
9
+ # for managing different types of Database objects (e.g., sets, lists, hashes).
8
10
  #
9
11
  # Key metaprogramming features:
10
- # * Dynamically defines methods for each Redis type (e.g., set, list, hashkey)
12
+ # * Dynamically defines methods for each Database type (e.g., set, list, hashkey)
11
13
  # * Creates both instance-level and class-level relation methods
12
14
  # * Provides query methods for checking relation types
13
15
  #
14
16
  # Usage:
15
- # Include this module in classes that need Redis-type management
17
+ # Include this module in classes that need DataType management
16
18
  # Call setup_relations_accessors to initialize the feature
17
19
  #
18
- module RelationsManagement
20
+ module RelatedFieldsManagement
19
21
  # A practical flag to indicate that a Horreum member has relations,
20
22
  # not just theoretically but actually at least one list/haskey/etc.
21
23
  @has_relations = nil
@@ -26,18 +28,18 @@ module Familia
26
28
  end
27
29
 
28
30
  module ClassMethods
29
- # Sets up all Redis-type related methods
31
+ # Sets up all DataType related methods
30
32
  # This method is the core of the metaprogramming logic
31
33
  #
32
34
  def setup_relations_accessors
33
- Familia::RedisType.registered_types.each_pair do |kind, klass|
35
+ Familia::DataType.registered_types.each_pair do |kind, klass|
34
36
  Familia.ld "[registered_types] #{kind} => #{klass}"
35
37
 
36
38
  # Dynamically define instance-level relation methods
37
39
  #
38
40
  # Once defined, these methods can be used at the instance-level of a
39
41
  # Familia member to define *instance-level* relations to any of the
40
- # RedisType types (e.g. set, list, hash, etc).
42
+ # DataType types (e.g. set, list, hash, etc).
41
43
  #
42
44
  define_method :"#{kind}" do |*args|
43
45
  name, opts = *args
@@ -45,15 +47,15 @@ module Familia
45
47
  # As log as we have at least one relation, we can set this flag.
46
48
  @has_relations = true
47
49
 
48
- attach_instance_redis_object_relation name, klass, opts
50
+ attach_instance_related_field name, klass, opts
49
51
  end
50
52
  define_method :"#{kind}?" do |name|
51
- obj = redis_types[name.to_s.to_sym]
53
+ obj = related_fields[name.to_s.to_sym]
52
54
  !obj.nil? && klass == obj.klass
53
55
  end
54
56
  define_method :"#{kind}s" do
55
- names = redis_types.keys.select { |name| send(:"#{kind}?", name) }
56
- names.collect! { |name| redis_types[name] }
57
+ names = related_fields.keys.select { |name| send(:"#{kind}?", name) }
58
+ names.collect! { |name| related_fields[name] }
57
59
  names
58
60
  end
59
61
 
@@ -61,24 +63,24 @@ module Familia
61
63
  #
62
64
  # Once defined, these methods can be used at the class-level of a
63
65
  # Familia member to define *class-level relations* to any of the
64
- # RedisType types (e.g. class_set, class_list, class_hash, etc).
66
+ # DataType types (e.g. class_set, class_list, class_hash, etc).
65
67
  #
66
68
  define_method :"class_#{kind}" do |*args|
67
69
  name, opts = *args
68
- attach_class_redis_object_relation name, klass, opts
70
+ attach_class_related_field name, klass, opts
69
71
  end
70
72
  define_method :"class_#{kind}?" do |name|
71
- obj = class_redis_types[name.to_s.to_sym]
73
+ obj = class_related_fields[name.to_s.to_sym]
72
74
  !obj.nil? && klass == obj.klass
73
75
  end
74
76
  define_method :"class_#{kind}s" do
75
- names = class_redis_types.keys.select { |name| send(:"class_#{kind}?", name) }
76
- # TODO: This returns instances of the RedisType class which
77
+ names = class_related_fields.keys.select { |name| send(:"class_#{kind}?", name) }
78
+ # TODO: This returns instances of the DataType class which
77
79
  # also contain the options. This is different from the instance
78
- # RedisTypes defined above which returns the Struct of name, klass, and opts.
80
+ # DataTypes defined above which returns the Struct of name, klass, and opts.
79
81
  # names.collect! { |name| self.send name }
80
82
  # OR NOT:
81
- names.collect! { |name| class_redis_types[name] }
83
+ names.collect! { |name| class_related_fields[name] }
82
84
  names
83
85
  end
84
86
  end
@@ -87,17 +89,17 @@ module Familia
87
89
  # End of ClassMethods module
88
90
 
89
91
  # Creates an instance-level relation
90
- def attach_instance_redis_object_relation(name, klass, opts)
92
+ def attach_instance_related_field(name, klass, opts)
91
93
  Familia.ld "[#{self}##{name}] Attaching instance-level #{klass} #{opts}"
92
94
  raise ArgumentError, "Name is blank (#{klass})" if name.to_s.empty?
93
95
 
94
96
  name = name.to_s.to_sym
95
97
  opts ||= {}
96
98
 
97
- redis_types[name] = Struct.new(:name, :klass, :opts).new
98
- redis_types[name].name = name
99
- redis_types[name].klass = klass
100
- redis_types[name].opts = opts
99
+ related_fields[name] = Struct.new(:name, :klass, :opts).new
100
+ related_fields[name].name = name
101
+ related_fields[name].klass = klass
102
+ related_fields[name].opts = opts
101
103
 
102
104
  attr_reader name
103
105
 
@@ -108,11 +110,11 @@ module Familia
108
110
  !send(name).empty?
109
111
  end
110
112
 
111
- redis_types[name]
113
+ related_fields[name]
112
114
  end
113
115
 
114
116
  # Creates a class-level relation
115
- def attach_class_redis_object_relation(name, klass, opts)
117
+ def attach_class_related_field(name, klass, opts)
116
118
  Familia.ld "[#{self}.#{name}] Attaching class-level #{klass} #{opts}"
117
119
  raise ArgumentError, 'Name is blank (klass)' if name.to_s.empty?
118
120
 
@@ -120,10 +122,10 @@ module Familia
120
122
  opts = opts.nil? ? {} : opts.clone
121
123
  opts[:parent] = self unless opts.key?(:parent)
122
124
 
123
- class_redis_types[name] = Struct.new(:name, :klass, :opts).new
124
- class_redis_types[name].name = name
125
- class_redis_types[name].klass = klass
126
- class_redis_types[name].opts = opts
125
+ class_related_fields[name] = Struct.new(:name, :klass, :opts).new
126
+ class_related_fields[name].name = name
127
+ class_related_fields[name].klass = klass
128
+ class_related_fields[name].opts = opts
127
129
 
128
130
  # An accessor method created in the metaclass will
129
131
  # access the instance variables for this class.
@@ -136,13 +138,13 @@ module Familia
136
138
  !send(name).empty?
137
139
  end
138
140
 
139
- redis_object = klass.new name, opts
140
- redis_object.freeze
141
- instance_variable_set(:"@#{name}", redis_object)
141
+ related_field = klass.new name, opts
142
+ related_field.freeze
143
+ instance_variable_set(:"@#{name}", related_field)
142
144
 
143
- class_redis_types[name]
145
+ class_related_fields[name]
144
146
  end
145
147
  end
146
- # End of RelationsManagement module
148
+ # End of RelatedFieldsManagement module
147
149
  end
148
150
  end