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.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +42 -0
- data/README.md +82 -176
- data/Rakefile +77 -0
- data/SUBMODULE_INTEGRATION.md +325 -0
- data/docs/advanced_usage.md +40 -0
- data/docs/clang_bridge.md +69 -0
- data/docs/field_names.md +30 -0
- data/docs/migration.md +30 -0
- data/docs/performance.md +61 -0
- data/examples/benchmark.rb +98 -19
- data/ext/mongory_ext/extconf.rb +91 -0
- data/ext/mongory_ext/mongory-core/LICENSE +3 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/array.h +105 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/config.h +206 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/error.h +82 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/memory_pool.h +95 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/table.h +108 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/value.h +175 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/matchers/matcher.h +76 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core.h +12 -0
- data/ext/mongory_ext/mongory-core/src/foundations/array.c +246 -0
- data/ext/mongory_ext/mongory-core/src/foundations/array_private.h +18 -0
- data/ext/mongory_ext/mongory-core/src/foundations/config.c +406 -0
- data/ext/mongory_ext/mongory-core/src/foundations/config_private.h +30 -0
- data/ext/mongory_ext/mongory-core/src/foundations/error.c +30 -0
- data/ext/mongory_ext/mongory-core/src/foundations/memory_pool.c +298 -0
- data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.c +65 -0
- data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.h +49 -0
- data/ext/mongory_ext/mongory-core/src/foundations/table.c +458 -0
- data/ext/mongory_ext/mongory-core/src/foundations/value.c +459 -0
- data/ext/mongory_ext/mongory-core/src/matchers/array_record_matcher.c +309 -0
- data/ext/mongory_ext/mongory-core/src/matchers/array_record_matcher.h +47 -0
- data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.c +161 -0
- data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.h +115 -0
- data/ext/mongory_ext/mongory-core/src/matchers/compare_matcher.c +210 -0
- data/ext/mongory_ext/mongory-core/src/matchers/compare_matcher.h +83 -0
- data/ext/mongory_ext/mongory-core/src/matchers/composite_matcher.c +539 -0
- data/ext/mongory_ext/mongory-core/src/matchers/composite_matcher.h +125 -0
- data/ext/mongory_ext/mongory-core/src/matchers/existance_matcher.c +144 -0
- data/ext/mongory_ext/mongory-core/src/matchers/existance_matcher.h +48 -0
- data/ext/mongory_ext/mongory-core/src/matchers/external_matcher.c +121 -0
- data/ext/mongory_ext/mongory-core/src/matchers/external_matcher.h +46 -0
- data/ext/mongory_ext/mongory-core/src/matchers/inclusion_matcher.c +199 -0
- data/ext/mongory_ext/mongory-core/src/matchers/inclusion_matcher.h +46 -0
- data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.c +334 -0
- data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.h +97 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher.c +196 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.c +107 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.h +50 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.c +55 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.h +23 -0
- data/ext/mongory_ext/mongory_ext.c +635 -0
- data/lib/mongory/c_query_builder.rb +44 -0
- data/lib/mongory/converters/converted.rb +2 -2
- data/lib/mongory/matchers/abstract_matcher.rb +4 -0
- data/lib/mongory/matchers/abstract_multi_matcher.rb +44 -0
- data/lib/mongory/matchers/and_matcher.rb +2 -23
- data/lib/mongory/matchers/array_record_matcher.rb +2 -23
- data/lib/mongory/matchers/elem_match_matcher.rb +4 -0
- data/lib/mongory/matchers/eq_matcher.rb +4 -0
- data/lib/mongory/matchers/every_matcher.rb +4 -0
- data/lib/mongory/matchers/exists_matcher.rb +4 -0
- data/lib/mongory/matchers/field_matcher.rb +4 -0
- data/lib/mongory/matchers/gt_matcher.rb +4 -0
- data/lib/mongory/matchers/gte_matcher.rb +4 -0
- data/lib/mongory/matchers/hash_condition_matcher.rb +2 -23
- data/lib/mongory/matchers/in_matcher.rb +13 -4
- data/lib/mongory/matchers/literal_matcher.rb +4 -0
- data/lib/mongory/matchers/lt_matcher.rb +4 -0
- data/lib/mongory/matchers/lte_matcher.rb +4 -0
- data/lib/mongory/matchers/ne_matcher.rb +4 -0
- data/lib/mongory/matchers/nin_matcher.rb +14 -5
- data/lib/mongory/matchers/not_matcher.rb +4 -0
- data/lib/mongory/matchers/or_matcher.rb +2 -23
- data/lib/mongory/matchers/present_matcher.rb +4 -0
- data/lib/mongory/matchers/regex_matcher.rb +4 -0
- data/lib/mongory/matchers/size_matcher.rb +4 -0
- data/lib/mongory/query_builder.rb +8 -0
- data/lib/mongory/utils/context.rb +7 -0
- data/lib/mongory/utils.rb +1 -1
- data/lib/mongory/version.rb +1 -1
- data/lib/mongory.rb +7 -0
- data/mongory.gemspec +10 -4
- data/scripts/build_with_core.sh +292 -0
- metadata +70 -5
@@ -0,0 +1,309 @@
|
|
1
|
+
/**
|
2
|
+
* @file array_record_matcher.c
|
3
|
+
* @brief Implements a versatile matcher for arrays, handling various condition
|
4
|
+
* types. This is an internal implementation file for the matcher module.
|
5
|
+
*
|
6
|
+
* This matcher can interpret conditions as:
|
7
|
+
* - A table: Parsed for operators like $elemMatch or implicit field conditions
|
8
|
+
* on array elements.
|
9
|
+
* - A regex: Creates an $elemMatch to match array elements against the regex.
|
10
|
+
* - A literal: Creates an $elemMatch to check for equality with elements.
|
11
|
+
* - Another array: Checks for whole-array equality.
|
12
|
+
* It often constructs a composite OR matcher to combine these possibilities.
|
13
|
+
*/
|
14
|
+
#include "array_record_matcher.h"
|
15
|
+
#include "../foundations/config_private.h" // For mongory_try_parse_int
|
16
|
+
#include "base_matcher.h" // For mongory_matcher_base_new, mongory_try_parse_int
|
17
|
+
#include "compare_matcher.h" // For mongory_matcher_equal_new
|
18
|
+
#include "composite_matcher.h" // For mongory_matcher_composite_new, $elemMatch, table_cond_new, or_match
|
19
|
+
#include "literal_matcher.h" // Potentially used by table_cond_new
|
20
|
+
#include "mongory-core/foundations/table.h"
|
21
|
+
#include "mongory-core/foundations/value.h"
|
22
|
+
#include <mongory-core.h> // General include
|
23
|
+
#include <stdio.h> // For NULL
|
24
|
+
#include <string.h> // For strcmp
|
25
|
+
|
26
|
+
/**
|
27
|
+
* @struct mongory_matcher_array_record_parse_table_context
|
28
|
+
* @brief Context used when parsing a table condition for array record matching.
|
29
|
+
*
|
30
|
+
* Helps separate parts of the condition table:
|
31
|
+
* - `parsed_table`: For explicit operators (e.g., $size, $all if implemented)
|
32
|
+
* or indexed conditions.
|
33
|
+
* - `elem_match_table`: For conditions that should apply to individual elements
|
34
|
+
* via an implicit or explicit $elemMatch.
|
35
|
+
*/
|
36
|
+
typedef struct mongory_matcher_array_record_parse_table_context {
|
37
|
+
mongory_table *parsed_table; /**< Stores operator or indexed conditions. */
|
38
|
+
mongory_table *elem_match_table; /**< Stores conditions for element matching. */
|
39
|
+
} mongory_matcher_array_record_parse_table_context;
|
40
|
+
|
41
|
+
/**
|
42
|
+
* @brief Callback to populate the `elem_match_table` during parsing.
|
43
|
+
* Used when an explicit $elemMatch object is found within the condition.
|
44
|
+
* @param key Key from the $elemMatch object.
|
45
|
+
* @param value Value from the $elemMatch object.
|
46
|
+
* @param acc Pointer to `mongory_matcher_array_record_parse_table_context`.
|
47
|
+
* @return Always true to continue iteration.
|
48
|
+
*/
|
49
|
+
static inline bool mongory_matcher_array_record_set_table_elem(char *key, mongory_value *value, void *acc) {
|
50
|
+
mongory_matcher_array_record_parse_table_context *context = (mongory_matcher_array_record_parse_table_context *)acc;
|
51
|
+
mongory_table *elem_match_table_to_populate = context->elem_match_table;
|
52
|
+
elem_match_table_to_populate->set(elem_match_table_to_populate, key, value);
|
53
|
+
return true;
|
54
|
+
}
|
55
|
+
|
56
|
+
/**
|
57
|
+
* @brief Callback to parse a condition table for array matching.
|
58
|
+
*
|
59
|
+
* Iterates through the main condition table:
|
60
|
+
* - If `key` is "$elemMatch" and `value` is a table, its contents are added to
|
61
|
+
* `context->elem_match_table`.
|
62
|
+
* - If `key` starts with '$' (another operator) or is numeric (array index),
|
63
|
+
* it's added to `context->parsed_table`.
|
64
|
+
* - Otherwise (plain field name), it's considered part of an implicit element
|
65
|
+
* match and added to `context->elem_match_table`.
|
66
|
+
*
|
67
|
+
* @param key Current key in the condition table.
|
68
|
+
* @param value Current value for the key.
|
69
|
+
* @param acc Pointer to `mongory_matcher_array_record_parse_table_context`.
|
70
|
+
* @return Always true to continue.
|
71
|
+
*/
|
72
|
+
static inline bool mongory_matcher_array_record_parse_table_foreach(char *key, mongory_value *value, void *acc) {
|
73
|
+
mongory_matcher_array_record_parse_table_context *context = (mongory_matcher_array_record_parse_table_context *)acc;
|
74
|
+
mongory_table *parsed_table_for_operators = context->parsed_table;
|
75
|
+
mongory_table *table_for_elem_match_conditions = context->elem_match_table;
|
76
|
+
|
77
|
+
if (strcmp(key, "$elemMatch") == 0 && value->type == MONGORY_TYPE_TABLE && value->data.t != NULL) {
|
78
|
+
// Explicit $elemMatch: iterate its sub-table and add to elem_match_table
|
79
|
+
mongory_table *elem_match_condition_object = value->data.t;
|
80
|
+
elem_match_condition_object->each(elem_match_condition_object, context,
|
81
|
+
mongory_matcher_array_record_set_table_elem);
|
82
|
+
} else if (*key == '$' || mongory_try_parse_int(key, NULL)) {
|
83
|
+
// Operator (like $size) or numeric index: goes to parsed_table
|
84
|
+
parsed_table_for_operators->set(parsed_table_for_operators, key, value);
|
85
|
+
} else {
|
86
|
+
// Regular field name: implies a condition on elements, goes to
|
87
|
+
// elem_match_table
|
88
|
+
table_for_elem_match_conditions->set(table_for_elem_match_conditions, key, value);
|
89
|
+
}
|
90
|
+
return true;
|
91
|
+
}
|
92
|
+
|
93
|
+
/**
|
94
|
+
* @brief Parses a table `condition` intended for array matching.
|
95
|
+
*
|
96
|
+
* Separates the condition into parts for direct array operations (like $size)
|
97
|
+
* and parts for matching individual elements (implicit or explicit $elemMatch).
|
98
|
+
* If any element-matching conditions are found, they are grouped under an
|
99
|
+
* "$elemMatch" key in the returned `parsed_table`.
|
100
|
+
*
|
101
|
+
* @param condition The `mongory_value` (must be a table) to parse.
|
102
|
+
* @return A new `mongory_value` (table) containing the parsed and restructured
|
103
|
+
* condition. Returns NULL if input is not a table or on allocation failure.
|
104
|
+
*/
|
105
|
+
static inline mongory_value *mongory_matcher_array_record_parse_table(mongory_value *condition) {
|
106
|
+
if (!condition || condition->type != MONGORY_TYPE_TABLE || !condition->data.t || !condition->pool) {
|
107
|
+
return NULL; // Invalid input
|
108
|
+
}
|
109
|
+
mongory_memory_pool *pool = condition->pool;
|
110
|
+
mongory_table *parsed_table = mongory_table_new(pool);
|
111
|
+
mongory_table *elem_match_sub_table = mongory_table_new(pool);
|
112
|
+
|
113
|
+
if (!parsed_table || !elem_match_sub_table) {
|
114
|
+
// Cleanup if one allocation succeeded but other failed? Pool should handle.
|
115
|
+
return NULL;
|
116
|
+
}
|
117
|
+
|
118
|
+
mongory_matcher_array_record_parse_table_context parse_ctx = {parsed_table, elem_match_sub_table};
|
119
|
+
mongory_table *original_condition_table = condition->data.t;
|
120
|
+
|
121
|
+
original_condition_table->each(original_condition_table, &parse_ctx,
|
122
|
+
mongory_matcher_array_record_parse_table_foreach);
|
123
|
+
|
124
|
+
if (elem_match_sub_table->count > 0) {
|
125
|
+
// If there were conditions for element matching, add them as an $elemMatch
|
126
|
+
// clause to the main parsed_table.
|
127
|
+
parsed_table->set(parsed_table, "$elemMatch", mongory_value_wrap_t(pool, elem_match_sub_table));
|
128
|
+
}
|
129
|
+
// If elem_match_sub_table is empty, it's not added. The original parsed_table
|
130
|
+
// (which might contain $size etc.) is returned.
|
131
|
+
return mongory_value_wrap_t(pool, parsed_table);
|
132
|
+
}
|
133
|
+
|
134
|
+
/**
|
135
|
+
* @brief Wraps a table in a value.
|
136
|
+
* @param pool Memory pool.
|
137
|
+
* @param key Key to set in the table.
|
138
|
+
* @param value Value to set in the table.
|
139
|
+
* @return A new `mongory_value` (table) containing the wrapped table.
|
140
|
+
*/
|
141
|
+
static inline mongory_value *mongory_matcher_array_record_parse_table_wrap(mongory_memory_pool *pool, char *key, mongory_value *value) {
|
142
|
+
if (!pool || !key || !value)
|
143
|
+
return NULL;
|
144
|
+
mongory_table *table = mongory_table_new(pool);
|
145
|
+
if (!table)
|
146
|
+
return NULL;
|
147
|
+
table->set(table, key, value);
|
148
|
+
return mongory_value_wrap_t(pool, table);
|
149
|
+
}
|
150
|
+
|
151
|
+
/**
|
152
|
+
* @brief Helper to create an $elemMatch matcher with an inner $regex condition.
|
153
|
+
* E.g., for matching an array where elements match a regex.
|
154
|
+
* @param pool Memory pool.
|
155
|
+
* @param regex_condition The `mongory_value` (string or regex type) for the
|
156
|
+
* regex.
|
157
|
+
* @return A new $elemMatch matcher with a nested $regex, or NULL on failure.
|
158
|
+
*/
|
159
|
+
static inline mongory_matcher *mongory_matcher_array_record_elem_match_regex_new(mongory_memory_pool *pool,
|
160
|
+
mongory_value *regex_condition, void *extern_ctx) {
|
161
|
+
mongory_value *wrapped_table = mongory_matcher_array_record_parse_table_wrap(pool, "$regex", regex_condition);
|
162
|
+
if (!wrapped_table)
|
163
|
+
return NULL;
|
164
|
+
return mongory_matcher_elem_match_new(pool, wrapped_table, extern_ctx);
|
165
|
+
}
|
166
|
+
|
167
|
+
/**
|
168
|
+
* @brief Helper to create an $elemMatch matcher with an inner $eq condition.
|
169
|
+
* E.g., for matching an array containing a specific literal value.
|
170
|
+
* @param pool Memory pool.
|
171
|
+
* @param literal_condition The `mongory_value` literal to find in array
|
172
|
+
* elements.
|
173
|
+
* @return A new $elemMatch matcher with a nested $eq, or NULL on failure.
|
174
|
+
*/
|
175
|
+
static inline mongory_matcher *mongory_matcher_array_record_elem_match_equal_new(mongory_memory_pool *pool,
|
176
|
+
mongory_value *literal_condition, void *extern_ctx) {
|
177
|
+
mongory_value *wrapped_table = mongory_matcher_array_record_parse_table_wrap(pool, "$eq", literal_condition);
|
178
|
+
if (!wrapped_table)
|
179
|
+
return NULL;
|
180
|
+
return mongory_matcher_elem_match_new(pool, wrapped_table, extern_ctx);
|
181
|
+
}
|
182
|
+
|
183
|
+
/**
|
184
|
+
* @brief Delegates creation of the "left" part of an array_record_matcher.
|
185
|
+
*
|
186
|
+
* This part typically handles conditions related to array elements (e.g.,
|
187
|
+
* $elemMatch logic).
|
188
|
+
* - If `condition` is a table, it's parsed by
|
189
|
+
* `mongory_matcher_array_record_parse_table` and then a
|
190
|
+
* `mongory_matcher_table_cond_new` is created.
|
191
|
+
* - If `condition` is regex, creates an $elemMatch with inner $regex.
|
192
|
+
* - Otherwise (literal), creates an $elemMatch with inner $eq.
|
193
|
+
*
|
194
|
+
* @param pool Memory pool.
|
195
|
+
* @param condition The original condition for the array_record_matcher.
|
196
|
+
* @return The "left" child matcher, or NULL on failure.
|
197
|
+
*/
|
198
|
+
static inline mongory_matcher *mongory_matcher_array_record_generic_delegate(mongory_memory_pool *pool,
|
199
|
+
mongory_value *condition, void *extern_ctx) {
|
200
|
+
if (!condition)
|
201
|
+
return NULL;
|
202
|
+
switch (condition->type) {
|
203
|
+
case MONGORY_TYPE_TABLE: {
|
204
|
+
mongory_value *parsed_table_condition = mongory_matcher_array_record_parse_table(condition);
|
205
|
+
if (!parsed_table_condition)
|
206
|
+
return NULL;
|
207
|
+
return mongory_matcher_table_cond_new(pool, parsed_table_condition, extern_ctx);
|
208
|
+
}
|
209
|
+
case MONGORY_TYPE_REGEX:
|
210
|
+
return mongory_matcher_array_record_elem_match_regex_new(pool, condition, extern_ctx);
|
211
|
+
default: // Literals (string, int, bool, etc.)
|
212
|
+
return mongory_matcher_array_record_elem_match_equal_new(pool, condition, extern_ctx);
|
213
|
+
}
|
214
|
+
}
|
215
|
+
|
216
|
+
/**
|
217
|
+
* @brief Delegates creation of the "right" part of an array_record_matcher.
|
218
|
+
*
|
219
|
+
* This part handles conditions where the array_record_matcher's `condition`
|
220
|
+
* is itself an array, implying a whole-array equality check.
|
221
|
+
*
|
222
|
+
* @param pool Memory pool.
|
223
|
+
* @param condition The original condition for the array_record_matcher.
|
224
|
+
* @return An equality matcher if `condition` is an array, otherwise NULL.
|
225
|
+
*/
|
226
|
+
static inline mongory_matcher *mongory_matcher_array_record_array_cond_delegate(mongory_memory_pool *pool,
|
227
|
+
mongory_value *condition, void *extern_ctx) {
|
228
|
+
if (!condition)
|
229
|
+
return NULL;
|
230
|
+
switch (condition->type) {
|
231
|
+
case MONGORY_TYPE_ARRAY:
|
232
|
+
// If original condition is an array, right delegate is for direct equality.
|
233
|
+
return mongory_matcher_equal_new(pool, condition, extern_ctx);
|
234
|
+
default:
|
235
|
+
return NULL; // No whole-array equality check needed for other types.
|
236
|
+
}
|
237
|
+
}
|
238
|
+
|
239
|
+
/**
|
240
|
+
* @brief Match function for the array_record_matcher.
|
241
|
+
*
|
242
|
+
* It expects the `matcher` to be a composite OR matcher.
|
243
|
+
* It checks if the input `value` is an array, then applies the OR logic.
|
244
|
+
* This OR logic typically combines:
|
245
|
+
* 1. Matching based on element conditions (via `composite->left` from
|
246
|
+
* `left_delegate`).
|
247
|
+
* 2. Matching based on whole-array equality (via `composite->right` from
|
248
|
+
* `right_delegate`, if applicable).
|
249
|
+
*
|
250
|
+
* @param matcher The array_record_matcher instance (cast as base `mongory_matcher`).
|
251
|
+
* @param value The `mongory_value` to check, expected to be an array.
|
252
|
+
* @return True if the array matches according to the ORed conditions, false
|
253
|
+
* otherwise.
|
254
|
+
*/
|
255
|
+
static inline bool mongory_matcher_array_record_match(mongory_matcher *matcher, mongory_value *value) {
|
256
|
+
if (value == NULL || value->type != MONGORY_TYPE_ARRAY) {
|
257
|
+
return false; // Only applies to arrays.
|
258
|
+
}
|
259
|
+
// This matcher is constructed as a composite OR internally.
|
260
|
+
return mongory_matcher_or_match(matcher, value);
|
261
|
+
}
|
262
|
+
|
263
|
+
/**
|
264
|
+
* @brief Main constructor for `mongory_matcher_array_record_new`.
|
265
|
+
*
|
266
|
+
* Constructs a two-part matcher (often combined with OR):
|
267
|
+
* - `left`: Handles element-wise conditions (like $elemMatch from various
|
268
|
+
* condition types).
|
269
|
+
* - `right`: Handles whole-array equality if the original `condition` was an
|
270
|
+
* array.
|
271
|
+
*
|
272
|
+
* If `right` is NULL (i.e., original `condition` was not an array), only the
|
273
|
+
* `left` matcher is returned. Otherwise, a composite OR matcher is created
|
274
|
+
* with `left` and `right` as children.
|
275
|
+
*
|
276
|
+
* @param pool Memory pool.
|
277
|
+
* @param condition The condition to apply to arrays.
|
278
|
+
* @return The constructed array_record_matcher, or NULL on failure.
|
279
|
+
*/
|
280
|
+
mongory_matcher *mongory_matcher_array_record_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx) {
|
281
|
+
if (!pool || !condition)
|
282
|
+
return NULL;
|
283
|
+
|
284
|
+
mongory_matcher *generic_child = mongory_matcher_array_record_generic_delegate(pool, condition, extern_ctx);
|
285
|
+
mongory_matcher *array_cond_child = mongory_matcher_array_record_array_cond_delegate(pool, condition, extern_ctx);
|
286
|
+
|
287
|
+
if (!(generic_child && array_cond_child)) {
|
288
|
+
return generic_child ? generic_child : array_cond_child;
|
289
|
+
}
|
290
|
+
|
291
|
+
// Both left (element-wise) and right (whole-array equality) are possible.
|
292
|
+
// Combine them with an OR.
|
293
|
+
mongory_composite_matcher *final_or_composite = mongory_matcher_composite_new(pool, condition, extern_ctx);
|
294
|
+
if (!final_or_composite) {
|
295
|
+
return NULL;
|
296
|
+
}
|
297
|
+
|
298
|
+
final_or_composite->children = mongory_array_new(pool);
|
299
|
+
if (!final_or_composite->children) {
|
300
|
+
return NULL;
|
301
|
+
}
|
302
|
+
final_or_composite->children->push(final_or_composite->children, (mongory_value *)generic_child);
|
303
|
+
final_or_composite->children->push(final_or_composite->children, (mongory_value *)array_cond_child);
|
304
|
+
final_or_composite->base.match = mongory_matcher_array_record_match; // Uses or_match
|
305
|
+
final_or_composite->base.original_match = mongory_matcher_array_record_match;
|
306
|
+
|
307
|
+
final_or_composite->base.name = mongory_string_cpy(pool, "ArrayRecord");
|
308
|
+
return (mongory_matcher *)final_or_composite;
|
309
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
#ifndef MONGORY_MATCHER_ARRAY_RECORD_H
|
2
|
+
#define MONGORY_MATCHER_ARRAY_RECORD_H
|
3
|
+
|
4
|
+
/**
|
5
|
+
* @file array_record_matcher.h
|
6
|
+
* @brief Defines the constructor for a matcher that handles complex conditions
|
7
|
+
* against arrays. This is an internal header for the matcher module.
|
8
|
+
*
|
9
|
+
* This matcher is more specialized than simple `$elemMatch` or `$every`. It can
|
10
|
+
* interpret a condition that might be a direct value to find, a regex to match
|
11
|
+
* elements, or a table defining multiple criteria for array elements (similar
|
12
|
+
* to an implicit `$elemMatch` with that table). It can also handle checking if
|
13
|
+
* an input array is equal to a condition array.
|
14
|
+
*/
|
15
|
+
|
16
|
+
#include "base_matcher.h"
|
17
|
+
#include "mongory-core/foundations/memory_pool.h"
|
18
|
+
#include "mongory-core/foundations/value.h"
|
19
|
+
#include "mongory-core/matchers/matcher.h" // For mongory_matcher structure
|
20
|
+
|
21
|
+
/**
|
22
|
+
* @brief Creates an "array record" matcher.
|
23
|
+
*
|
24
|
+
* This matcher is designed to apply various types of conditions to an input
|
25
|
+
* array or its elements. The behavior depends on the `condition`'s type:
|
26
|
+
* - If `condition` is a table: It's parsed. Keys like `$elemMatch` are handled
|
27
|
+
* specifically. Other field-value pairs in the table are typically wrapped
|
28
|
+
* into an implicit `$elemMatch` condition that applies to elements of the
|
29
|
+
* target array.
|
30
|
+
* - If `condition` is a regex: It effectively creates an `$elemMatch` where
|
31
|
+
* each element of the target array is matched against this regex.
|
32
|
+
* - If `condition` is a simple literal (string, number, bool): It creates an
|
33
|
+
* `$elemMatch` where elements are checked for equality against this literal.
|
34
|
+
* - If `condition` itself is an array: It creates a matcher that checks if the
|
35
|
+
* target array is equal to this condition array.
|
36
|
+
*
|
37
|
+
* The resulting matcher might be a composite of several internal matchers (e.g.,
|
38
|
+
* an OR between element matching and whole-array equality).
|
39
|
+
*
|
40
|
+
* @param pool Memory pool for allocation.
|
41
|
+
* @param condition A `mongory_value` representing the condition to apply to an
|
42
|
+
* array or its elements.
|
43
|
+
* @return A new array record matcher, or NULL on failure.
|
44
|
+
*/
|
45
|
+
mongory_matcher *mongory_matcher_array_record_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx);
|
46
|
+
|
47
|
+
#endif /* MONGORY_MATCHER_ARRAY_RECORD_H */
|
@@ -0,0 +1,161 @@
|
|
1
|
+
/**
|
2
|
+
* @file base_matcher.c
|
3
|
+
* @brief Implements the base matcher constructor and related utility functions.
|
4
|
+
* This is an internal implementation file for the matcher module.
|
5
|
+
*/
|
6
|
+
#include "base_matcher.h"
|
7
|
+
#include "../foundations/string_buffer.h"
|
8
|
+
#include <errno.h> // For errno, ERANGE
|
9
|
+
#include <limits.h> // For INT_MIN, INT_MAX
|
10
|
+
#include <mongory-core.h> // General include, for mongory_matcher types
|
11
|
+
#include <stdbool.h>
|
12
|
+
#include <stdio.h> // For printf
|
13
|
+
#include <stdlib.h> // For strtol
|
14
|
+
#include "matcher_explainable.h"
|
15
|
+
#include "matcher_traversable.h"
|
16
|
+
|
17
|
+
/**
|
18
|
+
* @brief Allocates and initializes common fields of a `mongory_matcher`.
|
19
|
+
*
|
20
|
+
* This function serves as a common initializer for all specific matcher types.
|
21
|
+
* It allocates memory for the `mongory_matcher` structure itself from the
|
22
|
+
* provided `pool`, sets the `pool` and `condition` members.
|
23
|
+
* The `matcher->match` function pointer specific to the matcher type must be
|
24
|
+
* set by the caller. `original_match` and `trace` in the context are set to
|
25
|
+
* NULL.
|
26
|
+
*
|
27
|
+
* @param pool The memory pool to use for allocation. Must be non-NULL and
|
28
|
+
* valid.
|
29
|
+
* @param condition The condition value for this matcher.
|
30
|
+
* @return mongory_matcher* Pointer to the newly allocated and partially
|
31
|
+
* initialized matcher, or NULL if allocation fails.
|
32
|
+
*/
|
33
|
+
mongory_matcher *mongory_matcher_base_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx) {
|
34
|
+
if (!pool || !pool->alloc) {
|
35
|
+
return NULL; // Invalid memory pool.
|
36
|
+
}
|
37
|
+
mongory_matcher *matcher = MG_ALLOC_PTR(pool, mongory_matcher);
|
38
|
+
if (matcher == NULL) {
|
39
|
+
// Allocation failed, pool->alloc might set pool->error.
|
40
|
+
pool->error = &MONGORY_ALLOC_ERROR;
|
41
|
+
return NULL;
|
42
|
+
}
|
43
|
+
|
44
|
+
// Initialize common fields
|
45
|
+
matcher->original_match = NULL;
|
46
|
+
matcher->sub_count = 0;
|
47
|
+
matcher->pool = pool;
|
48
|
+
matcher->condition = condition;
|
49
|
+
matcher->name = NULL; // Name is not set by base_new.
|
50
|
+
matcher->match = NULL; // Specific match function must be set by derived type.
|
51
|
+
matcher->explain = mongory_matcher_base_explain; // Specific explain function must be set by derived type.
|
52
|
+
matcher->traverse = mongory_matcher_leaf_traverse;
|
53
|
+
matcher->extern_ctx = extern_ctx; // Set the external context.
|
54
|
+
return matcher;
|
55
|
+
}
|
56
|
+
|
57
|
+
/**
|
58
|
+
* @brief The match function for a matcher that always returns true.
|
59
|
+
* @param matcher Unused.
|
60
|
+
* @param value Unused.
|
61
|
+
* @return Always true.
|
62
|
+
*/
|
63
|
+
static inline bool mongory_matcher_always_true_match(mongory_matcher *matcher, mongory_value *value) {
|
64
|
+
(void)matcher; // Mark as unused to prevent compiler warnings.
|
65
|
+
(void)value; // Mark as unused.
|
66
|
+
return true; // This matcher always indicates a match.
|
67
|
+
}
|
68
|
+
|
69
|
+
/**
|
70
|
+
* @brief Creates a matcher instance that will always evaluate to true.
|
71
|
+
* Useful as a placeholder or for default cases.
|
72
|
+
* @param pool Memory pool for allocation.
|
73
|
+
* @param condition Condition (typically ignored by this matcher).
|
74
|
+
* @return A new `mongory_matcher` or NULL on failure.
|
75
|
+
*/
|
76
|
+
mongory_matcher *mongory_matcher_always_true_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx) {
|
77
|
+
mongory_matcher *matcher = mongory_matcher_base_new(pool, condition, extern_ctx);
|
78
|
+
if (!matcher) {
|
79
|
+
return NULL;
|
80
|
+
}
|
81
|
+
matcher->match = mongory_matcher_always_true_match;
|
82
|
+
matcher->original_match = mongory_matcher_always_true_match;
|
83
|
+
matcher->name = mongory_string_cpy(pool, "Always True");
|
84
|
+
matcher->explain = mongory_matcher_base_explain;
|
85
|
+
// Optionally set original_match as well if it's a strict policy
|
86
|
+
// matcher->context.original_match = mongory_matcher_always_true_match;
|
87
|
+
return matcher;
|
88
|
+
}
|
89
|
+
|
90
|
+
/**
|
91
|
+
* @brief The match function for a matcher that always returns false.
|
92
|
+
* @param matcher Unused.
|
93
|
+
* @param value Unused.
|
94
|
+
* @return Always false.
|
95
|
+
*/
|
96
|
+
static inline bool mongory_matcher_always_false_match(mongory_matcher *matcher, mongory_value *value) {
|
97
|
+
(void)matcher; // Mark as unused.
|
98
|
+
(void)value; // Mark as unused.
|
99
|
+
return false; // This matcher never indicates a match.
|
100
|
+
}
|
101
|
+
|
102
|
+
/**
|
103
|
+
* @brief Creates a matcher instance that will always evaluate to false.
|
104
|
+
* Useful for conditions that should never match.
|
105
|
+
* @param pool Memory pool for allocation.
|
106
|
+
* @param condition Condition (typically ignored by this matcher).
|
107
|
+
* @return A new `mongory_matcher` or NULL on failure.
|
108
|
+
*/
|
109
|
+
mongory_matcher *mongory_matcher_always_false_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx) {
|
110
|
+
mongory_matcher *matcher = mongory_matcher_base_new(pool, condition, extern_ctx);
|
111
|
+
if (!matcher) {
|
112
|
+
return NULL;
|
113
|
+
}
|
114
|
+
matcher->match = mongory_matcher_always_false_match;
|
115
|
+
matcher->original_match = mongory_matcher_always_false_match;
|
116
|
+
matcher->name = mongory_string_cpy(pool, "Always False");
|
117
|
+
matcher->explain = mongory_matcher_base_explain;
|
118
|
+
// matcher->context.original_match = mongory_matcher_always_false_match;
|
119
|
+
return matcher;
|
120
|
+
}
|
121
|
+
|
122
|
+
/**
|
123
|
+
* @brief Attempts to parse a string `key` into an integer `out`.
|
124
|
+
*
|
125
|
+
* Uses `strtol` for conversion and checks for common parsing errors:
|
126
|
+
* - Input string is NULL or empty.
|
127
|
+
* - The string contains non-numeric characters after the number.
|
128
|
+
* - The parsed number is out of the range of `int` (`INT_MIN`, `INT_MAX`).
|
129
|
+
*
|
130
|
+
* @param key The null-terminated string to parse.
|
131
|
+
* @param out Pointer to an integer where the result is stored.
|
132
|
+
* @return `true` if parsing is successful and the value fits in an `int`.
|
133
|
+
* `false` otherwise. `errno` may be set by `strtol`.
|
134
|
+
*/
|
135
|
+
bool mongory_try_parse_int(const char *key, int *out) {
|
136
|
+
if (key == NULL || *key == '\0') {
|
137
|
+
return false; // Invalid input string.
|
138
|
+
}
|
139
|
+
if (out == NULL) {
|
140
|
+
return false; // Output pointer must be valid.
|
141
|
+
}
|
142
|
+
|
143
|
+
char *endptr = NULL;
|
144
|
+
errno = 0; // Clear errno before calling strtol.
|
145
|
+
long val = strtol(key, &endptr, 10); // Base 10 conversion.
|
146
|
+
|
147
|
+
// Check for parsing errors reported by strtol.
|
148
|
+
if (endptr == key) {
|
149
|
+
return false; // No digits were found.
|
150
|
+
}
|
151
|
+
if (*endptr != '\0') {
|
152
|
+
return false; // Additional characters after the number.
|
153
|
+
}
|
154
|
+
if (errno == ERANGE || val < INT_MIN || val > INT_MAX) {
|
155
|
+
// Value out of range for int. ERANGE is set by strtol for overflow/underflow.
|
156
|
+
return false;
|
157
|
+
}
|
158
|
+
|
159
|
+
*out = (int)val; // Successfully parsed and within int range.
|
160
|
+
return true;
|
161
|
+
}
|
@@ -0,0 +1,115 @@
|
|
1
|
+
#ifndef MONGORY_MATCHER_BASE_H
|
2
|
+
#define MONGORY_MATCHER_BASE_H
|
3
|
+
|
4
|
+
/**
|
5
|
+
* @file base_matcher.h
|
6
|
+
* @brief Defines the base matcher constructor and utility functions for
|
7
|
+
* matchers. This is an internal header for the matcher module.
|
8
|
+
*
|
9
|
+
* This includes a constructor for the fundamental `mongory_matcher` structure,
|
10
|
+
* constructors for trivial matchers (always true/false), and a utility
|
11
|
+
* for parsing integers from strings.
|
12
|
+
*/
|
13
|
+
|
14
|
+
#include "mongory-core/foundations/array.h" // For mongory_array (context trace)
|
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 "matcher_traversable.h"
|
20
|
+
#include <stdbool.h>
|
21
|
+
|
22
|
+
/**
|
23
|
+
* @brief Function pointer type for a matcher's core matching logic.
|
24
|
+
*
|
25
|
+
* @param matcher A pointer to the `mongory_matcher` instance itself.
|
26
|
+
* @param value A pointer to the `mongory_value` to be evaluated against the
|
27
|
+
* matcher's condition.
|
28
|
+
* @return bool True if the `value` matches the condition, false otherwise.
|
29
|
+
*/
|
30
|
+
typedef bool (*mongory_matcher_match_func)(mongory_matcher *matcher, mongory_value *value);
|
31
|
+
|
32
|
+
/**
|
33
|
+
* @struct mongory_matcher
|
34
|
+
* @brief Represents a generic matcher in the Mongory system.
|
35
|
+
*
|
36
|
+
* Each matcher has a name (optional, for identification), a condition value
|
37
|
+
* that defines its criteria, a function pointer to its matching logic,
|
38
|
+
* a memory pool for its allocations, and a context.
|
39
|
+
*/
|
40
|
+
struct mongory_matcher {
|
41
|
+
char *name; /**< Optional name for the matcher (e.g., "$eq").
|
42
|
+
String is typically allocated from the pool. */
|
43
|
+
mongory_value *condition; /**< The condition (a `mongory_value`) that this
|
44
|
+
matcher evaluates against. */
|
45
|
+
mongory_matcher_match_func match; /**< Function pointer to the specific matching
|
46
|
+
logic for this matcher type. */
|
47
|
+
mongory_memory_pool *pool; /**< The memory pool used for allocations related
|
48
|
+
to this matcher instance. */
|
49
|
+
mongory_matcher_explain_func explain; /**< Function pointer to the explanation
|
50
|
+
logic for this matcher type. */
|
51
|
+
mongory_matcher_match_func original_match; /**< Stores the original match function, potentially for
|
52
|
+
restoration or delegation. */
|
53
|
+
size_t sub_count; /**< The number of sub-matchers. */
|
54
|
+
mongory_matcher_traverse_func traverse; /**< Function pointer to the traversal logic for this matcher type. */
|
55
|
+
mongory_array *trace_stack; /**< The trace stack for this matcher. */
|
56
|
+
int trace_level; /**< The trace level for this matcher. */
|
57
|
+
void *extern_ctx; /**< External context for the matcher. */
|
58
|
+
};
|
59
|
+
|
60
|
+
/**
|
61
|
+
* @brief Creates a new base `mongory_matcher` instance and initializes its
|
62
|
+
* common fields.
|
63
|
+
*
|
64
|
+
* This function allocates a `mongory_matcher` structure from the provided pool
|
65
|
+
* and sets its `pool` and `condition` fields. The `match` function and other
|
66
|
+
* specific fields must be set by the caller or derived matcher constructors.
|
67
|
+
* The context's original_match and trace are initialized to NULL.
|
68
|
+
*
|
69
|
+
* @param pool The memory pool to use for allocating the matcher.
|
70
|
+
* @param condition The `mongory_value` representing the condition for this
|
71
|
+
* matcher.
|
72
|
+
* @return mongory_matcher* A pointer to the newly allocated base matcher, or
|
73
|
+
* NULL on allocation failure.
|
74
|
+
*/
|
75
|
+
mongory_matcher *mongory_matcher_base_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx);
|
76
|
+
|
77
|
+
/**
|
78
|
+
* @brief Creates a new matcher that always evaluates to true.
|
79
|
+
*
|
80
|
+
* @param pool The memory pool for allocation.
|
81
|
+
* @param condition The condition value (often unused by this matcher but stored
|
82
|
+
* for consistency).
|
83
|
+
* @return mongory_matcher* A pointer to the "always true" matcher, or NULL on
|
84
|
+
* failure.
|
85
|
+
*/
|
86
|
+
mongory_matcher *mongory_matcher_always_true_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx);
|
87
|
+
|
88
|
+
/**
|
89
|
+
* @brief Creates a new matcher that always evaluates to false.
|
90
|
+
*
|
91
|
+
* @param pool The memory pool for allocation.
|
92
|
+
* @param condition The condition value (often unused by this matcher but stored
|
93
|
+
* for consistency).
|
94
|
+
* @return mongory_matcher* A pointer to the "always false" matcher, or NULL on
|
95
|
+
* failure.
|
96
|
+
*/
|
97
|
+
mongory_matcher *mongory_matcher_always_false_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx);
|
98
|
+
|
99
|
+
/**
|
100
|
+
* @brief Attempts to parse an integer from a string.
|
101
|
+
*
|
102
|
+
* This utility function is useful for matchers that might operate on array
|
103
|
+
* indices or numeric string fields. It checks for valid integer formats and
|
104
|
+
* range (`INT_MIN`, `INT_MAX`).
|
105
|
+
*
|
106
|
+
* @param key The null-terminated string to parse. Must not be NULL or empty.
|
107
|
+
* @param out A pointer to an integer where the parsed value will be stored if
|
108
|
+
* successful. Must not be NULL.
|
109
|
+
* @return bool True if the string was successfully parsed as an integer and is
|
110
|
+
* within `int` range, false otherwise. `errno` might be set by `strtol` on
|
111
|
+
* failure (e.g. `ERANGE`).
|
112
|
+
*/
|
113
|
+
bool mongory_try_parse_int(const char *key, int *out);
|
114
|
+
|
115
|
+
#endif /* MONGORY_MATCHER_BASE_H */
|