familia 2.0.0.pre19 → 2.0.0.pre21

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 (372) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +4 -9
  3. data/.github/workflows/code-smells.yml +64 -3
  4. data/.pre-commit-config.yaml +8 -6
  5. data/.reek.yml +10 -9
  6. data/.rubocop.yml +4 -0
  7. data/CHANGELOG.rst +177 -112
  8. data/CLAUDE.md +28 -1
  9. data/Gemfile +1 -1
  10. data/Gemfile.lock +20 -17
  11. data/bin/try +16 -0
  12. data/bin/tryouts +16 -0
  13. data/changelog.d/20251105_flexible_external_identifier_format.rst +66 -0
  14. data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +44 -0
  15. data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +20 -0
  16. data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +91 -0
  17. data/changelog.d/20251107_optimized_redis_exists_checks.rst +94 -0
  18. data/changelog.d/20251108_frozen_string_literal_pragma.rst +44 -0
  19. data/docs/1106-participates_in-bidirectional-solution.md +129 -0
  20. data/docs/guides/encryption.md +486 -0
  21. data/docs/guides/feature-encrypted-fields.md +123 -7
  22. data/docs/guides/feature-expiration.md +161 -117
  23. data/docs/guides/feature-external-identifiers.md +415 -443
  24. data/docs/guides/feature-object-identifiers.md +400 -269
  25. data/docs/guides/feature-quantization.md +120 -6
  26. data/docs/guides/feature-relationships-indexing.md +318 -0
  27. data/docs/guides/feature-relationships-methods.md +146 -604
  28. data/docs/guides/feature-relationships-participation.md +263 -0
  29. data/docs/guides/feature-relationships.md +118 -136
  30. data/docs/guides/feature-system-devs.md +176 -693
  31. data/docs/guides/feature-system.md +119 -6
  32. data/docs/guides/feature-transient-fields.md +81 -0
  33. data/docs/guides/field-system.md +778 -0
  34. data/docs/guides/index.md +32 -15
  35. data/docs/guides/logging.md +187 -0
  36. data/docs/guides/optimized-loading.md +674 -0
  37. data/docs/guides/thread-safety-monitoring.md +61 -0
  38. data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
  39. data/docs/migrating/v2.0.0-pre22.md +241 -0
  40. data/docs/overview.md +7 -9
  41. data/docs/reference/api-technical.md +267 -320
  42. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
  43. data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
  44. data/examples/autoloader/mega_customer.rb +2 -0
  45. data/examples/datatype_standalone.rb +4 -3
  46. data/examples/encrypted_fields.rb +2 -1
  47. data/examples/json_usage_patterns.rb +2 -0
  48. data/examples/relationships.rb +3 -0
  49. data/examples/safe_dump.rb +2 -1
  50. data/examples/sampling_demo.rb +53 -0
  51. data/examples/single_connection_transaction_confusions.rb +2 -1
  52. data/familia.gemspec +2 -1
  53. data/lib/familia/base.rb +2 -0
  54. data/lib/familia/connection/behavior.rb +2 -0
  55. data/lib/familia/connection/handlers.rb +2 -0
  56. data/lib/familia/connection/individual_command_proxy.rb +2 -0
  57. data/lib/familia/connection/middleware.rb +34 -24
  58. data/lib/familia/connection/operation_core.rb +2 -0
  59. data/lib/familia/connection/operations.rb +2 -0
  60. data/lib/familia/connection/pipelined_core.rb +2 -0
  61. data/lib/familia/connection/transaction_core.rb +68 -0
  62. data/lib/familia/connection.rb +18 -3
  63. data/lib/familia/data_type/class_methods.rb +3 -1
  64. data/lib/familia/data_type/connection.rb +2 -0
  65. data/lib/familia/data_type/database_commands.rb +2 -0
  66. data/lib/familia/data_type/serialization.rb +6 -4
  67. data/lib/familia/data_type/settings.rb +2 -0
  68. data/lib/familia/data_type/types/counter.rb +2 -0
  69. data/lib/familia/data_type/types/hashkey.rb +7 -5
  70. data/lib/familia/data_type/types/listkey.rb +2 -0
  71. data/lib/familia/data_type/types/lock.rb +2 -0
  72. data/lib/familia/data_type/types/sorted_set.rb +2 -0
  73. data/lib/familia/data_type/types/stringkey.rb +2 -0
  74. data/lib/familia/data_type/types/unsorted_set.rb +2 -0
  75. data/lib/familia/data_type.rb +2 -0
  76. data/lib/familia/encryption/encrypted_data.rb +4 -2
  77. data/lib/familia/encryption/manager.rb +2 -0
  78. data/lib/familia/encryption/provider.rb +2 -0
  79. data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
  80. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
  81. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
  82. data/lib/familia/encryption/registry.rb +2 -0
  83. data/lib/familia/encryption/request_cache.rb +2 -0
  84. data/lib/familia/encryption.rb +9 -2
  85. data/lib/familia/errors.rb +2 -0
  86. data/lib/familia/features/autoloader.rb +2 -0
  87. data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
  88. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
  89. data/lib/familia/features/encrypted_fields.rb +2 -2
  90. data/lib/familia/features/expiration/extensions.rb +3 -1
  91. data/lib/familia/features/expiration.rb +12 -4
  92. data/lib/familia/features/external_identifier.rb +33 -7
  93. data/lib/familia/features/object_identifier.rb +2 -0
  94. data/lib/familia/features/quantization.rb +3 -1
  95. data/lib/familia/features/relationships/README.md +3 -1
  96. data/lib/familia/features/relationships/collection_operations.rb +2 -0
  97. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +138 -9
  98. data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
  99. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +89 -21
  100. data/lib/familia/features/relationships/indexing.rb +3 -0
  101. data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
  102. data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
  103. data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
  104. data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
  105. data/lib/familia/features/relationships/participation.rb +155 -69
  106. data/lib/familia/features/relationships/participation_membership.rb +69 -0
  107. data/lib/familia/features/relationships/participation_relationship.rb +34 -6
  108. data/lib/familia/features/relationships/score_encoding.rb +2 -0
  109. data/lib/familia/features/relationships.rb +5 -3
  110. data/lib/familia/features/safe_dump.rb +2 -0
  111. data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
  112. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
  113. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
  114. data/lib/familia/features/transient_fields.rb +2 -0
  115. data/lib/familia/features.rb +2 -0
  116. data/lib/familia/field_type.rb +3 -1
  117. data/lib/familia/horreum/connection.rb +17 -1
  118. data/lib/familia/horreum/database_commands.rb +2 -0
  119. data/lib/familia/horreum/definition.rb +16 -6
  120. data/lib/familia/horreum/management.rb +212 -42
  121. data/lib/familia/horreum/persistence.rb +176 -108
  122. data/lib/familia/horreum/related_fields.rb +2 -0
  123. data/lib/familia/horreum/serialization.rb +23 -4
  124. data/lib/familia/horreum/settings.rb +2 -0
  125. data/lib/familia/horreum/utils.rb +2 -0
  126. data/lib/familia/horreum.rb +15 -1
  127. data/lib/familia/identifier_extractor.rb +2 -0
  128. data/lib/familia/instrumentation.rb +156 -0
  129. data/lib/familia/json_serializer.rb +2 -0
  130. data/lib/familia/logging.rb +92 -32
  131. data/lib/familia/refinements/dear_json.rb +2 -0
  132. data/lib/familia/refinements/stylize_words.rb +2 -14
  133. data/lib/familia/refinements/time_literals.rb +2 -0
  134. data/lib/familia/refinements.rb +2 -0
  135. data/lib/familia/secure_identifier.rb +10 -2
  136. data/lib/familia/settings.rb +2 -0
  137. data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
  138. data/lib/familia/thread_safety/monitor.rb +328 -0
  139. data/lib/familia/utils.rb +13 -0
  140. data/lib/familia/verifiable_identifier.rb +3 -1
  141. data/lib/familia/version.rb +3 -1
  142. data/lib/familia.rb +31 -4
  143. data/lib/middleware/database_command_counter.rb +152 -0
  144. data/lib/middleware/database_logger.rb +295 -170
  145. data/lib/multi_result.rb +2 -0
  146. data/try/edge_cases/empty_identifiers_try.rb +2 -0
  147. data/try/edge_cases/hash_symbolization_try.rb +2 -0
  148. data/try/edge_cases/json_serialization_try.rb +2 -0
  149. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
  150. data/try/edge_cases/race_conditions_try.rb +4 -0
  151. data/try/edge_cases/reserved_keywords_try.rb +4 -0
  152. data/try/edge_cases/string_coercion_try.rb +2 -0
  153. data/try/edge_cases/ttl_side_effects_try.rb +4 -0
  154. data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
  155. data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
  156. data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
  157. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
  158. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
  159. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
  160. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
  161. data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
  162. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
  163. data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
  164. data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
  165. data/try/features/encrypted_fields/memory_security_try.rb +4 -0
  166. data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
  167. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
  168. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
  169. data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
  170. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
  171. data/try/features/encryption/config_persistence_try.rb +4 -0
  172. data/try/features/encryption/core_try.rb +4 -0
  173. data/try/features/encryption/instance_variable_scope_try.rb +4 -0
  174. data/try/features/encryption/module_loading_try.rb +4 -0
  175. data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
  176. data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
  177. data/try/features/encryption/roundtrip_validation_try.rb +4 -0
  178. data/try/features/encryption/secure_memory_handling_try.rb +4 -0
  179. data/try/features/expiration/expiration_try.rb +4 -0
  180. data/try/features/external_identifier/external_identifier_try.rb +171 -8
  181. data/try/features/feature_dependencies_try.rb +2 -0
  182. data/try/features/feature_improvements_try.rb +2 -0
  183. data/try/features/field_groups_try.rb +2 -0
  184. data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
  185. data/try/features/object_identifier/object_identifier_try.rb +2 -0
  186. data/try/features/quantization/quantization_try.rb +4 -0
  187. data/try/features/real_feature_integration_try.rb +2 -0
  188. data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
  189. data/try/features/relationships/indexing_rebuild_try.rb +600 -0
  190. data/try/features/relationships/indexing_try.rb +2 -0
  191. data/try/features/relationships/participation_bidirectional_try.rb +242 -0
  192. data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
  193. data/try/features/relationships/participation_commands_verification_try.rb +2 -0
  194. data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
  195. data/try/features/relationships/participation_reverse_index_try.rb +15 -13
  196. data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
  197. data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
  198. data/try/features/relationships/relationships_api_changes_try.rb +2 -0
  199. data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
  200. data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
  201. data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
  202. data/try/features/relationships/relationships_performance_try.rb +4 -0
  203. data/try/features/relationships/relationships_performance_working_try.rb +4 -0
  204. data/try/features/relationships/relationships_try.rb +6 -4
  205. data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
  206. data/try/features/safe_dump/safe_dump_try.rb +4 -0
  207. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  208. data/try/features/transient_fields/refresh_reset_try.rb +3 -0
  209. data/try/features/transient_fields/simple_refresh_test.rb +3 -0
  210. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  211. data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
  212. data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
  213. data/try/integration/connection/fiber_context_preservation_try.rb +4 -0
  214. data/try/integration/connection/handler_constraints_try.rb +4 -0
  215. data/try/integration/connection/isolated_dbclient_try.rb +4 -0
  216. data/try/integration/connection/middleware_reconnect_try.rb +2 -0
  217. data/try/integration/connection/operation_mode_guards_try.rb +4 -0
  218. data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
  219. data/try/integration/connection/pools_try.rb +4 -0
  220. data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
  221. data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
  222. data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
  223. data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
  224. data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
  225. data/try/integration/connection/transaction_modes_try.rb +4 -0
  226. data/try/integration/conventional_inheritance_try.rb +4 -0
  227. data/try/integration/create_method_try.rb +4 -0
  228. data/try/integration/cross_component_try.rb +4 -0
  229. data/try/integration/data_types/datatype_pipelines_try.rb +4 -0
  230. data/try/integration/data_types/datatype_transactions_try.rb +4 -0
  231. data/try/integration/database_consistency_try.rb +4 -0
  232. data/try/integration/familia_extended_try.rb +4 -0
  233. data/try/integration/familia_members_methods_try.rb +4 -0
  234. data/try/integration/models/customer_safe_dump_try.rb +4 -0
  235. data/try/integration/models/customer_try.rb +4 -0
  236. data/try/integration/models/datatype_base_try.rb +4 -0
  237. data/try/integration/models/familia_object_try.rb +4 -0
  238. data/try/integration/persistence_operations_try.rb +4 -0
  239. data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
  240. data/try/integration/save_methods_consistency_try.rb +241 -0
  241. data/try/integration/scenarios_try.rb +4 -0
  242. data/try/integration/secure_identifier_try.rb +4 -0
  243. data/try/integration/transaction_safety_core_try.rb +176 -0
  244. data/try/integration/transaction_safety_workflow_try.rb +291 -0
  245. data/try/integration/verifiable_identifier_try.rb +4 -0
  246. data/try/investigation/pipeline_routing/README.md +228 -0
  247. data/try/performance/benchmarks_try.rb +4 -0
  248. data/try/performance/transaction_safety_benchmark_try.rb +238 -0
  249. data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
  250. data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
  251. data/try/support/debugging/cache_behavior_tracer.rb +4 -0
  252. data/try/support/debugging/debug_aad_process.rb +3 -0
  253. data/try/support/debugging/debug_concealed_internal.rb +3 -0
  254. data/try/support/debugging/debug_concealed_reveal.rb +3 -0
  255. data/try/support/debugging/debug_context_aad.rb +3 -0
  256. data/try/support/debugging/debug_context_simple.rb +3 -0
  257. data/try/support/debugging/debug_cross_context.rb +3 -0
  258. data/try/support/debugging/debug_database_load.rb +3 -0
  259. data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
  260. data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
  261. data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
  262. data/try/support/debugging/debug_field_decrypt.rb +3 -0
  263. data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
  264. data/try/support/debugging/debug_load_path.rb +3 -0
  265. data/try/support/debugging/debug_method_definition.rb +3 -0
  266. data/try/support/debugging/debug_method_resolution.rb +3 -0
  267. data/try/support/debugging/debug_minimal.rb +3 -0
  268. data/try/support/debugging/debug_provider.rb +3 -0
  269. data/try/support/debugging/debug_secure_behavior.rb +3 -0
  270. data/try/support/debugging/debug_string_class.rb +3 -0
  271. data/try/support/debugging/debug_test.rb +3 -0
  272. data/try/support/debugging/debug_test_design.rb +3 -0
  273. data/try/support/debugging/encryption_method_tracer.rb +4 -0
  274. data/try/support/debugging/provider_diagnostics.rb +4 -0
  275. data/try/support/helpers/test_cleanup.rb +4 -0
  276. data/try/support/helpers/test_helpers.rb +5 -0
  277. data/try/support/memory/memory_basic_test.rb +4 -0
  278. data/try/support/memory/memory_detailed_test.rb +4 -0
  279. data/try/support/memory/memory_search_for_string.rb +4 -0
  280. data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
  281. data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
  282. data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
  283. data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
  284. data/try/support/prototypes/atomic_saves_v4.rb +4 -0
  285. data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
  286. data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  287. data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
  288. data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  289. data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
  290. data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
  291. data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
  292. data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
  293. data/try/support/prototypes/pooling/pool_siege.rb +4 -2
  294. data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
  295. data/try/thread_safety/README.md +496 -0
  296. data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
  297. data/try/thread_safety/connection_chain_race_try.rb +148 -0
  298. data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
  299. data/try/thread_safety/feature_registry_race_try.rb +226 -0
  300. data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
  301. data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
  302. data/try/thread_safety/field_registration_race_try.rb +222 -0
  303. data/try/thread_safety/logger_initialization_race_try.rb +170 -0
  304. data/try/thread_safety/middleware_registration_race_try.rb +154 -0
  305. data/try/thread_safety/module_config_race_try.rb +175 -0
  306. data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
  307. data/try/unit/core/autoloader_try.rb +4 -0
  308. data/try/unit/core/base_enhancements_try.rb +4 -0
  309. data/try/unit/core/connection_try.rb +4 -0
  310. data/try/unit/core/errors_try.rb +4 -0
  311. data/try/unit/core/extensions_try.rb +4 -0
  312. data/try/unit/core/familia_logger_try.rb +2 -0
  313. data/try/unit/core/familia_try.rb +4 -0
  314. data/try/unit/core/middleware_sampling_try.rb +335 -0
  315. data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
  316. data/try/unit/core/middleware_thread_safety_try.rb +245 -0
  317. data/try/unit/core/middleware_try.rb +4 -0
  318. data/try/unit/core/settings_try.rb +4 -0
  319. data/try/unit/core/time_utils_try.rb +4 -0
  320. data/try/unit/core/tools_try.rb +4 -0
  321. data/try/unit/core/utils_try.rb +37 -0
  322. data/try/unit/data_types/boolean_try.rb +4 -0
  323. data/try/unit/data_types/counter_try.rb +4 -0
  324. data/try/unit/data_types/datatype_base_try.rb +4 -0
  325. data/try/unit/data_types/hash_try.rb +4 -0
  326. data/try/unit/data_types/list_try.rb +4 -0
  327. data/try/unit/data_types/lock_try.rb +4 -0
  328. data/try/unit/data_types/sorted_set_try.rb +4 -0
  329. data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
  330. data/try/unit/data_types/string_try.rb +4 -0
  331. data/try/unit/data_types/unsortedset_try.rb +4 -0
  332. data/try/unit/familia_resolve_class_try.rb +116 -0
  333. data/try/unit/horreum/auto_indexing_on_save_try.rb +5 -1
  334. data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
  335. data/try/unit/horreum/base_try.rb +4 -0
  336. data/try/unit/horreum/class_methods_try.rb +4 -0
  337. data/try/unit/horreum/commands_try.rb +4 -0
  338. data/try/unit/horreum/defensive_initialization_try.rb +4 -0
  339. data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +4 -0
  340. data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
  341. data/try/unit/horreum/field_categories_try.rb +4 -0
  342. data/try/unit/horreum/field_definition_try.rb +4 -0
  343. data/try/unit/horreum/initialization_try.rb +4 -0
  344. data/try/unit/horreum/json_type_preservation_try.rb +2 -0
  345. data/try/unit/horreum/optimized_loading_try.rb +156 -0
  346. data/try/unit/horreum/relations_try.rb +4 -0
  347. data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
  348. data/try/unit/horreum/serialization_try.rb +4 -0
  349. data/try/unit/horreum/settings_try.rb +4 -0
  350. data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
  351. data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -0
  352. data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
  353. data/try/unit/middleware/database_logger_methods_try.rb +251 -0
  354. data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
  355. data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
  356. data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
  357. data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
  358. data/try/unit/thread_safety_monitor_try.rb +149 -0
  359. metadata +72 -17
  360. data/.github/workflows/code-quality.yml +0 -138
  361. data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
  362. data/changelog.d/20251011_203905_delano_next.rst +0 -30
  363. data/changelog.d/20251011_212633_delano_next.rst +0 -13
  364. data/changelog.d/20251011_221253_delano_next.rst +0 -26
  365. data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
  366. data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
  367. data/docs/archive/FAMILIA_UPDATE.md +0 -226
  368. data/docs/archive/README.md +0 -64
  369. data/docs/archive/api-reference.md +0 -333
  370. data/docs/guides/core-field-system.md +0 -806
  371. data/docs/guides/implementation.md +0 -276
  372. data/docs/guides/security-model.md +0 -183
@@ -1,4 +1,6 @@
1
1
  # lib/familia/horreum/management.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  class Horreum
@@ -104,56 +106,71 @@ module Familia
104
106
  # key.
105
107
  #
106
108
  # @param objkey [String] The full dbkey for the object.
109
+ # @param check_exists [Boolean] Whether to check key existence before HGETALL
110
+ # (default: true). When false, skips EXISTS check for better performance
111
+ # but still returns nil for non-existent keys (detected via empty hash).
107
112
  # @return [Object, nil] An instance of the class if the key exists, nil
108
113
  # otherwise.
109
114
  # @raise [ArgumentError] If the provided key is empty.
110
115
  #
111
- # This method performs a two-step process to safely retrieve and
112
- # instantiate objects:
116
+ # This method can operate in two modes:
113
117
  #
114
- # 1. It first checks if the key exists in the database. This is crucial because:
115
- # - It provides a definitive answer about the object's existence.
116
- # - It prevents ambiguity that could arise from `hgetall` returning an
117
- # empty hash for non-existent keys, which could lead to the creation
118
- # of "empty" objects.
118
+ # **Safe mode (check_exists: true, default):**
119
+ # 1. First checks if the key exists with EXISTS command
120
+ # 2. Returns nil immediately if key doesn't exist
121
+ # 3. If exists, retrieves data with HGETALL and instantiates object
122
+ # - Best for: Single object lookups, defensive code
123
+ # - Commands: 2 per object (EXISTS + HGETALL)
119
124
  #
120
- # 2. If the key exists, it retrieves the object's data and instantiates
121
- # it.
125
+ # **Optimized mode (check_exists: false):**
126
+ # 1. Directly calls HGETALL without EXISTS check
127
+ # 2. Returns nil if HGETALL returns empty hash (key doesn't exist)
128
+ # 3. Otherwise instantiates object with returned data
129
+ # - Best for: Bulk operations, performance-critical paths, when keys likely exist
130
+ # - Commands: 1 per object (HGETALL only)
131
+ # - Reduction: 50% fewer Redis commands
122
132
  #
123
- # This approach ensures that we only attempt to instantiate objects that
124
- # actually exist in Valkey/Redis, improving reliability and simplifying
125
- # debugging.
133
+ # @example Safe mode (default)
134
+ # User.find_by_key("user:123") # 2 commands: EXISTS + HGETALL
126
135
  #
127
- # @example
128
- # User.find_by_key("user:123") # Returns a User instance if it exists,
129
- # nil otherwise
136
+ # @example Optimized mode (skip existence check)
137
+ # User.find_by_key("user:123", check_exists: false) # 1 command: HGETALL
138
+ #
139
+ # @note When check_exists: false, HGETALL on non-existent keys returns {}
140
+ # which we detect and return nil (not an empty object instance).
130
141
  #
131
- def find_by_dbkey(objkey)
142
+ def find_by_dbkey(objkey, check_exists: true)
132
143
  raise ArgumentError, 'Empty key' if objkey.to_s.empty?
133
144
 
134
- # We use a lower-level method here b/c we're working with the
135
- # full key and not just the identifier.
136
- does_exist = dbclient.exists(objkey).positive?
137
-
138
- Familia.ld "[find_by_key] #{self} from key #{objkey} (exists: #{does_exist})"
139
- Familia.trace :FIND_BY_DBKEY_KEY, nil, objkey
140
-
141
- # This is the reason for calling exists first. We want to definitively
142
- # and without any ambiguity know if the object exists in the database. If it
143
- # doesn't, we return nil. If it does, we proceed to load the object.
144
- # Otherwise, hgetall will return an empty hash, which will be passed to
145
- # the constructor, which will then be annoying to debug.
146
- return unless does_exist
145
+ if check_exists
146
+ # Safe mode: Check existence first (original behavior)
147
+ # We use a lower-level method here b/c we're working with the
148
+ # full key and not just the identifier.
149
+ does_exist = dbclient.exists(objkey).positive?
150
+
151
+ Familia.debug "[find_by_key] #{self} from key #{objkey} (exists: #{does_exist})"
152
+ Familia.trace :FIND_BY_DBKEY_KEY, nil, objkey
153
+
154
+ # This is the reason for calling exists first. We want to definitively
155
+ # and without any ambiguity know if the object exists in the database. If it
156
+ # doesn't, we return nil. If it does, we proceed to load the object.
157
+ # Otherwise, hgetall will return an empty hash, which will be passed to
158
+ # the constructor, which will then be annoying to debug.
159
+ return unless does_exist
160
+ else
161
+ # Optimized mode: Skip existence check
162
+ Familia.debug "[find_by_key] #{self} from key #{objkey} (check_exists: false)"
163
+ Familia.trace :FIND_BY_DBKEY_KEY, nil, objkey
164
+ end
147
165
 
148
166
  obj = dbclient.hgetall(objkey) # horreum objects are persisted as database hashes
149
167
  Familia.trace :FIND_BY_DBKEY_INSPECT, nil, "#{objkey}: #{obj.inspect}"
150
168
 
151
- # Create instance and deserialize fields using existing helper method
152
- # This avoids duplicating deserialization logic and keeps field-by-field processing
153
- instance = allocate
154
- instance.send(:initialize_relatives)
155
- instance.send(:initialize_with_keyword_args_deserialize_value, **obj)
156
- instance
169
+ # If we skipped existence check and got empty hash, key doesn't exist
170
+ return nil if !check_exists && obj.empty?
171
+
172
+ # Create instance and deserialize fields using shared helper method
173
+ instantiate_from_hash(obj)
157
174
  end
158
175
  alias find_by_key find_by_dbkey
159
176
 
@@ -161,8 +178,10 @@ module Familia
161
178
  #
162
179
  # @param identifier [String, Integer] The unique identifier for the
163
180
  # object.
164
- # @param suffix [Symbol] The suffix to use in the dbkey (default:
165
- # :object).
181
+ # @param suffix [Symbol, nil] The suffix to use in the dbkey (default:
182
+ # class suffix). Keyword parameter for consistency with check_exists.
183
+ # @param check_exists [Boolean] Whether to check key existence before HGETALL
184
+ # (default: true). See find_by_dbkey for details.
166
185
  # @return [Object, nil] An instance of the class if found, nil otherwise.
167
186
  #
168
187
  # This method constructs the full dbkey using the provided identifier
@@ -173,23 +192,153 @@ module Familia
173
192
  # making it easier to retrieve objects when you only have their
174
193
  # identifier.
175
194
  #
176
- # @example
177
- # User.find_by_id(123) # Equivalent to User.find_by_key("user:123:object")
195
+ # @example Safe mode (default)
196
+ # User.find_by_id(123) # 2 commands: EXISTS + HGETALL
197
+ #
198
+ # @example Optimized mode
199
+ # User.find_by_id(123, check_exists: false) # 1 command: HGETALL
178
200
  #
179
- def find_by_identifier(identifier, suffix = nil)
201
+ # @example Custom suffix
202
+ # Session.find_by_id('abc', suffix: :session)
203
+ #
204
+ def find_by_identifier(identifier, suffix: nil, check_exists: true)
180
205
  suffix ||= self.suffix
181
206
  return nil if identifier.to_s.empty?
182
207
 
183
208
  objkey = dbkey(identifier, suffix)
184
209
 
185
- Familia.ld "[find_by_id] #{self} from key #{objkey})"
210
+ Familia.debug "[find_by_id] #{self} from key #{objkey})"
186
211
  Familia.trace :FIND_BY_ID, nil, objkey if Familia.debug?
187
- find_by_dbkey objkey
212
+ find_by_dbkey objkey, check_exists: check_exists
188
213
  end
189
214
  alias find_by_id find_by_identifier
190
215
  alias find find_by_id
191
216
  alias load find_by_id
192
217
 
218
+ # Loads multiple objects by their identifiers using pipelined HGETALL commands.
219
+ #
220
+ # This method provides significant performance improvements for bulk loading by:
221
+ # 1. Batching all HGETALL commands into a single Redis pipeline
222
+ # 2. Eliminating network round-trip overhead
223
+ # 3. Skipping individual EXISTS checks (like check_exists: false)
224
+ #
225
+ # @param identifiers [Array<String, Integer>] Array of identifiers to load
226
+ # @param suffix [Symbol, nil] The suffix to use in dbkeys (default: class suffix)
227
+ # @return [Array<Object>] Array of instantiated objects (nils for non-existent)
228
+ #
229
+ # Performance characteristics:
230
+ # - Standard approach: N objects × 2 commands (EXISTS + HGETALL) = 2N round trips
231
+ # - check_exists: false: N objects × 1 command (HGETALL) = N round trips
232
+ # - load_multi: 1 pipeline with N commands = 1 round trip
233
+ # - Improvement: Up to 2N× faster for bulk operations
234
+ #
235
+ # @example Load multiple users efficiently
236
+ # users = User.load_multi([123, 456, 789])
237
+ # # 1 pipeline with 3 HGETALL commands instead of 6 individual commands
238
+ #
239
+ # @example Filter out nils
240
+ # existing_users = User.load_multi(ids).compact
241
+ #
242
+ # @note Returns nil for non-existent keys (maintains same contract as find_by_id)
243
+ # @note Objects are returned in the same order as input identifiers
244
+ # @note Empty/nil identifiers are skipped and return nil in result array
245
+ #
246
+ def load_multi(identifiers, suffix = nil)
247
+ suffix ||= self.suffix
248
+ return [] if identifiers.empty?
249
+
250
+ # Build list of valid keys and track their original positions
251
+ valid_keys = []
252
+ valid_positions = []
253
+
254
+ identifiers.each_with_index do |identifier, idx|
255
+ next if identifier.to_s.empty?
256
+
257
+ valid_keys << dbkey(identifier, suffix)
258
+ valid_positions << idx
259
+ end
260
+
261
+ Familia.trace :LOAD_MULTI, nil, "Loading #{identifiers.size} objects" if Familia.debug?
262
+
263
+ # Pipeline all HGETALL commands
264
+ multi_result = pipelined do |pipeline|
265
+ valid_keys.each do |objkey|
266
+ pipeline.hgetall(objkey)
267
+ end
268
+ end
269
+
270
+ # Extract results array from MultiResult
271
+ results = multi_result.results
272
+
273
+ # Map results back to original positions
274
+ objects = Array.new(identifiers.size)
275
+ valid_positions.each_with_index do |pos, result_idx|
276
+ obj_hash = results[result_idx]
277
+
278
+ # Skip empty hashes (non-existent keys)
279
+ next if obj_hash.nil? || obj_hash.empty?
280
+
281
+ # Instantiate object using shared helper method
282
+ objects[pos] = instantiate_from_hash(obj_hash)
283
+ end
284
+
285
+ objects
286
+ end
287
+ alias load_batch load_multi
288
+
289
+ # Loads multiple objects by their full dbkeys using pipelined HGETALL commands.
290
+ #
291
+ # This is a lower-level variant of load_multi that works directly with dbkeys
292
+ # instead of identifiers. Useful when you already have the full keys.
293
+ #
294
+ # @param objkeys [Array<String>] Array of full dbkeys to load
295
+ # @return [Array<Object>] Array of instantiated objects (nils for non-existent)
296
+ #
297
+ # @example Load objects by full keys
298
+ # keys = ["user:123:object", "user:456:object"]
299
+ # users = User.load_multi_by_keys(keys)
300
+ #
301
+ # @note Returns nil for empty/nil keys, maintaining position alignment with input array
302
+ #
303
+ # @see load_multi For loading by identifiers
304
+ #
305
+ def load_multi_by_keys(objkeys)
306
+ return [] if objkeys.empty?
307
+
308
+ Familia.trace :LOAD_MULTI_BY_KEYS, nil, "Loading #{objkeys.size} objects" if Familia.debug?
309
+
310
+ # Track which positions have valid keys to maintain result array alignment
311
+ valid_positions = []
312
+ objkeys.each_with_index do |objkey, idx|
313
+ valid_positions << idx unless objkey.to_s.empty?
314
+ end
315
+
316
+ # Pipeline all HGETALL commands for valid keys
317
+ multi_result = pipelined do |pipeline|
318
+ objkeys.each do |objkey|
319
+ next if objkey.to_s.empty?
320
+ pipeline.hgetall(objkey)
321
+ end
322
+ end
323
+
324
+ # Extract results array from MultiResult
325
+ results = multi_result.results
326
+
327
+ # Map results back to original positions
328
+ objects = Array.new(objkeys.size)
329
+ valid_positions.each_with_index do |pos, result_idx|
330
+ obj_hash = results[result_idx]
331
+
332
+ # Skip empty hashes (non-existent keys)
333
+ next if obj_hash.nil? || obj_hash.empty?
334
+
335
+ # Instantiate object using shared helper method
336
+ objects[pos] = instantiate_from_hash(obj_hash)
337
+ end
338
+
339
+ objects
340
+ end
341
+
193
342
  # Checks if an object with the given identifier exists in the database.
194
343
  #
195
344
  # @param identifier [String, Integer] The unique identifier for the object.
@@ -317,6 +466,27 @@ module Familia
317
466
  end
318
467
  alias size matching_keys_count
319
468
  alias length matching_keys_count
469
+
470
+ # Instantiates an object from a hash of field values.
471
+ #
472
+ # This is an internal helper method used by find_by_dbkey, load_multi, and
473
+ # load_multi_by_keys to eliminate code duplication. Not intended for direct use.
474
+ #
475
+ # @param obj_hash [Hash] Hash of field names to serialized values from Redis
476
+ # @return [Object] Instantiated object with deserialized fields
477
+ #
478
+ # @note This method:
479
+ # 1. Allocates a new instance without calling initialize
480
+ # 2. Initializes related DataType fields
481
+ # 3. Deserializes and assigns field values from the hash
482
+ #
483
+ # @api private
484
+ def instantiate_from_hash(obj_hash)
485
+ instance = allocate
486
+ instance.send(:initialize_relatives)
487
+ instance.send(:initialize_with_keyword_args_deserialize_value, **obj_hash)
488
+ instance
489
+ end
320
490
  end
321
491
  end
322
492
  end