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.
Files changed (288) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -2
  3. data/.github/workflows/code-quality.yml +138 -0
  4. data/.github/workflows/code-smells.yml +85 -0
  5. data/.github/workflows/docs.yml +31 -8
  6. data/.gitignore +3 -1
  7. data/.pre-commit-config.yaml +7 -1
  8. data/.reek.yml +98 -0
  9. data/.rubocop.yml +54 -10
  10. data/.talismanrc +9 -0
  11. data/.yardopts +18 -13
  12. data/CHANGELOG.rst +86 -4
  13. data/CLAUDE.md +39 -1
  14. data/Gemfile +6 -5
  15. data/Gemfile.lock +99 -23
  16. data/LICENSE.txt +1 -1
  17. data/README.md +285 -85
  18. data/changelog.d/README.md +2 -2
  19. data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
  20. data/docs/archive/FAMILIA_TECHNICAL.md +42 -42
  21. data/docs/archive/FAMILIA_UPDATE.md +3 -3
  22. data/docs/archive/README.md +3 -2
  23. data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
  24. data/docs/conf.py +29 -0
  25. data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
  26. data/docs/guides/feature-encrypted-fields.md +785 -0
  27. data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
  28. data/docs/guides/feature-external-identifiers.md +637 -0
  29. data/docs/guides/feature-object-identifiers.md +435 -0
  30. data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
  31. data/docs/guides/feature-relationships-methods.md +684 -0
  32. data/docs/guides/feature-relationships.md +200 -0
  33. data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
  34. data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
  35. data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
  36. data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
  37. data/docs/guides/index.md +176 -0
  38. data/docs/guides/{Security-Model.md → security-model.md} +1 -1
  39. data/docs/migrating/v2.0.0-pre.md +1 -1
  40. data/docs/migrating/v2.0.0-pre11.md +2 -2
  41. data/docs/migrating/v2.0.0-pre12.md +2 -2
  42. data/docs/migrating/v2.0.0-pre5.md +33 -12
  43. data/docs/migrating/v2.0.0-pre6.md +2 -2
  44. data/docs/migrating/v2.0.0-pre7.md +8 -8
  45. data/docs/overview.md +624 -20
  46. data/docs/reference/api-technical.md +1365 -0
  47. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
  48. data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
  49. data/examples/autoloader/mega_customer.rb +3 -1
  50. data/examples/encrypted_fields.rb +378 -0
  51. data/examples/json_usage_patterns.rb +144 -0
  52. data/examples/relationships.rb +13 -13
  53. data/examples/safe_dump.rb +7 -7
  54. data/examples/single_connection_transaction_confusions.rb +379 -0
  55. data/lib/familia/base.rb +51 -10
  56. data/lib/familia/connection/handlers.rb +223 -0
  57. data/lib/familia/connection/individual_command_proxy.rb +64 -0
  58. data/lib/familia/connection/middleware.rb +75 -0
  59. data/lib/familia/connection/operation_core.rb +93 -0
  60. data/lib/familia/connection/operations.rb +277 -0
  61. data/lib/familia/connection/pipeline_core.rb +87 -0
  62. data/lib/familia/connection/transaction_core.rb +100 -0
  63. data/lib/familia/connection.rb +60 -186
  64. data/lib/familia/data_type/class_methods.rb +63 -0
  65. data/lib/familia/data_type/commands.rb +53 -51
  66. data/lib/familia/data_type/connection.rb +83 -0
  67. data/lib/familia/data_type/serialization.rb +108 -107
  68. data/lib/familia/data_type/settings.rb +96 -0
  69. data/lib/familia/data_type/types/counter.rb +1 -1
  70. data/lib/familia/data_type/types/hashkey.rb +15 -11
  71. data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
  72. data/lib/familia/data_type/types/lock.rb +3 -2
  73. data/lib/familia/data_type/types/sorted_set.rb +128 -14
  74. data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -9
  75. data/lib/familia/data_type/types/unsorted_set.rb +20 -27
  76. data/lib/familia/data_type.rb +12 -171
  77. data/lib/familia/distinguisher.rb +85 -0
  78. data/lib/familia/encryption/encrypted_data.rb +15 -24
  79. data/lib/familia/encryption/manager.rb +6 -4
  80. data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
  81. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
  82. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
  83. data/lib/familia/encryption/request_cache.rb +7 -7
  84. data/lib/familia/encryption.rb +2 -3
  85. data/lib/familia/errors.rb +9 -3
  86. data/lib/familia/features/autoloader.rb +30 -12
  87. data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
  88. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
  89. data/lib/familia/features/encrypted_fields.rb +71 -66
  90. data/lib/familia/features/expiration/extensions.rb +1 -1
  91. data/lib/familia/features/expiration.rb +31 -26
  92. data/lib/familia/features/external_identifier.rb +57 -19
  93. data/lib/familia/features/object_identifier.rb +134 -25
  94. data/lib/familia/features/quantization.rb +16 -21
  95. data/lib/familia/features/relationships/README.md +97 -0
  96. data/lib/familia/features/relationships/collection_operations.rb +104 -0
  97. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
  98. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +306 -0
  99. data/lib/familia/features/relationships/indexing.rb +182 -256
  100. data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
  101. data/lib/familia/features/relationships/participation/participant_methods.rb +164 -0
  102. data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
  103. data/lib/familia/features/relationships/participation.rb +656 -0
  104. data/lib/familia/features/relationships/participation_relationship.rb +31 -0
  105. data/lib/familia/features/relationships/score_encoding.rb +20 -20
  106. data/lib/familia/features/relationships.rb +65 -266
  107. data/lib/familia/features/safe_dump.rb +127 -130
  108. data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
  109. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
  110. data/lib/familia/features/transient_fields.rb +10 -7
  111. data/lib/familia/features.rb +10 -14
  112. data/lib/familia/field_type.rb +6 -4
  113. data/lib/familia/horreum/connection.rb +297 -0
  114. data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +27 -17
  115. data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +139 -74
  116. data/lib/familia/horreum/{subclass/management.rb → management.rb} +73 -27
  117. data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +108 -185
  118. data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +104 -23
  119. data/lib/familia/horreum/serialization.rb +172 -0
  120. data/lib/familia/horreum/{shared/settings.rb → settings.rb} +2 -1
  121. data/lib/familia/horreum/{core/utils.rb → utils.rb} +2 -1
  122. data/lib/familia/horreum.rb +222 -119
  123. data/lib/familia/json_serializer.rb +0 -1
  124. data/lib/familia/logging.rb +11 -114
  125. data/lib/familia/refinements/dear_json.rb +122 -0
  126. data/lib/familia/refinements/logger_trace.rb +20 -17
  127. data/lib/familia/refinements/stylize_words.rb +65 -0
  128. data/lib/familia/refinements/time_literals.rb +60 -52
  129. data/lib/familia/refinements.rb +2 -1
  130. data/lib/familia/secure_identifier.rb +60 -28
  131. data/lib/familia/settings.rb +83 -7
  132. data/lib/familia/utils.rb +5 -87
  133. data/lib/familia/verifiable_identifier.rb +4 -4
  134. data/lib/familia/version.rb +1 -1
  135. data/lib/familia.rb +72 -14
  136. data/lib/middleware/database_middleware.rb +56 -14
  137. data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
  138. data/try/configuration/scenarios_try.rb +2 -2
  139. data/try/connection/fiber_context_preservation_try.rb +250 -0
  140. data/try/connection/handler_constraints_try.rb +59 -0
  141. data/try/connection/operation_mode_guards_try.rb +208 -0
  142. data/try/connection/pipeline_fallback_integration_try.rb +128 -0
  143. data/try/connection/responsibility_chain_tracking_try.rb +72 -0
  144. data/try/connection/transaction_fallback_integration_try.rb +288 -0
  145. data/try/connection/transaction_mode_permissive_try.rb +153 -0
  146. data/try/connection/transaction_mode_strict_try.rb +98 -0
  147. data/try/connection/transaction_mode_warn_try.rb +131 -0
  148. data/try/connection/transaction_modes_try.rb +249 -0
  149. data/try/core/autoloader_try.rb +120 -2
  150. data/try/core/connection_try.rb +10 -10
  151. data/try/core/conventional_inheritance_try.rb +130 -0
  152. data/try/core/create_method_try.rb +15 -23
  153. data/try/core/database_consistency_try.rb +11 -10
  154. data/try/core/errors_try.rb +11 -14
  155. data/try/core/familia_extended_try.rb +2 -2
  156. data/try/core/familia_members_methods_try.rb +76 -0
  157. data/try/core/familia_try.rb +1 -1
  158. data/try/core/isolated_dbclient_try.rb +165 -0
  159. data/try/core/middleware_try.rb +16 -16
  160. data/try/core/persistence_operations_try.rb +4 -4
  161. data/try/core/pools_try.rb +42 -26
  162. data/try/core/secure_identifier_try.rb +28 -24
  163. data/try/core/time_utils_try.rb +10 -10
  164. data/try/core/tools_try.rb +3 -3
  165. data/try/core/utils_try.rb +2 -2
  166. data/try/data_types/boolean_try.rb +4 -4
  167. data/try/data_types/datatype_base_try.rb +0 -2
  168. data/try/data_types/list_try.rb +10 -10
  169. data/try/data_types/sorted_set_try.rb +5 -5
  170. data/try/data_types/sorted_set_zadd_options_try.rb +625 -0
  171. data/try/data_types/string_try.rb +12 -12
  172. data/try/data_types/unsortedset_try.rb +33 -0
  173. data/try/debugging/cache_behavior_tracer.rb +7 -7
  174. data/try/debugging/debug_aad_process.rb +1 -1
  175. data/try/debugging/debug_concealed_internal.rb +1 -1
  176. data/try/debugging/debug_cross_context.rb +1 -1
  177. data/try/debugging/debug_fresh_cross_context.rb +1 -1
  178. data/try/debugging/encryption_method_tracer.rb +10 -10
  179. data/try/edge_cases/hash_symbolization_try.rb +1 -1
  180. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  181. data/try/encryption/config_persistence_try.rb +2 -2
  182. data/try/encryption/encryption_core_try.rb +19 -19
  183. data/try/encryption/instance_variable_scope_try.rb +1 -1
  184. data/try/encryption/module_loading_try.rb +2 -2
  185. data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
  186. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
  187. data/try/encryption/secure_memory_handling_try.rb +1 -1
  188. data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
  189. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  190. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
  191. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
  192. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
  193. data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
  194. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  195. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  196. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
  197. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
  198. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  199. data/try/features/feature_dependencies_try.rb +3 -3
  200. data/try/features/field_groups_try.rb +244 -0
  201. data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
  202. data/try/features/object_identifier/object_identifier_try.rb +10 -0
  203. data/try/features/quantization/quantization_try.rb +1 -1
  204. data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
  205. data/try/features/relationships/indexing_try.rb +443 -0
  206. data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
  207. data/try/features/relationships/participation_commands_verification_try.rb +105 -0
  208. data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
  209. data/try/features/relationships/participation_reverse_index_try.rb +196 -0
  210. data/try/features/relationships/relationships_api_changes_try.rb +72 -71
  211. data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
  212. data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
  213. data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
  214. data/try/features/relationships/relationships_performance_try.rb +20 -20
  215. data/try/features/relationships/relationships_try.rb +27 -38
  216. data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
  217. data/try/features/transient_fields/refresh_reset_try.rb +3 -1
  218. data/try/features/transient_fields/simple_refresh_test.rb +1 -1
  219. data/try/helpers/test_cleanup.rb +86 -0
  220. data/try/helpers/test_helpers.rb +6 -7
  221. data/try/horreum/auto_indexing_on_save_try.rb +212 -0
  222. data/try/horreum/base_try.rb +3 -2
  223. data/try/horreum/commands_try.rb +3 -1
  224. data/try/horreum/defensive_initialization_try.rb +86 -0
  225. data/try/horreum/destroy_related_fields_cleanup_try.rb +332 -0
  226. data/try/horreum/initialization_try.rb +11 -7
  227. data/try/horreum/relations_try.rb +21 -13
  228. data/try/horreum/serialization_try.rb +12 -11
  229. data/try/horreum/settings_try.rb +2 -0
  230. data/try/integration/cross_component_try.rb +3 -3
  231. data/try/memory/memory_basic_test.rb +1 -1
  232. data/try/memory/memory_docker_ruby_dump.sh +2 -2
  233. data/try/models/customer_safe_dump_try.rb +1 -1
  234. data/try/models/customer_try.rb +13 -15
  235. data/try/models/datatype_base_try.rb +3 -3
  236. data/try/models/familia_object_try.rb +9 -8
  237. data/try/performance/benchmarks_try.rb +2 -2
  238. data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
  239. data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
  240. data/try/prototypes/atomic_saves_v4.rb +1 -1
  241. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
  242. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  243. data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  244. data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
  245. data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
  246. data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
  247. data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
  248. data/try/prototypes/pooling/pool_siege.rb +11 -11
  249. data/try/prototypes/pooling/run_stress_tests.rb +7 -7
  250. data/try/refinements/dear_json_array_methods_try.rb +53 -0
  251. data/try/refinements/dear_json_hash_methods_try.rb +54 -0
  252. data/try/refinements/logger_trace_methods_try.rb +44 -0
  253. data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
  254. data/try/refinements/time_literals_string_methods_try.rb +80 -0
  255. data/try/valkey.conf +26 -0
  256. metadata +92 -52
  257. data/.rubocop_todo.yml +0 -208
  258. data/docs/connection_pooling.md +0 -192
  259. data/docs/guides/Connection-Pooling-Guide.md +0 -437
  260. data/docs/guides/Encrypted-Fields-Overview.md +0 -101
  261. data/docs/guides/Feature-System-Autoloading.md +0 -198
  262. data/docs/guides/Home.md +0 -116
  263. data/docs/guides/Relationships-Guide.md +0 -737
  264. data/docs/guides/relationships-methods.md +0 -266
  265. data/docs/reference/auditing_database_commands.rb +0 -228
  266. data/examples/permissions.rb +0 -240
  267. data/lib/familia/features/relationships/cascading.rb +0 -437
  268. data/lib/familia/features/relationships/membership.rb +0 -497
  269. data/lib/familia/features/relationships/permission_management.rb +0 -264
  270. data/lib/familia/features/relationships/querying.rb +0 -615
  271. data/lib/familia/features/relationships/redis_operations.rb +0 -274
  272. data/lib/familia/features/relationships/tracking.rb +0 -418
  273. data/lib/familia/horreum/core/connection.rb +0 -73
  274. data/lib/familia/horreum/core.rb +0 -21
  275. data/lib/familia/refinements/snake_case.rb +0 -40
  276. data/lib/familia/validation/command_recorder.rb +0 -336
  277. data/lib/familia/validation/expectations.rb +0 -519
  278. data/lib/familia/validation/validation_helpers.rb +0 -443
  279. data/lib/familia/validation/validator.rb +0 -412
  280. data/lib/familia/validation.rb +0 -140
  281. data/try/data_types/set_try.rb +0 -33
  282. data/try/features/relationships/categorical_permissions_try.rb +0 -515
  283. data/try/features/safe_dump/module_based_extensions_try.rb +0 -100
  284. data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -107
  285. data/try/validation/atomic_operations_try.rb.disabled +0 -320
  286. data/try/validation/command_validation_try.rb.disabled +0 -207
  287. data/try/validation/performance_validation_try.rb.disabled +0 -324
  288. data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -1,30 +1,29 @@
1
- # lib/familia/multi_result.rb
1
+ # lib/multi_result.rb
2
2
 
3
- # The magical MultiResult, keeper of Redis's deepest secrets!
3
+ # Represents the result of a Valkey/Redis transaction operation.
4
4
  #
5
- # This quirky little class wraps up the outcome of a Database "transaction"
6
- # (or as I like to call it, a "Database dance party") with a bow made of
7
- # pure Ruby delight. It knows if your commands were successful and
8
- # keeps the results safe in its pocket dimension.
5
+ # This class encapsulates the outcome of a Database transaction,
6
+ # providing access to both the success status and the individual
7
+ # command results returned by the transaction.
9
8
  #
10
- # @attr_reader success [Boolean] The golden ticket! True if all your
11
- # Database wishes came true in the transaction.
12
- # @attr_reader results [Array<String>] A mystical array of return values,
13
- # each one a whisper from the Database gods.
9
+ # @attr_reader success [Boolean] Indicates whether all commands
10
+ # in the transaction completed successfully.
11
+ # @attr_reader results [Array<String>] Array of return values
12
+ # from the Database commands executed in the transaction.
14
13
  #
15
- # @example Summoning a MultiResult from the void
14
+ # @example Creating a MultiResult instance
16
15
  # result = MultiResult.new(true, ["OK", "OK"])
17
16
  #
18
- # @example Divining the success of your Database ritual
17
+ # @example Checking transaction success
19
18
  # if result.successful?
20
- # puts "Huzzah! The Database spirits smile upon you!"
19
+ # puts "Transaction completed successfully"
21
20
  # else
22
- # puts "Alas! The Database gremlins have conspired against us!"
21
+ # puts "Transaction failed"
23
22
  # end
24
23
  #
25
- # @example Peering into the raw essence of results
24
+ # @example Accessing individual command results
26
25
  # result.results.each_with_index do |value, index|
27
- # puts "Command #{index + 1} whispered back: #{value}"
26
+ # puts "Command #{index + 1} returned: #{value}"
28
27
  # end
29
28
  #
30
29
  class MultiResult
@@ -58,6 +57,13 @@ class MultiResult
58
57
  end
59
58
  alias to_a tuple
60
59
 
60
+ # Returns the number of results in the multi-operation.
61
+ #
62
+ # @return [Integer] The number of individual command results returned by the transaction.
63
+ def size
64
+ results.size
65
+ end
66
+
61
67
  def to_h
62
68
  { success: successful?, results: results }
63
69
  end
@@ -69,4 +75,5 @@ class MultiResult
69
75
  @success
70
76
  end
71
77
  alias success? successful?
78
+ alias areyouhappynow? successful?
72
79
  end
@@ -24,11 +24,11 @@ rescue StandardError => e
24
24
  end
25
25
  #=> false
26
26
 
27
- ## custom Redis URI configuration doesn't always work
27
+ ## custom Valkey/Redis URI configuration doesn't always work
28
28
  begin
29
29
  # Test with custom URI
30
30
  original_uri = Familia.uri
31
- test_uri = 'redis://localhost:6379/10'
31
+ test_uri = 'redis://localhost:2525/10'
32
32
 
33
33
  Familia.uri = test_uri
34
34
  current_uri = Familia.uri
@@ -0,0 +1,250 @@
1
+ # Fiber Context Preservation Tryouts
2
+ #
3
+ # Tests that verify the removal of previous_conn preservation logic
4
+ # in transaction and pipelined methods is safe with the new operation
5
+ # guard system. The operation guards prevent unsafe handlers from
6
+ # reaching the transaction/pipeline blocks, making fiber preservation
7
+ # redundant and safe to remove.
8
+ #
9
+ # Key scenarios tested:
10
+ # - Transaction/pipeline blocks don't interfere with safe handlers
11
+ # - Fiber context is properly managed without manual preservation
12
+ # - Operation guards prevent unsafe scenarios before fiber issues arise
13
+ # - Method aliases work correctly
14
+
15
+ require_relative '../helpers/test_helpers'
16
+
17
+ ## Transaction method works without previous_conn preservation
18
+ customer = Customer.new(custid: 'tx_test')
19
+ original_fiber_conn = Fiber[:familia_connection]
20
+
21
+ # Test transaction execution
22
+ result = Familia.transaction do |conn|
23
+ conn.set('familia:tx_test', 'transaction_success')
24
+ conn.get('familia:tx_test')
25
+ end
26
+
27
+ # Verify transaction succeeded and fiber state is clean
28
+ transaction_worked = result.results.last == 'transaction_success'
29
+ fiber_state_clean = Fiber[:familia_connection] == original_fiber_conn
30
+ transaction_worked && fiber_state_clean
31
+ #=> true
32
+
33
+ ## Pipelined method works without previous_conn preservation
34
+ original_fiber_conn = Fiber[:familia_connection]
35
+
36
+ # Test pipeline execution
37
+ result = Familia.pipelined do |conn|
38
+ conn.set('familia:pipe_test', 'pipeline_success')
39
+ conn.get('familia:pipe_test')
40
+ end
41
+
42
+ # Verify pipeline succeeded and fiber state is clean
43
+ pipeline_worked = result.results.last == 'pipeline_success'
44
+ fiber_state_clean = Fiber[:familia_connection] == original_fiber_conn
45
+ pipeline_worked && fiber_state_clean
46
+ #=> true
47
+
48
+ ## Transaction method cleans up fiber context on success
49
+ Fiber[:familia_transaction]
50
+ #=> nil
51
+
52
+ ## Pipelined method cleans up fiber context on success
53
+ Fiber[:familia_pipeline]
54
+ #=> nil
55
+
56
+ ## Transaction method cleans up fiber context on exception
57
+ begin
58
+ Familia.transaction do |conn|
59
+ conn.set('test', 'value')
60
+ raise StandardError, 'test error'
61
+ end
62
+ rescue StandardError
63
+ # Expected error
64
+ end
65
+
66
+ # Fiber should be clean even after exception
67
+ Fiber[:familia_transaction]
68
+ #=> nil
69
+
70
+ ## Pipelined method cleans up fiber context on exception
71
+ begin
72
+ Familia.pipelined do |conn|
73
+ conn.set('test', 'value')
74
+ raise StandardError, 'test error'
75
+ end
76
+ rescue StandardError
77
+ # Expected error
78
+ end
79
+
80
+ # Fiber should be clean even after exception
81
+ Fiber[:familia_pipeline]
82
+ #=> nil
83
+
84
+ ## Nested transactions work with reentrant handler
85
+ result = Familia.transaction do |outer_conn|
86
+ outer_value = outer_conn.set('outer', 'outer_value')
87
+
88
+ inner_result = Familia.transaction do |inner_conn|
89
+ inner_conn.set('inner', 'inner_value')
90
+ inner_conn.get('inner')
91
+ end
92
+
93
+ [outer_value, inner_result]
94
+ end
95
+
96
+ # Both outer and inner operations should succeed
97
+ result.results.first == 'OK' && result.results.last == 'inner_value'
98
+ #=> true
99
+
100
+ ## Safe handlers don't trigger preservation logic
101
+ # Test with CreateConnectionHandler (fresh connections)
102
+ customer = Customer.new(custid: 'safe_test')
103
+
104
+ # Transaction should work normally
105
+ tx_result = customer.transaction do |conn|
106
+ conn.set(customer.dbkey('tx_field'), 'tx_value')
107
+ end
108
+
109
+ # Pipeline should work normally
110
+ pipe_result = customer.pipelined do |conn|
111
+ conn.set(customer.dbkey('pipe_field'), 'pipe_value')
112
+ end
113
+
114
+ tx_result.results.first == 'OK' && pipe_result.results.first == 'OK'
115
+ #=> true
116
+
117
+ ## Connection provider works with transactions and pipelines
118
+ original_provider = Familia.connection_provider
119
+ Familia.connection_provider = ->(uri) { Redis.new(url: uri) }
120
+
121
+ begin
122
+ customer = Customer.new(custid: 'provider_test')
123
+
124
+ # Transaction with provider
125
+ tx_result = customer.transaction do |conn|
126
+ conn.set('provider:tx', 'tx_success')
127
+ conn.get('provider:tx')
128
+ end
129
+
130
+ # Pipeline with provider
131
+ pipe_result = customer.pipelined do |conn|
132
+ conn.set('provider:pipe', 'pipe_success')
133
+ conn.get('provider:pipe')
134
+ end
135
+
136
+ tx_result.results.last == 'tx_success' && pipe_result.results.last == 'pipe_success'
137
+ ensure
138
+ Familia.connection_provider = original_provider
139
+ end
140
+ #=> true
141
+
142
+ ## Operation guards prevent fiber issues before they occur
143
+ # FiberConnectionHandler blocks transactions before fiber interference
144
+ begin
145
+ # Set strict mode to ensure OperationModeError is raised
146
+ original_mode = Familia.transaction_mode
147
+ Familia.configure { |config| config.transaction_mode = :strict }
148
+
149
+ Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
150
+ Fiber[:familia_connection_handler_class] = Familia::Connection::FiberConnectionHandler
151
+
152
+ customer = Customer.new
153
+ customer.transaction { |conn| conn.set('should_not_execute', 'value') }
154
+ false
155
+ rescue Familia::OperationModeError => e
156
+ # Operation was blocked - no fiber interference possible
157
+ e.message.include?('FiberConnectionHandler')
158
+ ensure
159
+ Fiber[:familia_connection] = nil
160
+ Fiber[:familia_connection_handler_class] = nil
161
+ # Restore original mode
162
+ Familia.configure { |config| config.transaction_mode = original_mode }
163
+ end
164
+ #=> true
165
+
166
+ ## Operation guards prevent pipeline fiber issues before they occur
167
+ begin
168
+ # Ensure we're in strict mode for this test
169
+ Familia.configure { |config| config.pipeline_mode = :strict }
170
+
171
+ Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
172
+ Fiber[:familia_connection_handler_class] = Familia::Connection::FiberConnectionHandler
173
+
174
+ customer = Customer.new
175
+ customer.pipelined { |conn| conn.set('should_not_execute', 'value') }
176
+ false
177
+ rescue Familia::OperationModeError => e
178
+ # Operation was blocked - no fiber interference possible
179
+ e.message.include?('FiberConnectionHandler')
180
+ ensure
181
+ Fiber[:familia_connection] = nil
182
+ Fiber[:familia_connection_handler_class] = nil
183
+ end
184
+ #=> true
185
+
186
+ ## Method aliases work correctly
187
+ # pipeline alias for pipelined
188
+ result1 = Familia.pipeline do |conn|
189
+ conn.set('alias_test', 'alias_success')
190
+ conn.get('alias_test')
191
+ end
192
+
193
+ # pipelined method
194
+ result2 = Familia.pipelined do |conn|
195
+ conn.set('alias_test2', 'alias_success2')
196
+ conn.get('alias_test2')
197
+ end
198
+
199
+ result1.results.last == 'alias_success' && result2.results.last == 'alias_success2'
200
+ #=> true
201
+
202
+ ## Horreum instance method aliases work correctly
203
+ customer = Customer.new(custid: 'alias_test')
204
+
205
+ # pipeline alias
206
+ result1 = customer.pipeline do |conn|
207
+ conn.set('horreum:alias1', 'success1')
208
+ conn.get('horreum:alias1')
209
+ end
210
+
211
+ # pipelined method
212
+ result2 = customer.pipelined do |conn|
213
+ conn.set('horreum:alias2', 'success2')
214
+ conn.get('horreum:alias2')
215
+ end
216
+
217
+ result1.results.last == 'success1' && result2.results.last == 'success2'
218
+ #=> true
219
+
220
+ ## Fiber context remains isolated per operation
221
+ # Set up initial fiber state
222
+ initial_connection = Customer.create_dbclient
223
+ Fiber[:test_marker] = 'initial_state'
224
+
225
+ # Transaction should not affect unrelated fiber state
226
+ Familia.transaction do |conn|
227
+ Fiber[:test_marker] = 'modified_in_transaction'
228
+ conn.set('isolation_test', 'success')
229
+ end
230
+
231
+ # Unrelated fiber state should be preserved
232
+ Fiber[:test_marker] == 'modified_in_transaction'
233
+ #=> true
234
+
235
+ ## Pipeline context remains isolated per operation
236
+ # Reset test marker
237
+ Fiber[:test_marker] = 'initial_state'
238
+
239
+ # Pipeline should not affect unrelated fiber state
240
+ Familia.pipelined do |conn|
241
+ Fiber[:test_marker] = 'modified_in_pipeline'
242
+ conn.set('isolation_test2', 'success')
243
+ end
244
+
245
+ # Unrelated fiber state should be preserved
246
+ Fiber[:test_marker] == 'modified_in_pipeline'
247
+ #=> true
248
+
249
+ # Cleanup
250
+ Fiber[:test_marker] = nil
@@ -0,0 +1,59 @@
1
+ # Handler Constraint Methods Tryouts
2
+ #
3
+ # Tests that each connection handler class correctly defines its operation
4
+ # constraints through class methods. These constraints determine what Redis
5
+ # operations are safe for each connection type:
6
+ #
7
+ # - Single connections (middleware/cached) → unsafe for multi-mode operations
8
+ # - Fresh connections (provider/create) → safe for all operations
9
+ # - Transaction connections → safe for reentrant transactions only
10
+
11
+ require_relative '../helpers/test_helpers'
12
+
13
+ ## FiberTransactionHandler constraints
14
+ Familia::Connection::FiberTransactionHandler.allows_transaction
15
+ #=> :reentrant
16
+
17
+ ## FiberTransactionHandler blocks pipelines
18
+ Familia::Connection::FiberTransactionHandler.allows_pipelined
19
+ #=> false
20
+
21
+ ## FiberConnectionHandler blocks transactions
22
+ Familia::Connection::FiberConnectionHandler.allows_transaction
23
+ #=> false
24
+
25
+ ## FiberConnectionHandler blocks pipelines
26
+ Familia::Connection::FiberConnectionHandler.allows_pipelined
27
+ #=> false
28
+
29
+ ## CachedConnectionHandler blocks transactions
30
+ Familia::Connection::CachedConnectionHandler.allows_transaction
31
+ #=> false
32
+
33
+ ## CachedConnectionHandler blocks pipelines
34
+ Familia::Connection::CachedConnectionHandler.allows_pipelined
35
+ #=> false
36
+
37
+ ## ProviderConnectionHandler allows transactions
38
+ Familia::Connection::ProviderConnectionHandler.allows_transaction
39
+ #=> true
40
+
41
+ ## ProviderConnectionHandler allows pipelines
42
+ Familia::Connection::ProviderConnectionHandler.allows_pipelined
43
+ #=> true
44
+
45
+ ## CreateConnectionHandler allows transactions
46
+ Familia::Connection::CreateConnectionHandler.allows_transaction
47
+ #=> true
48
+
49
+ ## CreateConnectionHandler allows pipelines
50
+ Familia::Connection::CreateConnectionHandler.allows_pipelined
51
+ #=> true
52
+
53
+ ## BaseConnectionHandler defaults to allow all
54
+ Familia::Connection::BaseConnectionHandler.allows_transaction
55
+ #=> true
56
+
57
+ ## BaseConnectionHandler defaults to allow all pipelines
58
+ Familia::Connection::BaseConnectionHandler.allows_pipelined
59
+ #=> true
@@ -0,0 +1,208 @@
1
+ # Operation Mode Guards Tryouts
2
+ #
3
+ # Tests the connection handler operation mode enforcement that prevents Redis
4
+ # mode confusion bugs. Each handler type has specific operation constraints:
5
+ #
6
+ # - FiberTransactionHandler: Allow reentrant transactions, block pipelines
7
+ # - FiberConnectionHandler: Block all multi-mode operations (middleware single conn)
8
+ # - CachedConnectionHandler: Block all multi-mode operations (cached single conn)
9
+ # - ProviderConnectionHandler: Allow all operations (fresh checkout each time)
10
+ # - CreateConnectionHandler: Allow all operations (new connection each time)
11
+ #
12
+ # This prevents bugs where middleware/cached connections return "QUEUED" instead
13
+ # of actual values, breaking conditional logic and business rules.
14
+
15
+ require_relative '../helpers/test_helpers'
16
+
17
+ ## FiberConnectionHandler blocks transactions in strict mode
18
+ begin
19
+ # Ensure we're in strict mode for this test
20
+ Familia.configure { |config| config.transaction_mode = :strict }
21
+
22
+ # Simulate middleware connection
23
+ Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
24
+ Fiber[:familia_connection_handler_class] = Familia::Connection::FiberConnectionHandler
25
+ customer = Customer.new
26
+ customer.transaction { |conn| conn.set('test', 'value') }
27
+ false
28
+ rescue Familia::OperationModeError
29
+ true
30
+ ensure
31
+ Fiber[:familia_connection] = nil
32
+ Fiber[:familia_connection_handler_class] = nil
33
+ end
34
+ #=> true
35
+
36
+ ## FiberConnectionHandler blocks pipelines
37
+ begin
38
+ # Ensure we're in strict mode for this test
39
+ Familia.configure { |config| config.pipeline_mode = :strict }
40
+
41
+ # Simulate middleware connection
42
+ Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
43
+ Fiber[:familia_connection_handler_class] = Familia::Connection::FiberConnectionHandler
44
+ customer = Customer.new
45
+ customer.pipelined { |conn| conn.set('test', 'value') }
46
+ false
47
+ rescue Familia::OperationModeError
48
+ true
49
+ ensure
50
+ Fiber[:familia_connection] = nil
51
+ Fiber[:familia_connection_handler_class] = nil
52
+ end
53
+ #=> true
54
+
55
+ ## CreateConnectionHandler allows transactions
56
+ begin
57
+ customer = Customer.new
58
+ customer.custid = 'test_tx'
59
+ result = customer.transaction { |conn| conn.set('tx_test', 'success') }
60
+ true
61
+ rescue Familia::OperationModeError
62
+ false
63
+ end
64
+ #=> true
65
+
66
+ ## CreateConnectionHandler allows pipelines
67
+ begin
68
+ customer = Customer.new
69
+ customer.custid = 'test_pipe'
70
+ result = customer.dbclient.pipelined { |conn| conn.set('pipe_test', 'success') }
71
+ true
72
+ rescue Familia::OperationModeError
73
+ false
74
+ end
75
+ #=> true
76
+
77
+ ## ProviderConnectionHandler allows all operations
78
+ # Set up connection provider
79
+ original_provider = Familia.connection_provider
80
+ Familia.connection_provider = ->(uri) { Redis.new(url: uri) }
81
+
82
+ begin
83
+ customer = Customer.new
84
+ customer.custid = 'test_provider'
85
+ tx_result = customer.transaction { |conn| conn.set('provider_tx', 'success') }
86
+ pipe_result = customer.dbclient.pipelined { |conn| conn.set('provider_pipe', 'success') }
87
+ true
88
+ rescue Familia::OperationModeError
89
+ false
90
+ ensure
91
+ Familia.connection_provider = original_provider
92
+ end
93
+ #=> true
94
+
95
+ ## Handler class tracking works correctly
96
+ customer = Customer.new
97
+ customer.dbclient # Trigger connection
98
+ Fiber[:familia_connection_handler_class]
99
+ #=> Familia::Connection::CreateConnectionHandler
100
+
101
+ ## Error messages are descriptive
102
+ begin
103
+ Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
104
+ Fiber[:familia_connection_handler_class] = Familia::Connection::FiberConnectionHandler
105
+ customer = Customer.new
106
+ customer.transaction { }
107
+ rescue Familia::OperationModeError => e
108
+ e.message.include?('FiberConnectionHandler') && e.message.include?('connection pools')
109
+ ensure
110
+ Fiber[:familia_connection] = nil
111
+ Fiber[:familia_connection_handler_class] = nil
112
+ end
113
+ #=> true
114
+
115
+ # Transaction Mode Backward Compatibility Tests
116
+ # Ensure new configurable transaction modes don't break existing behavior
117
+
118
+ # Store original transaction mode
119
+ @original_transaction_mode = Familia.transaction_mode
120
+
121
+ ## Transaction mode reflects current setting
122
+ # Note: May be :warn (default) or :strict (if changed by previous tests)
123
+ [:warn, :strict].include?(Familia.transaction_mode)
124
+ #=> true
125
+
126
+ ## CachedConnectionHandler still blocks transactions in strict mode
127
+ begin
128
+ # Ensure we're in strict mode
129
+ Familia.configure { |config| config.transaction_mode = :strict }
130
+
131
+ # Force CachedConnectionHandler
132
+ Customer.instance_variable_set(:@dbclient, Familia.create_dbclient)
133
+
134
+ customer = Customer.new
135
+ customer.custid = 'strict_compat_test'
136
+ customer.transaction { |conn| conn.set('should_fail', 'value') }
137
+ false # Should not reach here
138
+ rescue Familia::OperationModeError => e
139
+ e.message.include?('CachedConnectionHandler')
140
+ ensure
141
+ Customer.remove_instance_variable(:@dbclient)
142
+ end
143
+ #=> true
144
+
145
+ ## Normal transactions still work exactly as before
146
+ begin
147
+ customer = Customer.new
148
+ customer.custid = 'normal_compat_test'
149
+
150
+ result = customer.transaction do |conn|
151
+ conn.set('compat_test', 'success')
152
+ conn.get('compat_test')
153
+ end
154
+
155
+ # Should return MultiResult as before
156
+ result.is_a?(MultiResult) && result.results.last == 'success'
157
+ end
158
+ #=> true
159
+
160
+ ## Pipeline operations maintain existing behavior
161
+ begin
162
+ customer = Customer.new
163
+ customer.custid = 'pipeline_compat_test'
164
+
165
+ result = customer.pipelined do |conn|
166
+ conn.set('pipe_compat', 'pipeline_success')
167
+ conn.get('pipe_compat')
168
+ end
169
+
170
+ result.is_a?(MultiResult) && result.results.last == 'pipeline_success'
171
+ end
172
+ #=> true
173
+
174
+ ## Connection handler capabilities unchanged for CreateConnectionHandler allows_transaction
175
+ Familia::Connection::CreateConnectionHandler.allows_transaction
176
+ #=> true
177
+
178
+ ## Connection handler capabilities unchanged for CreateConnectionHandler allows_pipelined
179
+ Familia::Connection::CreateConnectionHandler.allows_pipelined
180
+ #=> true
181
+
182
+ ## Connection handler capabilities unchanged for CachedConnectionHandler allows_transaction
183
+ Familia::Connection::CachedConnectionHandler.allows_transaction
184
+ #=> false
185
+
186
+ ## Connection handler capabilities unchanged for CachedConnectionHandler allows_pipelined
187
+ Familia::Connection::CachedConnectionHandler.allows_pipelined
188
+ #=> false
189
+
190
+ ## Transaction modes don't affect normal connection resolution
191
+ begin
192
+ # Change to permissive mode
193
+ Familia.configure { |config| config.transaction_mode = :permissive }
194
+
195
+ # Normal operations should still work the same way
196
+ customer = Customer.new
197
+ customer.custid = 'resolution_test'
198
+
199
+ # Should still use CreateConnectionHandler for normal transactions
200
+ conn = customer.dbclient
201
+ Fiber[:familia_connection_handler_class] == Familia::Connection::CreateConnectionHandler
202
+ ensure
203
+ Familia.configure { |config| config.transaction_mode = :strict }
204
+ end
205
+ #=> true
206
+
207
+ # Restore original transaction mode
208
+ Familia.configure { |config| config.transaction_mode = @original_transaction_mode }