mongory 0.6.2 → 0.7.0

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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/CHANGELOG.md +42 -0
  4. data/README.md +82 -176
  5. data/Rakefile +77 -0
  6. data/SUBMODULE_INTEGRATION.md +325 -0
  7. data/docs/advanced_usage.md +40 -0
  8. data/docs/clang_bridge.md +69 -0
  9. data/docs/field_names.md +30 -0
  10. data/docs/migration.md +30 -0
  11. data/docs/performance.md +61 -0
  12. data/examples/benchmark.rb +98 -19
  13. data/ext/mongory_ext/extconf.rb +91 -0
  14. data/ext/mongory_ext/mongory-core/LICENSE +3 -0
  15. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/array.h +105 -0
  16. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/config.h +206 -0
  17. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/error.h +82 -0
  18. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/memory_pool.h +95 -0
  19. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/table.h +108 -0
  20. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/value.h +175 -0
  21. data/ext/mongory_ext/mongory-core/include/mongory-core/matchers/matcher.h +76 -0
  22. data/ext/mongory_ext/mongory-core/include/mongory-core.h +12 -0
  23. data/ext/mongory_ext/mongory-core/src/foundations/array.c +246 -0
  24. data/ext/mongory_ext/mongory-core/src/foundations/array_private.h +18 -0
  25. data/ext/mongory_ext/mongory-core/src/foundations/config.c +406 -0
  26. data/ext/mongory_ext/mongory-core/src/foundations/config_private.h +30 -0
  27. data/ext/mongory_ext/mongory-core/src/foundations/error.c +30 -0
  28. data/ext/mongory_ext/mongory-core/src/foundations/memory_pool.c +298 -0
  29. data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.c +65 -0
  30. data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.h +49 -0
  31. data/ext/mongory_ext/mongory-core/src/foundations/table.c +458 -0
  32. data/ext/mongory_ext/mongory-core/src/foundations/value.c +459 -0
  33. data/ext/mongory_ext/mongory-core/src/matchers/array_record_matcher.c +309 -0
  34. data/ext/mongory_ext/mongory-core/src/matchers/array_record_matcher.h +47 -0
  35. data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.c +161 -0
  36. data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.h +115 -0
  37. data/ext/mongory_ext/mongory-core/src/matchers/compare_matcher.c +210 -0
  38. data/ext/mongory_ext/mongory-core/src/matchers/compare_matcher.h +83 -0
  39. data/ext/mongory_ext/mongory-core/src/matchers/composite_matcher.c +539 -0
  40. data/ext/mongory_ext/mongory-core/src/matchers/composite_matcher.h +125 -0
  41. data/ext/mongory_ext/mongory-core/src/matchers/existance_matcher.c +144 -0
  42. data/ext/mongory_ext/mongory-core/src/matchers/existance_matcher.h +48 -0
  43. data/ext/mongory_ext/mongory-core/src/matchers/external_matcher.c +121 -0
  44. data/ext/mongory_ext/mongory-core/src/matchers/external_matcher.h +46 -0
  45. data/ext/mongory_ext/mongory-core/src/matchers/inclusion_matcher.c +199 -0
  46. data/ext/mongory_ext/mongory-core/src/matchers/inclusion_matcher.h +46 -0
  47. data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.c +334 -0
  48. data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.h +97 -0
  49. data/ext/mongory_ext/mongory-core/src/matchers/matcher.c +196 -0
  50. data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.c +107 -0
  51. data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.h +50 -0
  52. data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.c +55 -0
  53. data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.h +23 -0
  54. data/ext/mongory_ext/mongory_ext.c +635 -0
  55. data/lib/mongory/c_query_builder.rb +44 -0
  56. data/lib/mongory/converters/converted.rb +2 -2
  57. data/lib/mongory/matchers/abstract_matcher.rb +4 -0
  58. data/lib/mongory/matchers/abstract_multi_matcher.rb +44 -0
  59. data/lib/mongory/matchers/and_matcher.rb +2 -23
  60. data/lib/mongory/matchers/array_record_matcher.rb +2 -23
  61. data/lib/mongory/matchers/elem_match_matcher.rb +4 -0
  62. data/lib/mongory/matchers/eq_matcher.rb +4 -0
  63. data/lib/mongory/matchers/every_matcher.rb +4 -0
  64. data/lib/mongory/matchers/exists_matcher.rb +4 -0
  65. data/lib/mongory/matchers/field_matcher.rb +4 -0
  66. data/lib/mongory/matchers/gt_matcher.rb +4 -0
  67. data/lib/mongory/matchers/gte_matcher.rb +4 -0
  68. data/lib/mongory/matchers/hash_condition_matcher.rb +2 -23
  69. data/lib/mongory/matchers/in_matcher.rb +13 -4
  70. data/lib/mongory/matchers/literal_matcher.rb +4 -0
  71. data/lib/mongory/matchers/lt_matcher.rb +4 -0
  72. data/lib/mongory/matchers/lte_matcher.rb +4 -0
  73. data/lib/mongory/matchers/ne_matcher.rb +4 -0
  74. data/lib/mongory/matchers/nin_matcher.rb +14 -5
  75. data/lib/mongory/matchers/not_matcher.rb +4 -0
  76. data/lib/mongory/matchers/or_matcher.rb +2 -23
  77. data/lib/mongory/matchers/present_matcher.rb +4 -0
  78. data/lib/mongory/matchers/regex_matcher.rb +4 -0
  79. data/lib/mongory/matchers/size_matcher.rb +4 -0
  80. data/lib/mongory/query_builder.rb +8 -0
  81. data/lib/mongory/utils/context.rb +7 -0
  82. data/lib/mongory/utils.rb +1 -1
  83. data/lib/mongory/version.rb +1 -1
  84. data/lib/mongory.rb +7 -0
  85. data/mongory.gemspec +10 -4
  86. data/scripts/build_with_core.sh +292 -0
  87. metadata +70 -5
@@ -0,0 +1,539 @@
1
+ /**
2
+ * @file composite_matcher.c
3
+ * @brief Implements composite matchers like AND, OR, $elemMatch, and the
4
+ * core table condition parser. This is an internal implementation file for the
5
+ * matcher module.
6
+ */
7
+ #include "composite_matcher.h"
8
+ #include "external_matcher.h"
9
+ #include "../foundations/config_private.h" // For mongory_matcher_build_func_get
10
+ #include "../foundations/string_buffer.h" // For mongory_string_buffer_new
11
+ #include "base_matcher.h" // For mongory_matcher_always_true_new, etc.
12
+ #include "literal_matcher.h" // For mongory_matcher_field_new
13
+ #include "mongory-core/foundations/error.h" // For MONGORY_ERROR_INVALID_ARGUMENT
14
+ #include "mongory-core/foundations/memory_pool.h"
15
+ #include "mongory-core/foundations/table.h" // For mongory_table operations
16
+ #include "mongory-core/foundations/value.h"
17
+ #include "matcher_explainable.h"
18
+ #include "matcher_traversable.h"
19
+ #include <mongory-core.h> // General include
20
+ #include <stdio.h> // For sprintf
21
+
22
+ /**
23
+ * @brief Allocates and initializes a `mongory_composite_matcher` structure.
24
+ *
25
+ * Initializes the base matcher part and sets child pointers (`left`, `right`)
26
+ * to NULL. The specific `match` function and child matchers must be set by the
27
+ * derived composite matcher's constructor.
28
+ *
29
+ * @param pool The memory pool for allocation.
30
+ * @param condition The condition value for this composite matcher.
31
+ * @return mongory_composite_matcher* Pointer to the new matcher, or NULL on
32
+ * failure.
33
+ */
34
+ // ============================================================================
35
+ // Core Composite Matcher Functions
36
+ // ============================================================================
37
+ mongory_composite_matcher *mongory_matcher_composite_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx) {
38
+ if (!pool || !pool->alloc)
39
+ return NULL;
40
+
41
+ mongory_composite_matcher *composite = MG_ALLOC_PTR(pool, mongory_composite_matcher);
42
+ if (composite == NULL) {
43
+ pool->error = &MONGORY_ALLOC_ERROR;
44
+ return NULL; // Allocation failed.
45
+ }
46
+ // Initialize base matcher fields
47
+ composite->base.pool = pool;
48
+ composite->base.name = NULL; // Specific name to be set by derived type if any
49
+ composite->base.match = NULL; // Specific match fn to be set by derived type
50
+ composite->base.explain = mongory_matcher_composite_explain; // Specific explain fn to be set by derived type
51
+ composite->base.original_match = NULL;
52
+ composite->base.sub_count = 0;
53
+ composite->base.condition = condition;
54
+ composite->base.traverse = mongory_matcher_composite_traverse;
55
+ composite->base.extern_ctx = extern_ctx;
56
+ return composite;
57
+ }
58
+
59
+ /**
60
+ * @brief Match function for an AND logical operation.
61
+ *
62
+ * Evaluates to true if both `left` and `right` child matchers (if they exist)
63
+ * evaluate to true. If a child does not exist, it's considered true for this
64
+ * operation.
65
+ *
66
+ * @param matcher Pointer to the composite AND matcher.
67
+ * @param value The value to evaluate.
68
+ * @return True if all child conditions are met, false otherwise.
69
+ */
70
+ // ============================================================================
71
+ // Logical Operator Match Functions (AND, OR)
72
+ // ============================================================================
73
+ static inline bool mongory_matcher_and_match(mongory_matcher *matcher, mongory_value *value) {
74
+ mongory_composite_matcher *composite = (mongory_composite_matcher *)matcher;
75
+ mongory_array *children = composite->children;
76
+ int total = (int)children->count;
77
+ for (int i = 0; i < total; i++) {
78
+ mongory_matcher *child = (mongory_matcher *)children->get(children, i);
79
+ if (!child->match(child, value)) {
80
+ return false;
81
+ }
82
+ }
83
+ return true; // Both matched (or didn't exist, which is fine for AND).
84
+ }
85
+
86
+ /**
87
+ * @brief Match function for an OR logical operation.
88
+ *
89
+ * Evaluates to true if either the `left` or `right` child matcher (if they
90
+ * exist) evaluates to true. If a child does not exist, it's considered false
91
+ * for this operation.
92
+ *
93
+ * @param matcher Pointer to the composite OR matcher.
94
+ * @param value The value to evaluate.
95
+ * @return True if any child condition is met, false otherwise.
96
+ */
97
+ bool mongory_matcher_or_match(mongory_matcher *matcher, mongory_value *value) {
98
+ mongory_composite_matcher *composite = (mongory_composite_matcher *)matcher;
99
+ mongory_array *children = composite->children;
100
+ int total = (int)children->count;
101
+ for (int i = 0; i < total; i++) {
102
+ mongory_matcher *child = (mongory_matcher *)children->get(children, i);
103
+ if (child->match(child, value)) {
104
+ return true;
105
+ }
106
+ }
107
+ return false; // Neither matched (or children didn't exist).
108
+ }
109
+
110
+ /**
111
+ * @brief Validates if a condition value is a non-null table.
112
+ * Used by table_cond_new and multi_table_cond_validate.
113
+ * @param condition The mongory_value to validate.
114
+ * @param acc Unused accumulator for the array `each` callback.
115
+ * @return True if condition is a valid table, false otherwise.
116
+ */
117
+ static inline bool mongory_matcher_table_cond_validate(mongory_value *condition, void *acc) {
118
+ (void)acc; // Unused parameter.
119
+ return condition != NULL && condition->type == MONGORY_TYPE_TABLE && condition->data.t != NULL;
120
+ }
121
+
122
+ /**
123
+ * @brief Context structure for building sub-matchers from a table.
124
+ */
125
+ typedef struct mongory_matcher_table_build_sub_matcher_context {
126
+ mongory_memory_pool *pool; /**< Main pool for allocating created matchers. */
127
+ mongory_array *matchers; /**< Array to store the created sub-matchers. */
128
+ void *extern_ctx; /**< External context for the matcher. */
129
+ } mongory_matcher_table_build_sub_matcher_context;
130
+
131
+ /**
132
+ * @brief Callback for iterating over a condition table's key-value pairs.
133
+ *
134
+ * For each pair, it creates an appropriate sub-matcher:
135
+ * - If key starts with '$', it looks up a registered matcher builder.
136
+ * - Otherwise, it creates a field matcher (`mongory_matcher_field_new`).
137
+ * The created sub-matcher is added to the `matchers` array in the context.
138
+ *
139
+ * @param key The key from the condition table.
140
+ * @param value The value associated with the key.
141
+ * @param acc Pointer to `mongory_matcher_table_build_sub_matcher_context`.
142
+ * @return True to continue iteration, false if a sub-matcher creation fails.
143
+ */
144
+ // ============================================================================
145
+ // Matcher Construction from Conditions
146
+ // ============================================================================
147
+ static inline bool mongory_matcher_table_build_sub_matcher(char *key, mongory_value *value, void *acc) {
148
+ mongory_matcher_table_build_sub_matcher_context *ctx = (mongory_matcher_table_build_sub_matcher_context *)acc;
149
+ mongory_memory_pool *pool = ctx->pool;
150
+ mongory_array *matchers_array = ctx->matchers;
151
+ mongory_matcher *sub_matcher = NULL;
152
+ mongory_matcher_build_func build_func = NULL;
153
+
154
+ if (key[0] == '$') { // Operator key (e.g., "$eq", "$in")
155
+ build_func = mongory_matcher_build_func_get(key);
156
+ if (build_func != NULL) {
157
+ sub_matcher = build_func(pool, value, ctx->extern_ctx);
158
+ } else if (mongory_custom_matcher_adapter != NULL && mongory_custom_matcher_adapter->lookup != NULL &&
159
+ mongory_custom_matcher_adapter->lookup(key)) {
160
+ sub_matcher = mongory_matcher_custom_new(pool, key, value, ctx->extern_ctx);
161
+ } else {
162
+ sub_matcher = mongory_matcher_field_new(pool, key, value, ctx->extern_ctx);
163
+ }
164
+ } else {
165
+ sub_matcher = mongory_matcher_field_new(pool, key, value, ctx->extern_ctx);
166
+ }
167
+
168
+ if (sub_matcher == NULL) {
169
+ // Failed to create sub-matcher (e.g., allocation error, invalid condition
170
+ // for sub-matcher)
171
+ return false;
172
+ }
173
+
174
+ matchers_array->push(matchers_array, (mongory_value *)sub_matcher);
175
+ return true;
176
+ }
177
+
178
+ /**
179
+ * @brief Creates a matcher from a table-based condition.
180
+ *
181
+ * Parses the `condition` table, creating sub-matchers for each key-value pair.
182
+ *
183
+ * This is a core function of the query engine. It takes a query document (a
184
+ * table) and builds a tree of matchers that represents the logic of that query.
185
+ *
186
+ * The process is as follows:
187
+ * 1. Iterate through each key-value pair in the `condition` table.
188
+ * 2. For each pair, create a specific sub-matcher (e.g., a `field_matcher` for
189
+ * a field name, or a `$gt` matcher for a `"$gt"` operator).
190
+ * 3. Store all these sub-matchers in a temporary array.
191
+ * 4. Use `mongory_matcher_binary_construct` to combine all the sub-matchers
192
+ * into a single matcher tree using AND logic.
193
+ *
194
+ * @param pool Memory pool for allocations.
195
+ * @param condition A `mongory_value` of type `MONGORY_TYPE_TABLE`.
196
+ * @return A `mongory_matcher` representing the combined logic of the table, or NULL on failure.
197
+ */
198
+ mongory_matcher *mongory_matcher_table_cond_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx) {
199
+ if (!mongory_matcher_table_cond_validate(condition, NULL)) {
200
+ pool->error = MG_ALLOC_PTR(pool, mongory_error);
201
+ if (pool->error) {
202
+ pool->error->type = MONGORY_ERROR_INVALID_ARGUMENT;
203
+ pool->error->message = "Condition target must be a valid table.";
204
+ }
205
+ return NULL;
206
+ }
207
+
208
+ mongory_table *table = condition->data.t;
209
+ if (table->count == 0) {
210
+ // Empty table condition matches everything.
211
+ return mongory_matcher_always_true_new(pool, condition, extern_ctx);
212
+ }
213
+
214
+ mongory_array *sub_matchers = mongory_array_new(pool);
215
+ if (sub_matchers == NULL)
216
+ return NULL; // Failed to create array for sub-matchers.
217
+
218
+ mongory_matcher_table_build_sub_matcher_context build_ctx = {pool, sub_matchers, extern_ctx};
219
+ // Iterate over the condition table, building sub-matchers.
220
+ if (!table->each(table, &build_ctx, mongory_matcher_table_build_sub_matcher)) {
221
+ // Building one of the sub-matchers failed.
222
+ return NULL;
223
+ }
224
+
225
+ if (sub_matchers->count == 1) {
226
+ return (mongory_matcher *)sub_matchers->get(sub_matchers, 0);
227
+ }
228
+
229
+ // Combine sub-matchers using AND logic.
230
+ mongory_composite_matcher *final_matcher = mongory_matcher_composite_new(pool, condition, extern_ctx);
231
+ if (final_matcher == NULL)
232
+ return NULL;
233
+ final_matcher->children = sub_matchers;
234
+ final_matcher->base.match = mongory_matcher_and_match;
235
+ final_matcher->base.original_match = mongory_matcher_and_match;
236
+ final_matcher->base.condition = condition;
237
+ final_matcher->base.sub_count = sub_matchers->count;
238
+ final_matcher->base.name = mongory_string_cpy(pool, "Condition");
239
+ return (mongory_matcher *)final_matcher;
240
+ }
241
+
242
+ /**
243
+ * @brief Validates if a condition is an array of valid tables.
244
+ * Used by $and and $or matcher constructors.
245
+ * @param condition The mongory_value to validate.
246
+ * @return True if valid, false otherwise.
247
+ */
248
+ static inline bool mongory_matcher_multi_table_cond_validate(mongory_value *condition) {
249
+ if (!condition || condition->type != MONGORY_TYPE_ARRAY || !condition->data.a) {
250
+ return false; // Must be a non-null array.
251
+ }
252
+ // Check each element of the array.
253
+ return condition->data.a->each(condition->data.a, NULL, mongory_matcher_table_cond_validate);
254
+ }
255
+
256
+ /**
257
+ * @brief Callback for $and constructor to build sub-matchers from each table
258
+ * in the condition array. This is a bit complex: each element of the $and array
259
+ * is a table, and each key-value in THAT table becomes a sub-matcher. These
260
+ * are then ANDed together.
261
+ * @param condition_table A `mongory_value` (table) from the $and array.
262
+ * @param acc Pointer to `mongory_matcher_table_build_sub_matcher_context`.
263
+ * @return Result of iterating through `condition_table`.
264
+ */
265
+ static inline bool mongory_matcher_build_and_sub_matcher(mongory_value *condition_table, void *acc) {
266
+ // The 'condition_table' is one of the tables in the $and:[{}, {}, ...] array.
267
+ // We need to build all matchers from this table and add them to the list.
268
+ // The list in 'acc' (ctx->matchers) will then be ANDed together.
269
+ if (!condition_table || condition_table->type != MONGORY_TYPE_TABLE || !condition_table->data.t) {
270
+ return false; // Element in $and array is not a table.
271
+ }
272
+ return condition_table->data.t->each(condition_table->data.t, acc, mongory_matcher_table_build_sub_matcher);
273
+ }
274
+
275
+ /**
276
+ * @brief Creates an "AND" ($and) matcher from an array of condition tables.
277
+ * @param pool Memory pool for allocations.
278
+ *
279
+ * The `$and` operator takes an array of query documents. This function builds
280
+ * a single, flat list of all the sub-matchers from all the query documents,
281
+ * and then combines them into one large AND-connected matcher tree.
282
+ *
283
+ * @param pool Memory pool for allocations.
284
+ * @param condition A `mongory_value` array of table conditions.
285
+ * @return A new $and matcher, or NULL on failure.
286
+ */
287
+ mongory_matcher *mongory_matcher_and_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx) {
288
+ if (!mongory_matcher_multi_table_cond_validate(condition)) {
289
+ pool->error = MG_ALLOC_PTR(pool, mongory_error);
290
+ if (pool->error) {
291
+ pool->error->type = MONGORY_ERROR_INVALID_ARGUMENT;
292
+ pool->error->message = "$and condition must be an array of tables.";
293
+ }
294
+ return NULL;
295
+ }
296
+
297
+ mongory_array *array_of_tables = condition->data.a;
298
+ if (array_of_tables->count == 0) {
299
+ return mongory_matcher_always_true_new(pool, condition, extern_ctx); // $and:[] is true
300
+ }
301
+
302
+ mongory_array *sub_matchers = mongory_array_new(pool);
303
+ if (sub_matchers == NULL) {
304
+ return NULL;
305
+ }
306
+
307
+ // Context for building matchers from EACH table within the $and array.
308
+ mongory_matcher_table_build_sub_matcher_context build_ctx = {pool, sub_matchers, extern_ctx};
309
+ // Iterate through the array of tables provided in the $and condition.
310
+ // mongory_matcher_build_and_sub_matcher will then iterate keys of EACH table.
311
+ int total = (int)array_of_tables->count;
312
+ for (int i = 0; i < total; i++) {
313
+ mongory_value *table = array_of_tables->get(array_of_tables, i);
314
+ if (!mongory_matcher_build_and_sub_matcher(table, &build_ctx)) {
315
+ return NULL; // Failure during sub-matcher construction.
316
+ }
317
+ }
318
+
319
+ if (sub_matchers->count == 0) {
320
+ return mongory_matcher_always_true_new(pool, condition, extern_ctx);
321
+ }
322
+
323
+ if (sub_matchers->count == 1) {
324
+ return (mongory_matcher *)sub_matchers->get(sub_matchers, 0);
325
+ }
326
+
327
+ mongory_composite_matcher *final_matcher = mongory_matcher_composite_new(pool, condition, extern_ctx);
328
+ if (final_matcher == NULL)
329
+ return NULL;
330
+ final_matcher->children = sub_matchers;
331
+ final_matcher->base.match = mongory_matcher_and_match;
332
+ final_matcher->base.original_match = mongory_matcher_and_match;
333
+ final_matcher->base.condition = condition;
334
+ final_matcher->base.sub_count = sub_matchers->count;
335
+ final_matcher->base.name = mongory_string_cpy(pool, "And");
336
+ return (mongory_matcher *)final_matcher;
337
+ }
338
+
339
+ /**
340
+ * @brief Callback for $or constructor. Each element in the $or array is a
341
+ * complete table condition, which is turned into a single matcher. These
342
+ * "table condition matchers" are then ORed.
343
+ * @param condition_table A `mongory_value` (table) from the $or array.
344
+ * @param acc Pointer to `mongory_matcher_table_build_sub_matcher_context`.
345
+ * The `matchers` array in context will store the result of
346
+ * `mongory_matcher_table_cond_new`.
347
+ * @return True if successful, false otherwise.
348
+ */
349
+ static inline bool mongory_matcher_build_or_sub_matcher(mongory_value *condition_table, void *acc) {
350
+ mongory_matcher_table_build_sub_matcher_context *ctx = (mongory_matcher_table_build_sub_matcher_context *)acc;
351
+ mongory_memory_pool *pool_for_new_matchers = ctx->pool;
352
+ mongory_array *array_to_store_table_matchers = ctx->matchers;
353
+
354
+ // Each 'condition_table' is a complete query object for one branch of the OR.
355
+ // So, create a full table_cond_new matcher for it.
356
+ mongory_matcher *table_level_matcher = mongory_matcher_table_cond_new(pool_for_new_matchers, condition_table, ctx->extern_ctx);
357
+ if (table_level_matcher == NULL) {
358
+ return false; // Failed to create a matcher for this OR branch.
359
+ }
360
+ array_to_store_table_matchers->push(array_to_store_table_matchers, (mongory_value *)table_level_matcher);
361
+ return true;
362
+ }
363
+
364
+ /**
365
+ * @brief Creates an "OR" ($or) matcher from an array of condition tables.
366
+ * @param pool Memory pool for allocations.
367
+ *
368
+ * The `$or` operator takes an array of query documents. For each document in
369
+ * the array, this function creates a complete sub-matcher (using
370
+ * `table_cond_new`). It then combines these top-level sub-matchers into an
371
+ * OR-connected tree. This is different from `$and`, which flattens the list.
372
+ *
373
+ * @param pool Memory pool for allocations.
374
+ * @param condition A `mongory_value` array of table conditions.
375
+ * @return A new $or matcher, or NULL on failure.
376
+ */
377
+ mongory_matcher *mongory_matcher_or_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx) {
378
+ if (!mongory_matcher_multi_table_cond_validate(condition)) {
379
+ pool->error = MG_ALLOC_PTR(pool, mongory_error);
380
+ if (pool->error) {
381
+ pool->error->type = MONGORY_ERROR_INVALID_ARGUMENT;
382
+ pool->error->message = "$or condition must be an array of tables.";
383
+ }
384
+ return NULL;
385
+ }
386
+ mongory_array *array_of_tables = condition->data.a;
387
+ if (array_of_tables->count == 0) {
388
+ return mongory_matcher_always_false_new(pool, condition, extern_ctx); // $or:[] is false
389
+ }
390
+
391
+ mongory_array *sub_matchers = mongory_array_new(pool);
392
+ if (sub_matchers == NULL) {
393
+ return NULL;
394
+ }
395
+
396
+ mongory_matcher_table_build_sub_matcher_context build_ctx = {pool, sub_matchers, extern_ctx};
397
+ int total = (int)array_of_tables->count;
398
+ for (int i = 0; i < total; i++) {
399
+ mongory_value *table = array_of_tables->get(array_of_tables, i);
400
+ if (!mongory_matcher_build_or_sub_matcher(table, &build_ctx)) {
401
+ return NULL; // Failure building one of the OR branches
402
+ }
403
+ }
404
+
405
+ if (sub_matchers->count == 1) {
406
+ return (mongory_matcher *)sub_matchers->get(sub_matchers, 0);
407
+ }
408
+
409
+ mongory_composite_matcher *final_matcher = mongory_matcher_composite_new(pool, condition, extern_ctx);
410
+ if (final_matcher == NULL)
411
+ return NULL;
412
+ final_matcher->children = sub_matchers;
413
+ final_matcher->base.match = mongory_matcher_or_match;
414
+ final_matcher->base.original_match = mongory_matcher_or_match;
415
+ final_matcher->base.condition = condition;
416
+ final_matcher->base.sub_count = sub_matchers->count;
417
+ final_matcher->base.name = mongory_string_cpy(pool, "Or");
418
+ return (mongory_matcher *)final_matcher;
419
+ }
420
+
421
+ /**
422
+ * @brief Match function for $elemMatch.
423
+ * Checks if any element in the input array `value` matches the condition
424
+ * stored in `composite->children`.
425
+ * @param matcher The $elemMatch composite matcher.
426
+ * @param value_to_check The input value, expected to be an array.
427
+ * @return True if `value_to_check` is an array and at least one of its elements
428
+ * matches.
429
+ */
430
+ // ============================================================================
431
+ // Array-based Match Functions ($elemMatch, $every)
432
+ // ============================================================================
433
+ static inline bool mongory_matcher_elem_match_match(mongory_matcher *matcher, mongory_value *value_to_check) {
434
+ if (!value_to_check || value_to_check->type != MONGORY_TYPE_ARRAY || !value_to_check->data.a) {
435
+ return false; // $elemMatch applies to arrays.
436
+ }
437
+ mongory_array *target_array = value_to_check->data.a;
438
+ if (target_array->count == 0)
439
+ return false; // Empty array cannot have a matching element.
440
+
441
+ int total = (int)target_array->count;
442
+ for (int i = 0; i < total; i++) {
443
+ mongory_value *value = target_array->get(target_array, i);
444
+ if (mongory_matcher_and_match(matcher, value)) {
445
+ return true;
446
+ }
447
+ }
448
+ return false;
449
+ }
450
+
451
+ /**
452
+ * @brief Creates an $elemMatch matcher.
453
+ * The `condition` (a table) is used to create a sub-matcher
454
+ * (`composite->left`) which is then applied to each element of an input array.
455
+ * @param pool Memory pool for allocations.
456
+ * @param condition The table condition for matching array elements.
457
+ * @return A new $elemMatch matcher, or NULL on failure.
458
+ */
459
+ mongory_matcher *mongory_matcher_elem_match_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx) {
460
+ mongory_array *sub_matchers = mongory_array_new(pool);
461
+ if (sub_matchers == NULL)
462
+ return NULL;
463
+ mongory_matcher_table_build_sub_matcher_context build_ctx = {pool, sub_matchers, extern_ctx};
464
+ if (!mongory_matcher_build_and_sub_matcher(condition, &build_ctx))
465
+ return NULL;
466
+
467
+ if (sub_matchers->count == 0)
468
+ return mongory_matcher_always_false_new(pool, condition, extern_ctx);
469
+
470
+ mongory_composite_matcher *composite = mongory_matcher_composite_new(pool, condition, extern_ctx);
471
+ if (composite == NULL)
472
+ return NULL;
473
+
474
+ composite->children = sub_matchers;
475
+ composite->base.match = mongory_matcher_elem_match_match;
476
+ composite->base.original_match = mongory_matcher_elem_match_match;
477
+ composite->base.sub_count = sub_matchers->count;
478
+ composite->base.name = mongory_string_cpy(pool, "ElemMatch");
479
+ return (mongory_matcher *)composite;
480
+ }
481
+
482
+ /**
483
+ * @brief Match function for $every.
484
+ * Checks if all elements in the input array `value` match the condition
485
+ * stored in `composite->children`.
486
+ * @param matcher The $every composite matcher.
487
+ * @param value_to_check The input value, expected to be an array.
488
+ * @return True if `value_to_check` is an array and all its elements match (or
489
+ * if array is empty).
490
+ */
491
+ static inline bool mongory_matcher_every_match(mongory_matcher *matcher, mongory_value *value_to_check) {
492
+ if (!value_to_check || value_to_check->type != MONGORY_TYPE_ARRAY || !value_to_check->data.a) {
493
+ return false;
494
+ }
495
+ mongory_array *target_array = value_to_check->data.a;
496
+ if (target_array->count == 0)
497
+ return false; // Non-empty array must have at least one element.
498
+
499
+ int total = (int)target_array->count;
500
+ for (int i = 0; i < total; i++) {
501
+ mongory_value *value = target_array->get(target_array, i);
502
+ if (!mongory_matcher_and_match(matcher, value)) {
503
+ return false;
504
+ }
505
+ }
506
+ return true;
507
+ }
508
+
509
+ /**
510
+ * @brief Creates an $every matcher.
511
+ * The `condition` (a table) is used to create a sub-matcher
512
+ * (`composite->left`) which is then applied to each element of an input array.
513
+ * @param pool Memory pool for allocations.
514
+ * @param condition The table condition for matching array elements.
515
+ * @return A new $every matcher, or NULL on failure.
516
+ */
517
+ mongory_matcher *mongory_matcher_every_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx) {
518
+ mongory_array *sub_matchers = mongory_array_new(pool);
519
+ if (sub_matchers == NULL)
520
+ return NULL;
521
+ mongory_matcher_table_build_sub_matcher_context build_ctx = {pool, sub_matchers, extern_ctx};
522
+ if (!mongory_matcher_build_and_sub_matcher(condition, &build_ctx))
523
+ return NULL;
524
+
525
+ if (sub_matchers->count == 0)
526
+ return mongory_matcher_always_true_new(pool, condition, extern_ctx);
527
+
528
+ mongory_composite_matcher *composite = mongory_matcher_composite_new(pool, condition, extern_ctx);
529
+ if (composite == NULL)
530
+ return NULL;
531
+
532
+ composite->children = sub_matchers;
533
+ composite->base.match = mongory_matcher_every_match;
534
+ composite->base.original_match = mongory_matcher_every_match;
535
+ composite->base.sub_count = sub_matchers->count;
536
+ composite->base.name = mongory_string_cpy(pool, "Every");
537
+
538
+ return (mongory_matcher *)composite;
539
+ }
@@ -0,0 +1,125 @@
1
+ #ifndef MONGORY_MATCHER_COMPOSITE_H
2
+ #define MONGORY_MATCHER_COMPOSITE_H
3
+
4
+ /**
5
+ * @file composite_matcher.h
6
+ * @brief Defines structures and constructors for composite matchers.
7
+ * This is an internal header for the matcher module.
8
+ *
9
+ * Composite matchers combine other matchers, such as logical AND/OR,
10
+ * or apply a matcher to elements of an array or fields of a table.
11
+ */
12
+
13
+ #include "base_matcher.h"
14
+ #include "mongory-core/foundations/array.h"
15
+ #include "mongory-core/foundations/memory_pool.h"
16
+ #include "mongory-core/foundations/value.h"
17
+ #include "mongory-core/matchers/matcher.h" // For mongory_matcher structure
18
+
19
+ /**
20
+ * @struct mongory_composite_matcher
21
+ * @brief Represents a matcher composed of other matchers.
22
+ *
23
+ * Typically has a `left` and/or `right` child matcher. The interpretation
24
+ * of these children depends on the specific composite matcher type (e.g., for
25
+ * AND/OR, both are used; for $elemMatch, `left` might be the sub-matcher for
26
+ * elements).
27
+ */
28
+ typedef struct mongory_composite_matcher {
29
+ mongory_matcher base; /**< Base matcher structure. */
30
+ mongory_array *children; /**< Children matchers. */
31
+ } mongory_composite_matcher;
32
+
33
+ /** @name Composite Matcher Constructors
34
+ * Functions to create instances of various composite matchers.
35
+ * @{
36
+ */
37
+
38
+ /**
39
+ * @brief Creates an "AND" ($and) matcher.
40
+ * Matches if all conditions specified in an array of condition tables are met.
41
+ * @param pool Memory pool for allocation.
42
+ * @param condition A `mongory_value` of type `MONGORY_TYPE_ARRAY`, where each
43
+ * element is a `MONGORY_TYPE_TABLE` representing a sub-condition.
44
+ * @return A new `$and` matcher, or NULL on failure or if condition is invalid.
45
+ */
46
+ mongory_matcher *mongory_matcher_and_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx);
47
+
48
+ /**
49
+ * @brief Creates an "OR" ($or) matcher.
50
+ * Matches if any condition specified in an array of condition tables is met.
51
+ * @param pool Memory pool for allocation.
52
+ * @param condition A `mongory_value` of type `MONGORY_TYPE_ARRAY`, where each
53
+ * element is a `MONGORY_TYPE_TABLE` representing a sub-condition.
54
+ * @return A new `$or` matcher, or NULL on failure or if condition is invalid.
55
+ */
56
+ mongory_matcher *mongory_matcher_or_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx);
57
+
58
+ /**
59
+ * @brief Creates an "element match" ($elemMatch) matcher.
60
+ * Matches an array field if at least one element in the array matches the given
61
+ * condition table.
62
+ * @param pool Memory pool for allocation.
63
+ * @param condition A `mongory_value` of type `MONGORY_TYPE_TABLE` representing
64
+ * the condition to apply to array elements.
65
+ * @return A new `$elemMatch` matcher, or NULL on failure.
66
+ */
67
+ mongory_matcher *mongory_matcher_elem_match_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx);
68
+
69
+ /**
70
+ * @brief Creates an "every element matches" ($every) matcher.
71
+ * Matches an array field if ALL elements in the array match the given condition
72
+ * table. (Note: This is a common pattern, though MongoDB's $all has more
73
+ * complex behavior. This $every is simpler.)
74
+ * @param pool Memory pool for allocation.
75
+ * @param condition A `mongory_value` of type `MONGORY_TYPE_TABLE` representing
76
+ * the condition to apply to array elements.
77
+ * @return A new `$every` matcher, or NULL on failure.
78
+ */
79
+ mongory_matcher *mongory_matcher_every_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx);
80
+
81
+ /**
82
+ * @brief Creates a "table condition" matcher.
83
+ * This is a core matcher that parses a condition table (similar to a MongoDB
84
+ * query document) and builds a tree of sub-matchers based on its keys and
85
+ * values. Field names imply field matchers, and `$`-prefixed keys imply
86
+ * operator matchers. These are implicitly ANDed together.
87
+ * @param pool Memory pool for allocation.
88
+ * @param condition A `mongory_value` of type `MONGORY_TYPE_TABLE`.
89
+ * @return A new table condition matcher, or NULL on failure/invalid condition.
90
+ */
91
+ mongory_matcher *mongory_matcher_table_cond_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx);
92
+ /** @} */
93
+
94
+ /**
95
+ * @brief Allocates and initializes a base `mongory_composite_matcher`
96
+ * structure.
97
+ *
98
+ * Sets up the base `mongory_matcher` fields within the composite structure.
99
+ * Child matchers (`left`, `right`) and the specific `match` function must be
100
+ * set by the caller.
101
+ *
102
+ * @param pool Memory pool for allocation.
103
+ * @param condition The condition associated with this composite matcher (can be
104
+ * NULL if the condition is implicitly defined by children).
105
+ * @return mongory_composite_matcher* Pointer to the new composite matcher
106
+ * structure, or NULL on failure.
107
+ */
108
+ mongory_composite_matcher *mongory_matcher_composite_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx);
109
+
110
+ /**
111
+ * @brief The actual match logic for an OR operation on a composite matcher.
112
+ *
113
+ * This function is typically assigned to the `match` field of an OR composite
114
+ * matcher. It checks if either the `left` or `right` child matcher matches the
115
+ * given value.
116
+ *
117
+ * @param matcher A pointer to the `mongory_matcher` (which is expected to be a
118
+ * `mongory_composite_matcher` for OR).
119
+ * @param value The `mongory_value` to evaluate.
120
+ * @return bool True if `left->match()` or `right->match()` returns true, false
121
+ * otherwise. Returns false if children are NULL or if their match calls fail.
122
+ */
123
+ bool mongory_matcher_or_match(mongory_matcher *matcher, mongory_value *value);
124
+
125
+ #endif /* MONGORY_MATCHER_COMPOSITE_H */