mongory 0.6.3 → 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 (62) 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/query_builder.rb +8 -0
  57. data/lib/mongory/utils/context.rb +7 -0
  58. data/lib/mongory/version.rb +1 -1
  59. data/lib/mongory.rb +7 -0
  60. data/mongory.gemspec +10 -4
  61. data/scripts/build_with_core.sh +292 -0
  62. metadata +69 -4
@@ -0,0 +1,334 @@
1
+ /**
2
+ * @file literal_matcher.c
3
+ * @brief Implements literal matchers, field matchers, $not, and $size.
4
+ * This is an internal implementation file for the matcher module.
5
+ *
6
+ * - "Literal" matching often involves comparing a value directly or using a
7
+ * simple condition (like equality).
8
+ * - Field matchers extract a value from a table/array by field name/index and
9
+ * apply a condition to it.
10
+ * - $not inverts the result of a condition.
11
+ * - $size checks the number of elements in an array against a condition.
12
+ */
13
+ #include "literal_matcher.h"
14
+ #include "../foundations/config_private.h" // For mongory_internal_value_converter, mongory_string_cpy
15
+ #include "../foundations/string_buffer.h" // For mongory_string_buffer_appendf
16
+ #include "array_record_matcher.h" // For handling array-specific matching logic
17
+ #include "base_matcher.h" // For mongory_try_parse_int, mongory_matcher_base_new
18
+ #include "compare_matcher.h" // For mongory_matcher_equal_new
19
+ #include "composite_matcher.h" // For mongory_matcher_composite_new, mongory_matcher_table_cond_new
20
+ #include "existance_matcher.h" // For mongory_matcher_exists_new (used by null_new)
21
+ #include "matcher_explainable.h"
22
+ #include "matcher_traversable.h"
23
+ #include "mongory-core/foundations/array.h" // For mongory_array access
24
+ #include "mongory-core/foundations/table.h" // For mongory_table access
25
+ #include "mongory-core/foundations/value.h" // For mongory_value types and wrappers
26
+ #include "external_matcher.h" // For mongory_matcher_regex_new
27
+ #include <mongory-core.h> // General include
28
+ #include <stdio.h> // For printf
29
+
30
+ /**
31
+ * @brief Core matching logic for literal-based conditions.
32
+ *
33
+ * This function is used by field matchers, $not, and $size.
34
+ * It checks the type of the input `value`.
35
+ * - If `value` is an array, it may delegate to an `array_record_matcher`
36
+ * (via `composite->right`, if initialized). This path seems intended for
37
+ * more complex array matching scenarios where the condition itself might
38
+ * describe how array elements should be matched.
39
+ * - Otherwise (if `value` is not an array, or if array-specific handling is
40
+ * not triggered), it uses the `composite->left` matcher. `composite->left`
41
+ * is typically set up by `mongory_matcher_literal_delegate` to handle the
42
+ * specific type of the literal condition (e.g., equality for simple values,
43
+ * regex matcher for regex conditions, table condition matcher for table
44
+ * conditions).
45
+ *
46
+ * @param matcher The `mongory_matcher` (expected to be a
47
+ * `mongory_composite_matcher` where `left` and possibly `right` are set up).
48
+ * @param value The `mongory_value` to evaluate against the matcher's condition.
49
+ * @return True if the value matches, false otherwise.
50
+ */
51
+ static inline bool mongory_matcher_literal_match(mongory_matcher *matcher, mongory_value *value) {
52
+ mongory_literal_matcher *literal = (mongory_literal_matcher *)matcher;
53
+ if (!literal || !literal->base.pool)
54
+ return false; // Basic safety check
55
+
56
+ if (value != NULL && value->type == MONGORY_TYPE_ARRAY) {
57
+ // If the value being matched is an array, there's special handling.
58
+ // The `right` child of the composite matcher is used for array record matching.
59
+ // It's created on-demand if not already present.
60
+ if (literal->array_record_matcher == NULL) {
61
+ // Lazily create the array_record_matcher if needed.
62
+ // The condition for array_record_new is the original condition of the literal matcher.
63
+ literal->array_record_matcher = mongory_matcher_array_record_new(literal->base.pool, literal->base.condition, literal->base.extern_ctx);
64
+ }
65
+ // If right child exists (or was successfully created), use it.
66
+ return literal->array_record_matcher ? literal->array_record_matcher->match(literal->array_record_matcher, value) : false;
67
+ } else {
68
+ // For non-array values, or if array-specific path wasn't taken.
69
+ // The `left` child handles the general literal condition.
70
+ return literal->delegate_matcher ? literal->delegate_matcher->match(literal->delegate_matcher, value) : false;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * @brief Internal helper to create a specialized matcher for a `MONGORY_TYPE_NULL`
76
+ * condition.
77
+ *
78
+ * This creates an OR condition:
79
+ * (`value` IS MONGORY_TYPE_NULL) OR (`value` DOES NOT EXIST (evaluates to
80
+ * false for $exists:true)). This means it matches actual BSON nulls or missing
81
+ * fields.
82
+ *
83
+ * @param pool Memory pool for allocation.
84
+ * @param condition The `mongory_value` which is of `MONGORY_TYPE_NULL`.
85
+ * @return A composite matcher for the NULL condition, or NULL on failure.
86
+ */
87
+ static inline mongory_matcher *mongory_matcher_null_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx) {
88
+ mongory_composite_matcher *composite = mongory_matcher_composite_new(pool, condition, extern_ctx);
89
+ if (!composite)
90
+ return NULL;
91
+
92
+ mongory_array *sub_matchers = mongory_array_new(pool);
93
+ if (!sub_matchers)
94
+ return NULL;
95
+
96
+ // Left branch: checks for actual MONGORY_TYPE_NULL
97
+ mongory_value *null_val = mongory_value_wrap_n(pool, NULL);
98
+ if (!null_val)
99
+ return NULL; // Failed to wrap null value
100
+ sub_matchers->push(sub_matchers, (mongory_value *)mongory_matcher_equal_new(pool, null_val, extern_ctx));
101
+
102
+ // Right branch: checks if the field does not exist (value is NULL from get)
103
+ // $exists: false
104
+ mongory_value *exists_false_cond = mongory_value_wrap_b(pool, false);
105
+ if (!exists_false_cond)
106
+ return NULL; // Failed to wrap bool
107
+ sub_matchers->push(sub_matchers, (mongory_value *)mongory_matcher_exists_new(pool, exists_false_cond, extern_ctx));
108
+
109
+ composite->children = sub_matchers;
110
+ composite->base.match = mongory_matcher_or_match; // OR logic
111
+ composite->base.original_match = mongory_matcher_or_match;
112
+ composite->base.sub_count = 1;
113
+ composite->base.name = mongory_string_cpy(pool, "Or");
114
+ // composite->base.condition is already set by mongory_matcher_composite_new
115
+ return (mongory_matcher *)composite;
116
+ }
117
+
118
+ /**
119
+ * @brief Delegates creation of a simple literal matcher based on the
120
+ * `condition`'s type.
121
+ *
122
+ * - Table condition: creates a `mongory_matcher_table_cond_new`.
123
+ * - Regex condition: creates a `mongory_matcher_regex_new`.
124
+ * - Null condition: creates a specialized `mongory_matcher_null_new`.
125
+ * - Other types: creates an equality matcher (`mongory_matcher_equal_new`).
126
+ *
127
+ * @param pool Memory pool for allocation.
128
+ * @param condition The literal value or condition table.
129
+ * @return The appropriate simple matcher for the condition, or NULL on failure.
130
+ */
131
+ static inline mongory_matcher *mongory_matcher_literal_delegate(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx) {
132
+ if (!condition)
133
+ return mongory_matcher_equal_new(pool, NULL, extern_ctx); // Or specific null matcher
134
+
135
+ switch (condition->type) {
136
+ case MONGORY_TYPE_TABLE:
137
+ return mongory_matcher_table_cond_new(pool, condition, extern_ctx);
138
+ case MONGORY_TYPE_REGEX:
139
+ return mongory_matcher_regex_new(pool, condition, extern_ctx);
140
+ case MONGORY_TYPE_NULL:
141
+ // When the condition is explicitly `null`, e.g. `{ field: null }`
142
+ return mongory_matcher_null_new(pool, condition, extern_ctx);
143
+ default:
144
+ // For boolean, int, double, string, array (equality), pointer, unsupported.
145
+ return mongory_matcher_equal_new(pool, condition, extern_ctx);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * @brief Match function for a field matcher.
151
+ *
152
+ * Extracts the value of `field_matcher->field` from the input `value`
153
+ * (table or array). Then, applies the literal matching logic
154
+ * (`mongory_matcher_literal_match`) using the sub-matcher stored in
155
+ * `composite.left` (which was set up by `literal_delegate` based on the
156
+ * original condition for this field).
157
+ *
158
+ * @param matcher Pointer to the `mongory_matcher` (a `mongory_field_matcher`).
159
+ * @param value The input table or array to extract the field from.
160
+ * @return True if the field's value matches the condition, false otherwise.
161
+ */
162
+ static inline bool mongory_matcher_field_match(mongory_matcher *matcher, mongory_value *value) {
163
+ if (value == NULL) { // Cannot extract field from NULL.
164
+ return false;
165
+ }
166
+ mongory_field_matcher *field_matcher = (mongory_field_matcher *)matcher;
167
+ if (!field_matcher->field)
168
+ return false; // No field specified.
169
+
170
+ mongory_value *field_value = NULL;
171
+ char *field_key = field_matcher->field;
172
+
173
+ if (value->type == MONGORY_TYPE_TABLE) {
174
+ if (value->data.t) {
175
+ field_value = value->data.t->get(value->data.t, field_key);
176
+ }
177
+ } else if (value->type == MONGORY_TYPE_ARRAY) {
178
+ if (value->data.a) {
179
+ int index;
180
+ if (!mongory_try_parse_int(field_key, &index))
181
+ return false; // Field key is not a valid integer index for an array.
182
+ if (index < 0) { // Handle negative indexing (from end of array)
183
+ if ((size_t)(-index) > value->data.a->count)
184
+ return false; // Out of bounds
185
+ index = value->data.a->count + index;
186
+ }
187
+ if ((size_t)index >= value->data.a->count)
188
+ return false; // Out of bounds
189
+ field_value = value->data.a->get(value->data.a, (size_t)index);
190
+ }
191
+ } else {
192
+ return false; // Can only extract fields from tables or arrays.
193
+ }
194
+
195
+ // If the extracted field value is a pointer type that needs conversion
196
+ // (e.g., from a language binding), convert it.
197
+ if (field_value && field_value->type == MONGORY_TYPE_POINTER && mongory_internal_value_converter &&
198
+ mongory_internal_value_converter->shallow_convert) {
199
+ // The pool for the converted value should ideally be the field_value's pool
200
+ // or the matcher's pool.
201
+ mongory_memory_pool *conversion_pool = field_value->pool ? field_value->pool : matcher->pool;
202
+ field_value = mongory_internal_value_converter->shallow_convert(conversion_pool, field_value->data.ptr);
203
+ }
204
+ // Now, use the literal_match logic (which uses composite.left primarily)
205
+ // to match the extracted field_value.
206
+ return mongory_matcher_literal_match(matcher, field_value);
207
+ }
208
+
209
+ mongory_matcher *mongory_matcher_field_new(mongory_memory_pool *pool, char *field_name,
210
+ mongory_value *condition_for_field, void *extern_ctx) {
211
+ mongory_field_matcher *field_m = MG_ALLOC_PTR(pool, mongory_field_matcher);
212
+ if (field_m == NULL) {
213
+ pool->error = &MONGORY_ALLOC_ERROR;
214
+ return NULL;
215
+ }
216
+ field_m->field = mongory_string_cpy(pool, field_name);
217
+ if (field_m->field == NULL) {
218
+ // pool->free(field_m) is not standard; pool manages its own memory.
219
+ // This indicates an error in string copy, likely pool allocation.
220
+ return NULL;
221
+ }
222
+
223
+ // Initialize the base composite matcher part
224
+ field_m->literal.base.pool = pool;
225
+ field_m->literal.base.name = NULL; // Can be set if needed, e.g. to field_name
226
+ field_m->literal.base.match = mongory_matcher_field_match;
227
+ field_m->literal.base.original_match = mongory_matcher_field_match;
228
+ field_m->literal.base.sub_count = 1;
229
+ field_m->literal.base.condition = condition_for_field; // Original condition for the field
230
+ field_m->literal.base.name = mongory_string_cpy(pool, "Field");
231
+ field_m->literal.base.explain = mongory_matcher_field_explain;
232
+ field_m->literal.base.traverse = mongory_matcher_literal_traverse;
233
+ // The 'left' child of the composite is the actual matcher for the field's value,
234
+ // determined by the type of 'condition_for_field'.
235
+ field_m->literal.delegate_matcher = mongory_matcher_literal_delegate(pool, condition_for_field, extern_ctx);
236
+ field_m->literal.array_record_matcher = NULL; // Not typically used by field_match directly,
237
+ // but literal_match might use it for arrays.
238
+
239
+ if (field_m->literal.delegate_matcher == NULL) {
240
+ // Failed to create the delegate matcher for the condition.
241
+ return NULL;
242
+ }
243
+
244
+ return (mongory_matcher *)field_m;
245
+ }
246
+
247
+ /**
248
+ * @brief Match function for a NOT matcher.
249
+ * Negates the result of `mongory_matcher_literal_match`.
250
+ * @param matcher The $not matcher.
251
+ * @param value The value to evaluate.
252
+ * @return True if the literal match is false, false if it's true.
253
+ */
254
+ static inline bool mongory_matcher_not_match(mongory_matcher *matcher, mongory_value *value) {
255
+ // literal_match uses composite.left (and sometimes .right for arrays)
256
+ return !mongory_matcher_literal_match(matcher, value);
257
+ }
258
+
259
+ mongory_matcher *mongory_matcher_not_new(mongory_memory_pool *pool, mongory_value *condition_to_negate, void *extern_ctx) {
260
+ mongory_literal_matcher *literal = MG_ALLOC_PTR(pool, mongory_literal_matcher);
261
+ if (!literal)
262
+ return NULL;
263
+
264
+ // The 'left' child is the matcher for the condition being negated.
265
+ literal->delegate_matcher = mongory_matcher_literal_delegate(pool, condition_to_negate, extern_ctx);
266
+ if (!literal->delegate_matcher) {
267
+ return NULL; // Failed to create delegate for the condition.
268
+ }
269
+ literal->array_record_matcher = NULL;
270
+ // composite->right remains NULL for $not, as literal_match's array path
271
+ // via composite->right will use condition_to_negate if right is NULL.
272
+
273
+ literal->base.pool = pool;
274
+ literal->base.condition = condition_to_negate;
275
+ literal->base.match = mongory_matcher_not_match;
276
+ literal->base.original_match = mongory_matcher_not_match;
277
+ literal->base.name = mongory_string_cpy(pool, "Not");
278
+ literal->base.explain = mongory_matcher_literal_explain;
279
+ literal->base.traverse = mongory_matcher_literal_traverse;
280
+ literal->base.sub_count = 1;
281
+ literal->base.extern_ctx = extern_ctx;
282
+ return (mongory_matcher *)literal;
283
+ }
284
+
285
+ /**
286
+ * @brief Match function for a $size matcher.
287
+ * Checks if the input `value` (must be an array) has a size that matches
288
+ * the condition associated with the $size matcher.
289
+ * @param matcher The $size matcher.
290
+ * @param value The value to evaluate (must be an array).
291
+ * @return True if array size matches condition, false otherwise.
292
+ */
293
+ static inline bool mongory_matcher_size_match(mongory_matcher *matcher, mongory_value *value) {
294
+ if (!value || value->type != MONGORY_TYPE_ARRAY || !value->data.a) {
295
+ return false; // $size only applies to valid arrays.
296
+ }
297
+ mongory_array *array = value->data.a;
298
+ // Wrap the array's count as a mongory_value (integer) to be matched.
299
+ // The pool for this temporary size value should be from the input value or matcher.
300
+
301
+ // Use literal_match with the matcher's original condition against the size_value.
302
+ // The $size matcher's `composite.left` was set up by literal_delegate
303
+ // based on the condition provided to $size (e.g., if {$size: 5}, left is an
304
+ // equality matcher for 5).
305
+ return mongory_matcher_literal_match(matcher, mongory_value_wrap_i(value->pool, (int)array->count));
306
+ }
307
+
308
+ mongory_matcher *mongory_matcher_size_new(mongory_memory_pool *pool, mongory_value *size_condition, void *extern_ctx) {
309
+ mongory_literal_matcher *literal = MG_ALLOC_PTR(pool, mongory_literal_matcher);
310
+ if (!literal)
311
+ return NULL;
312
+
313
+ // The 'left' child is the matcher for the size_condition itself.
314
+ // E.g., if {$size: {$gt: 5}}, size_condition is {$gt: 5}, and
315
+ // composite.left becomes a "greater than 5" matcher.
316
+ literal->delegate_matcher = mongory_matcher_literal_delegate(pool, size_condition, extern_ctx);
317
+ if (!literal->delegate_matcher) {
318
+ return NULL;
319
+ }
320
+ literal->array_record_matcher = NULL;
321
+ // composite->right typically NULL for $size, array path of literal_match not primary.
322
+
323
+ literal->base.pool = pool;
324
+ literal->base.condition = size_condition;
325
+ literal->base.match = mongory_matcher_size_match;
326
+ literal->base.original_match = mongory_matcher_size_match;
327
+ literal->base.name = mongory_string_cpy(pool, "Size");
328
+ literal->base.explain = mongory_matcher_literal_explain;
329
+ literal->base.traverse = mongory_matcher_literal_traverse;
330
+ literal->base.sub_count = 1;
331
+ literal->base.extern_ctx = extern_ctx;
332
+ return (mongory_matcher *)literal;
333
+ }
334
+
@@ -0,0 +1,97 @@
1
+ #ifndef MONGORY_MATCHER_LITERAL_H
2
+ #define MONGORY_MATCHER_LITERAL_H
3
+
4
+ /**
5
+ * @file literal_matcher.h
6
+ * @brief Defines constructors for literal-based matchers, field matchers,
7
+ * $not, and $size. This is an internal header for the matcher module.
8
+ *
9
+ * "Literal" here often refers to matching a field's value against a specific
10
+ * condition, which might itself be a simple value or a more complex condition
11
+ * table.
12
+ */
13
+
14
+ #include "base_matcher.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
+ #include "matcher_explainable.h"
19
+ #include "composite_matcher.h"
20
+
21
+ typedef struct mongory_literal_matcher {
22
+ mongory_matcher base;
23
+ mongory_matcher *delegate_matcher;
24
+ mongory_matcher *array_record_matcher;
25
+ } mongory_literal_matcher;
26
+
27
+ /**
28
+ * @struct mongory_field_matcher
29
+ * @brief Specialized composite matcher for matching a specific field.
30
+ * Stores the field name/index.
31
+ */
32
+ typedef struct mongory_field_matcher {
33
+ mongory_literal_matcher literal; /**< Base composite matcher structure. */
34
+ char *field; /**< Name/index of the field to match. Copied string. */
35
+ } mongory_field_matcher;
36
+ /**
37
+ * @brief Creates a "field" matcher.
38
+ *
39
+ * This matcher extracts a value from a specified `field` (or array index) of
40
+ * an input `mongory_value` (which is expected to be a table or array). It then
41
+ * applies a sub-matcher (derived from `condition`) to this extracted field
42
+ * value.
43
+ *
44
+ * @param pool Memory pool for allocation.
45
+ * @param field The name of the field (if input is a table) or string
46
+ * representation of an index (if input is an array). A copy of this string is
47
+ * made.
48
+ * @param condition The `mongory_value` condition to apply to the field's value.
49
+ * This condition is processed by `mongory_matcher_literal_delegate` to determine
50
+ * the actual sub-matcher (e.g., equality, regex, nested table condition).
51
+ * @return A new field matcher, or NULL on failure.
52
+ */
53
+ mongory_matcher *mongory_matcher_field_new(mongory_memory_pool *pool, char *field, mongory_value *condition, void *extern_ctx);
54
+
55
+ /**
56
+ * @brief Creates a "NOT" ($not) matcher.
57
+ *
58
+ * This matcher negates the result of a sub-matcher derived from `condition`.
59
+ * The sub-matcher is typically determined by
60
+ * `mongory_matcher_literal_delegate`.
61
+ *
62
+ * @param pool Memory pool for allocation.
63
+ * @param condition The `mongory_value` condition whose result will be negated.
64
+ * @return A new $not matcher, or NULL on failure.
65
+ */
66
+ mongory_matcher *mongory_matcher_not_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx);
67
+
68
+ /**
69
+ * @brief Creates a "size" ($size) matcher.
70
+ *
71
+ * This matcher checks if the size (count of elements) of an input array
72
+ * matches the given `condition`. The `condition` itself is processed by
73
+ * `mongory_matcher_literal_delegate` (e.g., it could be a number for exact
74
+ * size, or a condition table like {$gt: 5}).
75
+ *
76
+ * @param pool Memory pool for allocation.
77
+ * @param condition The `mongory_value` condition to apply to the array's size.
78
+ * @return A new $size matcher, or NULL on failure.
79
+ */
80
+ mongory_matcher *mongory_matcher_size_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx);
81
+
82
+ /**
83
+ * @brief Creates a "literal" matcher (deprecated or internal use).
84
+ *
85
+ * This function seems to be a more generic entry point that delegates to
86
+ * `mongory_matcher_literal_delegate` and potentially `array_record_matcher`.
87
+ * Its direct public use might be limited compared to `field_new` or specific
88
+ * operator matchers. It appears to be part of the internal logic for how field
89
+ * values are matched.
90
+ *
91
+ * @param pool Memory pool for allocation.
92
+ * @param condition The literal `mongory_value` or condition table.
93
+ * @return A new literal matcher, or NULL on failure.
94
+ */
95
+ mongory_matcher *mongory_matcher_literal_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx);
96
+
97
+ #endif /* MONGORY_MATCHER_LITERAL_H */
@@ -0,0 +1,196 @@
1
+ /**
2
+ * @file matcher.c
3
+ * @brief Implements the generic mongory_matcher constructor.
4
+ *
5
+ * This file provides the implementation for the top-level matcher creation
6
+ * function.
7
+ */
8
+ #include <stdio.h>
9
+ #include <string.h>
10
+ #include "mongory-core/matchers/matcher.h" // Public API
11
+
12
+ // Required internal headers for delegation
13
+ #include "../foundations/config_private.h" // Potentially for global settings
14
+ #include "base_matcher.h" // For mongory_matcher_base_new if used directly
15
+ #include "composite_matcher.h" // For mongory_matcher_table_cond_new
16
+ #include "literal_matcher.h" // Potentially for other default constructions
17
+ #include "mongory-core/foundations/array.h"
18
+ #include "../foundations/string_buffer.h"
19
+ #include "mongory-core/foundations/memory_pool.h"
20
+ #include "mongory-core/foundations/value.h"
21
+ #include <mongory-core.h> // General include, might not be strictly necessary here
22
+
23
+
24
+ /**
25
+ * @brief Creates a new matcher based on the provided condition.
26
+ *
27
+ * This is the primary public entry point for creating a matcher. The library
28
+ * uses a factory pattern where this function determines the appropriate
29
+ * specific matcher to create based on the structure of the `condition` value.
30
+ *
31
+ * Currently, it always delegates to `mongory_matcher_table_cond_new`,
32
+ * which handles query documents (tables). This is the most common use case,
33
+ * where the condition is a table like `{ "field": { "$op": "value" } }`.
34
+ *
35
+ * @param pool The memory pool to be used for allocating the matcher.
36
+ * @param condition A `mongory_value` defining the matching criteria. This is
37
+ * typically a `mongory_table`.
38
+ * @return mongory_matcher* A pointer to the newly constructed matcher, or NULL
39
+ * if allocation fails or the condition is invalid.
40
+ */
41
+ mongory_matcher *mongory_matcher_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx) {
42
+ // The core logic is delegated to a more specific constructor.
43
+ // This design allows for easy extension; for example, a different constructor
44
+ // could be chosen here based on the `condition->type`.
45
+ mongory_matcher *matcher = mongory_matcher_table_cond_new(pool, condition, extern_ctx);
46
+ if (matcher == NULL) {
47
+ return NULL;
48
+ }
49
+
50
+ return matcher;
51
+ }
52
+
53
+ /**
54
+ * @brief Executes the matching logic for the given matcher.
55
+ *
56
+ * This function is a polymorphic wrapper. It invokes the `match` function
57
+ * pointer on the specific `mongory_matcher` instance, which will be one of
58
+ * the internal matching functions (e.g., from a compare_matcher or
59
+ * composite_matcher).
60
+ *
61
+ * @param matcher The matcher to use.
62
+ * @param value The value to check against the matcher's condition.
63
+ * @return True if the value satisfies the matcher's condition, false otherwise.
64
+ */
65
+ bool mongory_matcher_match(mongory_matcher *matcher, mongory_value *value) { return matcher->match(matcher, value); }
66
+
67
+ /**
68
+ * @brief Generates a human-readable explanation of the matcher's criteria.
69
+ *
70
+ * This function is a polymorphic wrapper around the `explain` function pointer,
71
+ * allowing different matcher types to provide their own specific explanations.
72
+ *
73
+ * @param matcher The matcher to explain.
74
+ * @param temp_pool A temporary memory pool for allocating the explanation string(s).
75
+ */
76
+ void mongory_matcher_explain(mongory_matcher *matcher, mongory_memory_pool *temp_pool) {
77
+ mongory_matcher_explain_context ctx = {
78
+ .pool = temp_pool,
79
+ .count = 0,
80
+ .total = 0,
81
+ .prefix = "",
82
+ };
83
+ matcher->explain(matcher, &ctx);
84
+ }
85
+
86
+ typedef struct mongory_matcher_traced_match_context {
87
+ char *message;
88
+ int level;
89
+ } mongory_matcher_traced_match_context;
90
+
91
+ static bool mongory_matcher_traced_match(mongory_matcher *matcher, mongory_value *value) {
92
+ bool matched = matcher->original_match(matcher, value);
93
+ mongory_memory_pool *pool = matcher->trace_stack->pool;
94
+
95
+ char *res = matched ? "\e[30;42mMatched\e[0m" : "\e[30;41mDismatch\e[0m";
96
+ char *cdtn = matcher->condition->to_str(matcher->condition, pool);
97
+ char *rcd = value == NULL ? "Nothing" : value->to_str(value, pool);
98
+ char *name = matcher->name;
99
+ char *message;
100
+
101
+ if (strcmp(name, "Field") == 0) {
102
+ mongory_field_matcher *field_matcher = (mongory_field_matcher *)matcher;
103
+ char *fd = field_matcher->field;
104
+ message = mongory_string_cpyf(pool, "%s: %s, field: \"%s\", condition: %s, record: %s\n", name, res, fd, cdtn, rcd);
105
+ } else {
106
+ message = mongory_string_cpyf(pool, "%s: %s, condition: %s, record: %s\n", name, res, cdtn, rcd);
107
+ }
108
+
109
+ mongory_matcher_traced_match_context *trace_result = MG_ALLOC_PTR(pool, mongory_matcher_traced_match_context);
110
+ trace_result->message = message;
111
+ trace_result->level = matcher->trace_level;
112
+ matcher->trace_stack->push(matcher->trace_stack, mongory_value_wrap_ptr(pool, (void *)trace_result));
113
+
114
+ return matched;
115
+ }
116
+
117
+ static bool mongory_matcher_enable_trace_cb(mongory_matcher *matcher, mongory_matcher_traverse_context *ctx) {
118
+ matcher->trace_stack = (mongory_array *)ctx->acc;
119
+ matcher->trace_level = ctx->level;
120
+ matcher->match = mongory_matcher_traced_match;
121
+ return true;
122
+ }
123
+
124
+ static bool mongory_matcher_disable_trace_cb(mongory_matcher *matcher, mongory_matcher_traverse_context *ctx) {
125
+ (void)ctx;
126
+ matcher->match = matcher->original_match;
127
+ matcher->trace_stack = NULL;
128
+ return true;
129
+ }
130
+
131
+ static mongory_array *mongory_matcher_traces_sort(mongory_array *self, int level) {
132
+ mongory_array *sorted_array = mongory_array_new(self->pool);
133
+ mongory_array *group = mongory_array_new(self->pool);
134
+ int total = (int)self->count;
135
+ for (int i = 0; i < total; i++) {
136
+ mongory_value *item = self->get(self, i);
137
+ mongory_matcher_traced_match_context *trace = (mongory_matcher_traced_match_context *)item->data.ptr;
138
+ if (trace->level == level) {
139
+ sorted_array->push(sorted_array, item);
140
+ mongory_array *sorted_group = mongory_matcher_traces_sort(group, level + 1);
141
+ int sorted_group_total = (int)sorted_group->count;
142
+ for (int j = 0; j < sorted_group_total; j++) {
143
+ sorted_array->push(sorted_array, sorted_group->get(sorted_group, j));
144
+ }
145
+ group = mongory_array_new(self->pool);
146
+ } else {
147
+ group->push(group, item);
148
+ }
149
+ }
150
+ return sorted_array;
151
+ }
152
+
153
+ void mongory_matcher_enable_trace(mongory_matcher *matcher, mongory_memory_pool *temp_pool) {
154
+ mongory_array *trace_stack = mongory_array_new(temp_pool);
155
+ mongory_matcher_traverse_context ctx = {
156
+ .pool = temp_pool,
157
+ .level = 0,
158
+ .count = 0,
159
+ .total = 0,
160
+ .acc = (void *)trace_stack,
161
+ .callback = mongory_matcher_enable_trace_cb,
162
+ };
163
+ matcher->traverse(matcher, &ctx);
164
+ }
165
+
166
+ void mongory_matcher_disable_trace(mongory_matcher *matcher) {
167
+ mongory_matcher_traverse_context ctx = {
168
+ .level = 0,
169
+ .count = 0,
170
+ .total = 0,
171
+ .callback = mongory_matcher_disable_trace_cb,
172
+ };
173
+ matcher->traverse(matcher, &ctx);
174
+ }
175
+
176
+ void mongory_matcher_print_trace(mongory_matcher *matcher) {
177
+ mongory_array *sorted_trace_stack = mongory_matcher_traces_sort(matcher->trace_stack, 0);
178
+ int total = (int)sorted_trace_stack->count;
179
+ for (int i = 0; i < total; i++) {
180
+ mongory_value *item = sorted_trace_stack->get(sorted_trace_stack, i);
181
+ mongory_matcher_traced_match_context *trace = (mongory_matcher_traced_match_context *)item->data.ptr;
182
+ int indent_size = trace->level * 2;
183
+ char *indent = MG_ALLOC(sorted_trace_stack->pool, indent_size + 1);
184
+ memset(indent, ' ', indent_size);
185
+ indent[indent_size] = '\0';
186
+ printf("%s%s", indent, trace->message);
187
+ }
188
+ }
189
+
190
+ bool mongory_matcher_trace(mongory_matcher *matcher, mongory_value *value) {
191
+ mongory_matcher_enable_trace(matcher, value->pool);
192
+ bool matched = matcher->match(matcher, value);
193
+ mongory_matcher_print_trace(matcher);
194
+ mongory_matcher_disable_trace(matcher);
195
+ return matched;
196
+ }