mongory 0.6.3 → 0.7.1
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 +46 -0
- data/README.md +83 -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 +198 -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/query_builder.rb +8 -0
- data/lib/mongory/utils/context.rb +7 -0
- 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 +69 -4
@@ -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 */
|