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
@@ -0,0 +1,625 @@
1
+ # try/features/data_type/sorted_set_zadd_options_try.rb
2
+
3
+ require_relative '../helpers/test_helpers'
4
+
5
+ # Test class for ZADD options testing
6
+ class MetricsTest < Familia::Horreum
7
+ identifier_field :name
8
+
9
+ field :name
10
+ sorted_set :metrics
11
+
12
+ def initialize(name = nil)
13
+ super(name: name || SecureRandom.hex(4))
14
+ end
15
+ end
16
+
17
+ ## Store the zset in convenience var
18
+ @metricstest = MetricsTest.new('test')
19
+ @zset = @metricstest.metrics
20
+ #=:> Familia::SortedSet
21
+
22
+ # ============================================================
23
+ # NX Option Tests
24
+ # ============================================================
25
+
26
+ ## ZADD NX: Add new element
27
+ @zset.clear
28
+ @zset.add('member1', 100, nx: true)
29
+ #=> true
30
+
31
+ ## ZADD NX: Prevent update of existing element
32
+ @zset.add('member1', 200, nx: true)
33
+ #=> false
34
+
35
+ ## ZADD NX: Verify score unchanged
36
+ @zset.score('member1')
37
+ #=> 100.0
38
+
39
+ ## ZADD NX: Allow adding different member
40
+ @zset.add('member2', 150, nx: true)
41
+ #=> true
42
+
43
+ # ============================================================
44
+ # XX Option Tests
45
+ # ============================================================
46
+
47
+ ## ZADD XX: Prevent adding new element
48
+ @zset.clear
49
+ @zset.add('member1', 100, xx: true)
50
+ #=> false
51
+
52
+ ## ZADD XX: Verify element not added
53
+ @zset.member?('member1')
54
+ #=> false
55
+
56
+ ## ZADD XX: Update existing element
57
+ @zset.add('member1', 100)
58
+ @zset.add('member1', 200, xx: true)
59
+ #=> false
60
+
61
+ ## ZADD XX: Verify score updated
62
+ @zset.score('member1')
63
+ #=> 200.0
64
+
65
+ # ============================================================
66
+ # GT Option Tests
67
+ # ============================================================
68
+
69
+ ## ZADD GT: Allow adding new element
70
+ @zset.clear
71
+ @zset.add('member1', 100, gt: true)
72
+ #=> true
73
+
74
+ ## ZADD GT: Update when new score greater
75
+ @zset.add('member1', 200, gt: true)
76
+ #=> false
77
+
78
+ ## ZADD GT: Verify score updated
79
+ @zset.score('member1')
80
+ #=> 200.0
81
+
82
+ ## ZADD GT: Prevent update when new score not greater
83
+ @zset.add('member1', 150, gt: true)
84
+ #=> false
85
+
86
+ ## ZADD GT: Verify score unchanged
87
+ @zset.score('member1')
88
+ #=> 200.0
89
+
90
+ ## ZADD GT: Allow update when new score greater again
91
+ @zset.add('member1', 300, gt: true)
92
+ #=> false
93
+
94
+ ## ZADD GT: Verify score updated again
95
+ @zset.score('member1')
96
+ #=> 300.0
97
+
98
+ # ============================================================
99
+ # LT Option Tests
100
+ # ============================================================
101
+
102
+ ## ZADD LT: Allow adding new element
103
+ @zset.clear
104
+ @zset.add('member1', 100, lt: true)
105
+ #=> true
106
+
107
+ ## ZADD LT: Update when new score lesser
108
+ @zset.add('member1', 50, lt: true)
109
+ #=> false
110
+
111
+ ## ZADD LT: Verify score updated
112
+ @zset.score('member1')
113
+ #=> 50.0
114
+
115
+ ## ZADD LT: Prevent update when new score not lesser
116
+ @zset.add('member1', 75, lt: true)
117
+ #=> false
118
+
119
+ ## ZADD LT: Verify score unchanged
120
+ @zset.score('member1')
121
+ #=> 50.0
122
+
123
+ ## ZADD LT: Allow update when new score lesser again
124
+ @zset.add('member1', 25, lt: true)
125
+ #=> false
126
+
127
+ ## ZADD LT: Verify score updated again
128
+ @zset.score('member1')
129
+ #=> 25.0
130
+
131
+ # ============================================================
132
+ # CH Option Tests
133
+ # ============================================================
134
+
135
+ ## ZADD CH: Return count for new element
136
+ @zset.clear
137
+ @zset.add('member1', 100, ch: true)
138
+ #=> true
139
+
140
+ ## ZADD CH: Return count for update (without CH would be 0)
141
+ @zset.add('member1', 200, ch: true)
142
+ #=> true
143
+
144
+ ## ZADD CH without update: Return 0
145
+ @zset.add('member1', 200, ch: true)
146
+ #=> false
147
+
148
+ ## ZADD CH with GT: Count changes only
149
+ @zset.add('member1', 300, gt: true, ch: true)
150
+ #=> true
151
+
152
+ ## ZADD CH with GT: No change when score not greater
153
+ @zset.add('member1', 250, gt: true, ch: true)
154
+ #=> false
155
+
156
+ ## ZADD CH with LT: Count changes only
157
+ @zset.add('member1', 100, lt: true, ch: true)
158
+ #=> true
159
+
160
+ ## ZADD CH with LT: No change when score not lesser
161
+ @zset.add('member1', 150, lt: true, ch: true)
162
+ #=> false
163
+
164
+ # ============================================================
165
+ # Combined Options Tests
166
+ # ============================================================
167
+
168
+ ## ZADD XX+GT: Update only if exists and score greater
169
+ @zset.clear
170
+ @zset.add('member1', 100)
171
+ @zset.add('member1', 200, xx: true, gt: true)
172
+ #=> false
173
+
174
+ ## ZADD XX+GT: Verify score updated
175
+ @zset.score('member1')
176
+ #=> 200.0
177
+
178
+ ## ZADD XX+GT: Prevent update when score not greater
179
+ @zset.add('member1', 150, xx: true, gt: true)
180
+ #=> false
181
+
182
+ ## ZADD XX+GT: Verify score unchanged
183
+ @zset.score('member1')
184
+ #=> 200.0
185
+
186
+ ## ZADD XX+GT: Prevent adding new element
187
+ @zset.add('member2', 500, xx: true, gt: true)
188
+ #=> false
189
+
190
+ ## ZADD XX+GT: Verify element not added
191
+ @zset.member?('member2')
192
+ #=> false
193
+
194
+ ## ZADD XX+LT: Update only if exists and score lesser
195
+ @zset.clear
196
+ @zset.add('member1', 100)
197
+ @zset.add('member1', 50, xx: true, lt: true)
198
+ #=> false
199
+
200
+ ## ZADD XX+LT: Verify score updated
201
+ @zset.score('member1')
202
+ #=> 50.0
203
+
204
+ ## ZADD XX+LT: Prevent update when score not lesser
205
+ @zset.add('member1', 75, xx: true, lt: true)
206
+ #=> false
207
+
208
+ ## ZADD XX+LT: Verify score unchanged
209
+ @zset.score('member1')
210
+ #=> 50.0
211
+
212
+ ## ZADD XX+CH: Track updates for existing elements
213
+ @zset.clear
214
+ @zset.add('member1', 100)
215
+ @zset.add('member1', 200, xx: true, ch: true)
216
+ #=> true
217
+
218
+ ## ZADD XX+GT+CH: Combined tracking
219
+ @zset.add('member1', 300, xx: true, gt: true, ch: true)
220
+ #=> true
221
+
222
+ ## ZADD XX+GT+CH: No change when conditions not met
223
+ @zset.add('member1', 250, xx: true, gt: true, ch: true)
224
+ #=> false
225
+
226
+ # ============================================================
227
+ # Mutual Exclusivity Validation Tests
228
+ # ============================================================
229
+
230
+ ## ZADD NX+XX: Raise ArgumentError
231
+ @zset.clear
232
+ begin
233
+ @zset.add('member1', 100, nx: true, xx: true)
234
+ false
235
+ rescue ArgumentError => e
236
+ e.message.include?('mutually exclusive')
237
+ end
238
+ #=> true
239
+
240
+ ## ZADD GT+LT: Raise ArgumentError
241
+ begin
242
+ @zset.add('member1', 100, gt: true, lt: true)
243
+ false
244
+ rescue ArgumentError => e
245
+ e.message.include?('mutually exclusive')
246
+ end
247
+ #=> true
248
+
249
+ ## ZADD NX+GT: Raise ArgumentError
250
+ begin
251
+ @zset.add('member1', 100, nx: true, gt: true)
252
+ false
253
+ rescue ArgumentError => e
254
+ e.message.include?('mutually exclusive')
255
+ end
256
+ #=> true
257
+
258
+ ## ZADD NX+LT: Raise ArgumentError
259
+ begin
260
+ @zset.add('member1', 100, nx: true, lt: true)
261
+ false
262
+ rescue ArgumentError => e
263
+ e.message.include?('mutually exclusive')
264
+ end
265
+ #=> true
266
+
267
+ # ============================================================
268
+ # Backward Compatibility Tests
269
+ # ============================================================
270
+
271
+ ## ZADD: No options specified (default behavior)
272
+ @zset.clear
273
+ @zset.add('member1', 100)
274
+ #=> true
275
+
276
+ ## ZADD: Update without options
277
+ @zset.add('member1', 200)
278
+ #=> false
279
+
280
+ ## ZADD: Verify score updated
281
+ @zset.score('member1')
282
+ #=> 200.0
283
+
284
+ ## ZADD: Verify default score (Familia.now)
285
+ @zset.clear
286
+ @zset.add('member2')
287
+ @zset.score('member2') > 0
288
+ #=> true
289
+
290
+ # ============================================================
291
+ # Serialization Integration Tests
292
+ # ============================================================
293
+
294
+ ## ZADD with symbol values and NX
295
+ @zset.clear
296
+ @zset.add(:member1, 100, nx: true)
297
+ #=> true
298
+
299
+ ## ZADD with symbol values: Verify membership
300
+ @zset.member?(:member1)
301
+ #=> true
302
+
303
+ ## ZADD with symbol values: NX prevents update
304
+ @zset.add(:member1, 200, nx: true)
305
+ #=> false
306
+
307
+ ## ZADD with symbol values: XX updates
308
+ @zset.add(:member1, 300, xx: true)
309
+ #=> false
310
+
311
+ ## ZADD with symbol values: Verify score
312
+ @zset.score(:member1)
313
+ #=> 300.0
314
+
315
+ ## ZADD with object values and NX
316
+ @metrics_test3 = MetricsTest.new('object_value_test')
317
+ @zset.add(@metrics_test3, 100, nx: true)
318
+ #=> true
319
+
320
+ ## ZADD with object values: Verify membership
321
+ @zset.member?(@metrics_test3)
322
+ #=> true
323
+
324
+ # ============================================================
325
+ # Edge Cases
326
+ # ============================================================
327
+
328
+ ## ZADD with nil score defaults to Familia.now (NX option)
329
+ @zset.clear
330
+ result = @zset.add('member1', nil, nx: true)
331
+ result == true && @zset.score('member1') > 0
332
+ #=> true
333
+
334
+ ## ZADD with nil score defaults to Familia.now (XX option)
335
+ @zset.add('member2', 100)
336
+ result = @zset.add('member2', nil, xx: true)
337
+ result == false && @zset.score('member2') > 100
338
+ #=> true
339
+
340
+ ## ZADD GT with equal score: No update
341
+ @zset.clear
342
+ @zset.add('member1', 100)
343
+ @zset.add('member1', 100, gt: true)
344
+ #=> false
345
+
346
+ ## ZADD LT with equal score: No update
347
+ @zset.clear
348
+ @zset.add('member1', 100)
349
+ @zset.add('member1', 100, lt: true)
350
+ #=> false
351
+
352
+ ## ZADD NX with CH: Return count correctly
353
+ @zset.clear
354
+ @zset.add('member1', 100, nx: true, ch: true)
355
+ #=> true
356
+
357
+ ## ZADD NX with CH: Return 0 when exists
358
+ @zset.add('member1', 200, nx: true, ch: true)
359
+ #=> false
360
+
361
+ # ============================================================
362
+ # Multiple Elements Tests (Redis 6.2+ ZADD behavior)
363
+ # ============================================================
364
+
365
+ ## Multiple elements: Add multiple elements at once (simulate with array)
366
+ @zset.clear
367
+ result1 = @zset.add('member1', 100)
368
+ result2 = @zset.add('member2', 200)
369
+ [result1, result2]
370
+ #=> [true, true]
371
+
372
+ ## Multiple elements: Verify both elements exist
373
+ [@zset.member?('member1'), @zset.member?('member2')]
374
+ #=> [true, true]
375
+
376
+ ## Multiple elements: Update one existing, add one new (simulate)
377
+ result1 = @zset.add('member1', 150) # update existing
378
+ result2 = @zset.add('member3', 300) # add new
379
+ [result1, result2]
380
+ #=> [false, true]
381
+
382
+ ## Multiple elements with CH: Track all changes (simulate behavior)
383
+ @zset.clear
384
+ @zset.add('member1', 100) # existing element
385
+ result1 = @zset.add('member1', 150, ch: true) # update existing
386
+ result2 = @zset.add('member2', 200, ch: true) # add new
387
+ [result1, result2]
388
+ #=> [true, true]
389
+
390
+ ## Multiple elements with CH: No changes
391
+ result1 = @zset.add('member1', 150, ch: true) # same score
392
+ result2 = @zset.add('member2', 200, ch: true) # same score
393
+ [result1, result2]
394
+ #=> [false, false]
395
+
396
+ ## Multiple elements with NX: Mixed new and existing
397
+ @zset.clear
398
+ @zset.add('member1', 100) # pre-existing
399
+ result1 = @zset.add('member1', 150, nx: true) # existing, should not update
400
+ result2 = @zset.add('member2', 200, nx: true) # new, should add
401
+ [result1, result2]
402
+ #=> [false, true]
403
+
404
+ ## Multiple elements with NX: Verify scores unchanged for existing
405
+ @zset.score('member1')
406
+ #=> 100.0
407
+
408
+ ## Multiple elements with NX: Verify new element added
409
+ @zset.score('member2')
410
+ #=> 200.0
411
+
412
+ ## Multiple elements with XX: Mixed existing and new
413
+ @zset.clear
414
+ @zset.add('member1', 100) # pre-existing
415
+ result1 = @zset.add('member1', 150, xx: true) # existing, should update
416
+ result2 = @zset.add('member2', 200, xx: true) # new, should not add
417
+ [result1, result2]
418
+ #=> [false, false]
419
+
420
+ ## Multiple elements with XX: Verify existing updated
421
+ @zset.score('member1')
422
+ #=> 150.0
423
+
424
+ ## Multiple elements with XX: Verify new element not added
425
+ @zset.member?('member2')
426
+ #=> false
427
+
428
+ ## Multiple elements with GT: Mixed score conditions
429
+ @zset.clear
430
+ @zset.add('member1', 100)
431
+ @zset.add('member2', 200)
432
+ result1 = @zset.add('member1', 150, gt: true) # 150 > 100, should update
433
+ result2 = @zset.add('member2', 150, gt: true) # 150 < 200, should not update
434
+ [result1, result2]
435
+ #=> [false, false]
436
+
437
+ ## Multiple elements with GT: Verify selective updates
438
+ [@zset.score('member1'), @zset.score('member2')]
439
+ #=> [150.0, 200.0]
440
+
441
+ ## Multiple elements with LT: Mixed score conditions
442
+ @zset.clear
443
+ @zset.add('member1', 100)
444
+ @zset.add('member2', 200)
445
+ result1 = @zset.add('member1', 150, lt: true) # 150 > 100, should not update
446
+ result2 = @zset.add('member2', 150, lt: true) # 150 < 200, should update
447
+ [result1, result2]
448
+ #=> [false, false]
449
+
450
+ ## Multiple elements with LT: Verify selective updates
451
+ [@zset.score('member1'), @zset.score('member2')]
452
+ #=> [100.0, 150.0]
453
+
454
+ ## Multiple elements with XX+CH: Track updates only
455
+ @zset.clear
456
+ @zset.add('member1', 100)
457
+ @zset.add('member2', 200)
458
+ result1 = @zset.add('member1', 150, xx: true, ch: true) # existing, updated
459
+ result2 = @zset.add('member3', 300, xx: true, ch: true) # new, not added
460
+ [result1, result2]
461
+ #=> [true, false]
462
+
463
+ ## Multiple elements with XX+GT+CH: Complex conditions
464
+ @zset.clear
465
+ @zset.add('member1', 100)
466
+ @zset.add('member2', 200)
467
+ result1 = @zset.add('member1', 150, xx: true, gt: true, ch: true) # update: 150 > 100
468
+ result2 = @zset.add('member2', 150, xx: true, gt: true, ch: true) # no update: 150 < 200
469
+ [result1, result2]
470
+ #=> [true, false]
471
+
472
+ ## Multiple elements with NX+CH: Only new elements tracked
473
+ @zset.clear
474
+ @zset.add('member1', 100)
475
+ result1 = @zset.add('member1', 150, nx: true, ch: true) # existing, no change
476
+ result2 = @zset.add('member2', 200, nx: true, ch: true) # new, added
477
+ [result1, result2]
478
+ #=> [false, true]
479
+
480
+ # ============================================================
481
+ # Large Batch Operations Simulation
482
+ # ============================================================
483
+
484
+ ## Batch operation: Add multiple elements with different options
485
+ @zset.clear
486
+ batch_results = []
487
+ 5.times do |i|
488
+ member = "member#{i}"
489
+ score = (i + 1) * 100
490
+ result = @zset.add(member, score, ch: true)
491
+ batch_results << result
492
+ end
493
+ batch_results
494
+ #=> [true, true, true, true, true]
495
+
496
+ ## Batch operation: Update all with GT option
497
+ update_results = []
498
+ 5.times do |i|
499
+ member = "member#{i}"
500
+ new_score = (i + 1) * 100 + 50 # Increase all scores
501
+ result = @zset.add(member, new_score, gt: true, ch: true)
502
+ update_results << result
503
+ end
504
+ update_results
505
+ #=> [true, true, true, true, true]
506
+
507
+ ## Batch operation: Try to decrease all with GT (should fail)
508
+ decrease_results = []
509
+ 5.times do |i|
510
+ member = "member#{i}"
511
+ new_score = (i + 1) * 100 + 25 # Lower than current
512
+ result = @zset.add(member, new_score, gt: true, ch: true)
513
+ decrease_results << result
514
+ end
515
+ decrease_results
516
+ #=> [false, false, false, false, false]
517
+
518
+ ## Batch operation: Mixed NX operations on existing set
519
+ mixed_results = []
520
+ # Try to add existing members (should fail with NX)
521
+ 3.times do |i|
522
+ member = "member#{i}"
523
+ result = @zset.add(member, 999, nx: true, ch: true)
524
+ mixed_results << result
525
+ end
526
+ # Try to add new members (should succeed with NX)
527
+ 2.times do |i|
528
+ member = "new_member#{i}"
529
+ result = @zset.add(member, 999, nx: true, ch: true)
530
+ mixed_results << result
531
+ end
532
+ mixed_results
533
+ #=> [false, false, false, true, true]
534
+
535
+ # ============================================================
536
+ # Explicit Return Value Verification Tests
537
+ # ============================================================
538
+
539
+ ## Return value: Add new element (should return true)
540
+ @zset.clear
541
+ @zset.add('member1', 100)
542
+ #=> true
543
+
544
+ ## Return value: Update existing element (should return false)
545
+ @zset.add('member1', 200)
546
+ #=> false
547
+
548
+ ## Return value: Add with NX for new element (should return true)
549
+ @zset.add('member2', 100, nx: true)
550
+ #=> true
551
+
552
+ ## Return value: Add with NX for existing element (should return false)
553
+ @zset.add('member2', 200, nx: true)
554
+ #=> false
555
+
556
+ ## Return value: Add with XX for non-existing element (should return false)
557
+ @zset.clear
558
+ @zset.add('member1', 100, xx: true)
559
+ #=> false
560
+
561
+ ## Return value: Add with XX for existing element update (should return false)
562
+ @zset.add('member1', 100)
563
+ @zset.add('member1', 200, xx: true)
564
+ #=> false
565
+
566
+ ## Return value: Add with CH for new element (should return true)
567
+ @zset.clear
568
+ @zset.add('member1', 100, ch: true)
569
+ #=> true
570
+
571
+ ## Return value: Add with CH for element update (should return true)
572
+ @zset.add('member1', 200, ch: true)
573
+ #=> true
574
+
575
+ ## Return value: Add with CH for no change (should return false)
576
+ @zset.add('member1', 200, ch: true)
577
+ #=> false
578
+
579
+ ## Return value: Add with GT for new element (should return true)
580
+ @zset.clear
581
+ @zset.add('member1', 100, gt: true)
582
+ #=> true
583
+
584
+ ## Return value: Add with GT for score increase (should return false - update)
585
+ @zset.add('member1', 200, gt: true)
586
+ #=> false
587
+
588
+ ## Return value: Add with GT for score decrease (should return false - no change)
589
+ @zset.add('member1', 150, gt: true)
590
+ #=> false
591
+
592
+ ## Return value: Add with LT for new element (should return true)
593
+ @zset.clear
594
+ @zset.add('member1', 100, lt: true)
595
+ #=> true
596
+
597
+ ## Return value: Add with LT for score decrease (should return false - update)
598
+ @zset.add('member1', 50, lt: true)
599
+ #=> false
600
+
601
+ ## Return value: Add with LT for score increase (should return false - no change)
602
+ @zset.add('member1', 75, lt: true)
603
+ #=> false
604
+
605
+ ## Return value: Add with XX+GT+CH for valid update (should return true)
606
+ @zset.clear
607
+ @zset.add('member1', 100)
608
+ @zset.add('member1', 200, xx: true, gt: true, ch: true)
609
+ #=> true
610
+
611
+ ## Return value: Add with XX+GT+CH for invalid update (should return false)
612
+ @zset.add('member1', 150, xx: true, gt: true, ch: true)
613
+ #=> false
614
+
615
+ ## Return value: All return values are Boolean type (not Integer)
616
+ @zset.clear
617
+ results = []
618
+ results << @zset.add('member1', 100) # new
619
+ results << @zset.add('member1', 200) # update
620
+ results << @zset.add('member2', 100, nx: true) # new with NX
621
+ results << @zset.add('member2', 200, nx: true) # existing with NX
622
+ results << @zset.add('member3', 100, xx: true) # non-existing with XX
623
+ results << @zset.add('member1', 300, ch: true) # update with CH
624
+ results.all? { |r| r.is_a?(TrueClass) || r.is_a?(FalseClass) }
625
+ #=> true
@@ -8,24 +8,24 @@ require_relative '../helpers/test_helpers'
8
8
  @a.dbkey
9
9
  #=> 'bone:atoken2:object'
10
10
 
11
- ## Familia::String#value should give default value
11
+ ## Familia::StringKey#value should give default value
12
12
  @a.value.value
13
13
  #=> 'GREAT!'
14
14
 
15
- ## Familia::String#value=
15
+ ## Familia::StringKey#value=
16
16
  @a.value.value = 'DECENT!'
17
17
  #=> 'DECENT!'
18
18
 
19
- ## Familia::String#to_s
19
+ ## Familia::StringKey#to_s
20
20
  @a.value.to_s
21
21
  #=> 'DECENT!'
22
22
 
23
- ## Familia::String#destroy!
23
+ ## Familia::StringKey#destroy!
24
24
  @a.value.delete!
25
25
  #=> true
26
26
 
27
- ## Familia::String.new
28
- @ret = Familia::String.new 'arbitrary:key'
27
+ ## Familia::StringKey.new
28
+ @ret = Familia::StringKey.new 'arbitrary:key'
29
29
  @ret.dbkey
30
30
  #=> 'arbitrary:key'
31
31
 
@@ -37,27 +37,27 @@ require_relative '../helpers/test_helpers'
37
37
  @ret.value
38
38
  #=> '1000'
39
39
 
40
- ## Familia::String#increment
40
+ ## Familia::StringKey#increment
41
41
  @ret.increment
42
42
  #=> 1001
43
43
 
44
- ## Familia::String#incrementby
44
+ ## Familia::StringKey#incrementby
45
45
  @ret.incrementby 99
46
46
  #=> 1100
47
47
 
48
- ## Familia::String#decrement
48
+ ## Familia::StringKey#decrement
49
49
  @ret.decrement
50
50
  #=> 1099
51
51
 
52
- ## Familia::String#decrementby
52
+ ## Familia::StringKey#decrementby
53
53
  @ret.decrementby 49
54
54
  #=> 1050
55
55
 
56
- ## Familia::String#append
56
+ ## Familia::StringKey#append
57
57
  @ret.append 'bytes'
58
58
  #=> 9
59
59
 
60
- ## Familia::String#value after append
60
+ ## Familia::StringKey#value after append
61
61
  @ret.value
62
62
  #=> '1050bytes'
63
63
 
@@ -0,0 +1,33 @@
1
+ # try/data_types/unsortedset_try.rb
2
+
3
+ require_relative '../helpers/test_helpers'
4
+
5
+ @a = Bone.new 'atoken'
6
+
7
+ ## Familia::UnsortedSet#add
8
+ ret = @a.tags.add :a
9
+ ret.class
10
+ #=> Familia::UnsortedSet
11
+
12
+ ## Familia::UnsortedSet#<<
13
+ ret = @a.tags << :a << :b << :c
14
+ ret.class
15
+ #=> Familia::UnsortedSet
16
+
17
+ ## Familia::UnsortedSet#members
18
+ @a.tags.members.sort
19
+ #=> ['a', 'b', 'c']
20
+
21
+ ## Familia::UnsortedSet#member? knows when a value exists
22
+ @a.tags.member? :a
23
+ #=> true
24
+
25
+ ## Familia::UnsortedSet#member? knows when a value doesn't exist
26
+ @a.tags.member? :x
27
+ #=> false
28
+
29
+ ## Familia::UnsortedSet#member? knows when a value doesn't exist
30
+ @a.tags.size
31
+ #=> 3
32
+
33
+ @a.tags.delete!