mongory 0.7.6 → 0.7.7
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/Rakefile +0 -14
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/array.h +122 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/config.h +161 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/error.h +79 -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 +127 -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 +287 -0
- data/ext/mongory_ext/mongory-core/src/foundations/array_private.h +19 -0
- data/ext/mongory_ext/mongory-core/src/foundations/config.c +270 -0
- data/ext/mongory_ext/mongory-core/src/foundations/config_private.h +48 -0
- data/ext/mongory_ext/mongory-core/src/foundations/error.c +38 -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 +498 -0
- data/ext/mongory_ext/mongory-core/src/foundations/utils.c +210 -0
- data/ext/mongory_ext/mongory-core/src/foundations/utils.h +70 -0
- data/ext/mongory_ext/mongory-core/src/foundations/value.c +500 -0
- data/ext/mongory_ext/mongory-core/src/matchers/array_record_matcher.c +164 -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 +122 -0
- data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.h +100 -0
- data/ext/mongory_ext/mongory-core/src/matchers/compare_matcher.c +217 -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 +573 -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 +147 -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 +124 -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 +126 -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 +314 -0
- data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.h +97 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher.c +252 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.c +79 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.h +23 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.c +60 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.h +23 -0
- data/lib/mongory/version.rb +1 -1
- metadata +43 -2
@@ -0,0 +1,500 @@
|
|
1
|
+
/**
|
2
|
+
* @file value.c
|
3
|
+
* @brief Implements the mongory_value generic value type and its operations.
|
4
|
+
*
|
5
|
+
* This file provides functions for creating (`wrap`), comparing, and
|
6
|
+
* converting `mongory_value` instances to strings. Each data type supported by
|
7
|
+
* `mongory_value` has a corresponding comparison function and wrapping
|
8
|
+
* function.
|
9
|
+
*/
|
10
|
+
#include "string_buffer.h"
|
11
|
+
#include <mongory-core/foundations/array.h>
|
12
|
+
#include <mongory-core/foundations/config.h>
|
13
|
+
#include "utils.h" // For mongory_string_cpy
|
14
|
+
#include <mongory-core/foundations/memory_pool.h>
|
15
|
+
#include <mongory-core/foundations/table.h>
|
16
|
+
#include <mongory-core/foundations/value.h>
|
17
|
+
#include "config_private.h"
|
18
|
+
#include <stddef.h> // For NULL
|
19
|
+
#include <stdio.h> // For snprintf
|
20
|
+
#include <stdlib.h> // For general utilities (not directly used here but common)
|
21
|
+
#include <string.h> // For strcmp
|
22
|
+
|
23
|
+
/**
|
24
|
+
* @brief Returns a string literal representing the type of the mongory_value.
|
25
|
+
* @param value The mongory_value to inspect.
|
26
|
+
* @return A string like "Int", "String", "Array", etc., or "UnknownType".
|
27
|
+
*/
|
28
|
+
char *mongory_type_to_string(mongory_value *value) {
|
29
|
+
if (!value)
|
30
|
+
return "NullValuePtr"; // Handle null pointer to value itself
|
31
|
+
switch (value->type) {
|
32
|
+
// Use X-Macro to generate cases for each defined type.
|
33
|
+
#define CASE_GEN(name, num, str, field) \
|
34
|
+
case name: \
|
35
|
+
return str;
|
36
|
+
MONGORY_TYPE_MACRO(CASE_GEN)
|
37
|
+
#undef CASE_GEN
|
38
|
+
default:
|
39
|
+
return "UnknownType"; // Fallback for unrecognized types.
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
/**
|
44
|
+
* @brief Extracts a void pointer to the raw data within the mongory_value.
|
45
|
+
* The caller must know the actual type to cast this pointer correctly.
|
46
|
+
* @param value The mongory_value from which to extract data.
|
47
|
+
* @return A void pointer to the data, or NULL for unknown types or MONGORY_TYPE_NULL.
|
48
|
+
*/
|
49
|
+
void *mongory_value_extract(mongory_value *value) {
|
50
|
+
if (!value)
|
51
|
+
return NULL;
|
52
|
+
switch (value->type) {
|
53
|
+
// Use X-Macro to generate cases. For MONGORY_TYPE_NULL, &value->data.i is
|
54
|
+
// returned but is meaningless as data for NULL.
|
55
|
+
#define EXTRACT_CASE(name, num, str, field) \
|
56
|
+
case name: \
|
57
|
+
return (void *)&value->data.field;
|
58
|
+
MONGORY_TYPE_MACRO(EXTRACT_CASE)
|
59
|
+
#undef EXTRACT_CASE
|
60
|
+
default:
|
61
|
+
return NULL; // Unknown type.
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
static char *mongory_value_null_to_str(mongory_value *value, mongory_memory_pool *pool);
|
66
|
+
static char *mongory_value_bool_to_str(mongory_value *value, mongory_memory_pool *pool);
|
67
|
+
static char *mongory_value_int_to_str(mongory_value *value, mongory_memory_pool *pool);
|
68
|
+
static char *mongory_value_double_to_str(mongory_value *value, mongory_memory_pool *pool);
|
69
|
+
static char *mongory_value_string_to_str(mongory_value *value, mongory_memory_pool *pool);
|
70
|
+
static char *mongory_value_array_to_str(mongory_value *value, mongory_memory_pool *pool);
|
71
|
+
static char *mongory_value_table_to_str(mongory_value *value, mongory_memory_pool *pool);
|
72
|
+
static char *mongory_value_generic_ptr_to_str(mongory_value *value, mongory_memory_pool *pool);
|
73
|
+
|
74
|
+
/**
|
75
|
+
* @brief Internal helper to allocate a new mongory_value structure from a pool.
|
76
|
+
* @param pool The memory pool to allocate from.
|
77
|
+
* @return A pointer to the new mongory_value, or NULL on allocation failure.
|
78
|
+
*/
|
79
|
+
static inline mongory_value *mongory_value_new(mongory_memory_pool *pool) {
|
80
|
+
if (!pool || !pool->alloc)
|
81
|
+
return NULL; // Invalid pool.
|
82
|
+
mongory_value *value = MG_ALLOC_PTR(pool, mongory_value);
|
83
|
+
if (!value) {
|
84
|
+
pool->error = &MONGORY_ALLOC_ERROR;
|
85
|
+
return NULL;
|
86
|
+
}
|
87
|
+
value->pool = pool;
|
88
|
+
value->origin = NULL; // Default origin to NULL.
|
89
|
+
return value;
|
90
|
+
}
|
91
|
+
|
92
|
+
// ============================================================================
|
93
|
+
// Comparison Functions
|
94
|
+
//
|
95
|
+
// Each data type has a corresponding comparison function. These functions
|
96
|
+
// are assigned to the `comp` function pointer in the `mongory_value` struct.
|
97
|
+
// They typically handle comparisons with other compatible types (e.g., Int
|
98
|
+
// can be compared with Double).
|
99
|
+
// ============================================================================
|
100
|
+
|
101
|
+
/** Compares two MONGORY_TYPE_NULL values. Always equal. */
|
102
|
+
static inline int mongory_value_null_compare(mongory_value *a, mongory_value *b) {
|
103
|
+
(void)a; // 'a' is implicitly MONGORY_TYPE_NULL by context of call.
|
104
|
+
if (b->type != MONGORY_TYPE_NULL) {
|
105
|
+
return mongory_value_compare_fail; // Cannot compare NULL with non-NULL.
|
106
|
+
}
|
107
|
+
return 0; // NULL is equal to NULL.
|
108
|
+
}
|
109
|
+
|
110
|
+
/** Wraps a NULL value. The `n` parameter is ignored. */
|
111
|
+
mongory_value *mongory_value_wrap_n(mongory_memory_pool *pool, void *n) {
|
112
|
+
(void)n; // Parameter 'n' is unused for wrapping NULL.
|
113
|
+
mongory_value *value = mongory_value_new(pool);
|
114
|
+
if (!value)
|
115
|
+
return NULL;
|
116
|
+
value->type = MONGORY_TYPE_NULL;
|
117
|
+
value->comp = mongory_value_null_compare;
|
118
|
+
value->to_str = mongory_value_null_to_str;
|
119
|
+
// data union is not explicitly set for NULL.
|
120
|
+
return value;
|
121
|
+
}
|
122
|
+
|
123
|
+
// ============================================================================
|
124
|
+
// Wrapper Functions
|
125
|
+
//
|
126
|
+
// These functions create a `mongory_value` and wrap a native C type
|
127
|
+
// (like bool, int, char*). They allocate the value from the memory pool and
|
128
|
+
// set its type, data, and function pointers (`comp`, `to_str`).
|
129
|
+
// ============================================================================
|
130
|
+
|
131
|
+
/** Compares two MONGORY_TYPE_BOOL values. */
|
132
|
+
static inline int mongory_value_bool_compare(mongory_value *a, mongory_value *b) {
|
133
|
+
if (b->type != MONGORY_TYPE_BOOL)
|
134
|
+
return mongory_value_compare_fail;
|
135
|
+
// Standard comparison: (true > false)
|
136
|
+
return (a->data.b > b->data.b) - (a->data.b < b->data.b);
|
137
|
+
}
|
138
|
+
|
139
|
+
/** Wraps a boolean value. */
|
140
|
+
mongory_value *mongory_value_wrap_b(mongory_memory_pool *pool, bool b_val) {
|
141
|
+
mongory_value *value = mongory_value_new(pool);
|
142
|
+
if (!value)
|
143
|
+
return NULL;
|
144
|
+
value->type = MONGORY_TYPE_BOOL;
|
145
|
+
value->data.b = b_val;
|
146
|
+
value->comp = mongory_value_bool_compare;
|
147
|
+
value->to_str = mongory_value_bool_to_str;
|
148
|
+
return value;
|
149
|
+
}
|
150
|
+
|
151
|
+
/** Compares MONGORY_TYPE_INT with INT or DOUBLE. */
|
152
|
+
static inline int mongory_value_int_compare(mongory_value *a, mongory_value *b) {
|
153
|
+
if (b->type == MONGORY_TYPE_DOUBLE) {
|
154
|
+
// Compare int (a) as double with double (b).
|
155
|
+
double a_as_double = (double)a->data.i;
|
156
|
+
double b_value = b->data.d;
|
157
|
+
return (a_as_double > b_value) - (a_as_double < b_value);
|
158
|
+
}
|
159
|
+
if (b->type == MONGORY_TYPE_INT) {
|
160
|
+
// Compare int (a) with int (b).
|
161
|
+
int64_t a_value = a->data.i;
|
162
|
+
int64_t b_value = b->data.i;
|
163
|
+
return (a_value > b_value) - (a_value < b_value);
|
164
|
+
}
|
165
|
+
return mongory_value_compare_fail; // Incompatible type for comparison.
|
166
|
+
}
|
167
|
+
|
168
|
+
/** Wraps an integer (promoted to int64_t). */
|
169
|
+
mongory_value *mongory_value_wrap_i(mongory_memory_pool *pool, int64_t i_val) {
|
170
|
+
mongory_value *value = mongory_value_new(pool);
|
171
|
+
if (!value)
|
172
|
+
return NULL;
|
173
|
+
value->type = MONGORY_TYPE_INT;
|
174
|
+
value->data.i = i_val;
|
175
|
+
value->comp = mongory_value_int_compare;
|
176
|
+
value->to_str = mongory_value_int_to_str;
|
177
|
+
return value;
|
178
|
+
}
|
179
|
+
|
180
|
+
/** Compares MONGORY_TYPE_DOUBLE with DOUBLE or INT. */
|
181
|
+
static inline int mongory_value_double_compare(mongory_value *a, mongory_value *b) {
|
182
|
+
if (b->type == MONGORY_TYPE_DOUBLE) {
|
183
|
+
double a_value = a->data.d;
|
184
|
+
double b_value = b->data.d;
|
185
|
+
return (a_value > b_value) - (a_value < b_value);
|
186
|
+
}
|
187
|
+
if (b->type == MONGORY_TYPE_INT) {
|
188
|
+
// Compare double (a) with int (b) as double.
|
189
|
+
double a_value = a->data.d;
|
190
|
+
double b_as_double = (double)b->data.i;
|
191
|
+
return (a_value > b_as_double) - (a_value < b_as_double);
|
192
|
+
}
|
193
|
+
return mongory_value_compare_fail; // Incompatible type.
|
194
|
+
}
|
195
|
+
|
196
|
+
/** Wraps a double value. */
|
197
|
+
mongory_value *mongory_value_wrap_d(mongory_memory_pool *pool, double d_val) {
|
198
|
+
mongory_value *value = mongory_value_new(pool);
|
199
|
+
if (!value)
|
200
|
+
return NULL;
|
201
|
+
value->type = MONGORY_TYPE_DOUBLE;
|
202
|
+
value->data.d = d_val;
|
203
|
+
value->comp = mongory_value_double_compare;
|
204
|
+
value->to_str = mongory_value_double_to_str;
|
205
|
+
return value;
|
206
|
+
}
|
207
|
+
|
208
|
+
/** Compares two MONGORY_TYPE_STRING values lexicographically. */
|
209
|
+
static inline int mongory_value_string_compare(mongory_value *a, mongory_value *b) {
|
210
|
+
// Ensure both are strings and not NULL pointers.
|
211
|
+
if (b->type != MONGORY_TYPE_STRING || a->data.s == NULL || b->data.s == NULL) {
|
212
|
+
// If one is NULL string and other is not, how to compare?
|
213
|
+
// For now, strict: both must be valid strings.
|
214
|
+
// Or, define NULL string < non-NULL string.
|
215
|
+
// Current: fail if not string or if actual char* is NULL.
|
216
|
+
return mongory_value_compare_fail;
|
217
|
+
}
|
218
|
+
int cmp_result = strcmp(a->data.s, b->data.s);
|
219
|
+
return (cmp_result > 0) - (cmp_result < 0); // Normalize to -1, 0, 1
|
220
|
+
}
|
221
|
+
|
222
|
+
/** Wraps a string. Makes a copy of the string using the pool. */
|
223
|
+
mongory_value *mongory_value_wrap_s(mongory_memory_pool *pool, char *s_val) {
|
224
|
+
mongory_value *value = mongory_value_new(pool);
|
225
|
+
if (!value)
|
226
|
+
return NULL;
|
227
|
+
value->type = MONGORY_TYPE_STRING;
|
228
|
+
// `mongory_string_cpy` handles the allocation and copying of the string.
|
229
|
+
// If `s_val` is NULL, `value->data.s` will be NULL.
|
230
|
+
// If allocation fails, `value->data.s` will also be NULL.
|
231
|
+
// TODO: This function should ideally return NULL if the string copy fails
|
232
|
+
// when `s_val` was not NULL, but this requires a more complex error handling
|
233
|
+
// strategy with the memory pool.
|
234
|
+
value->data.s = mongory_string_cpy(pool, s_val);
|
235
|
+
value->comp = mongory_value_string_compare;
|
236
|
+
value->to_str = mongory_value_string_to_str;
|
237
|
+
return value;
|
238
|
+
}
|
239
|
+
|
240
|
+
/** Compares two MONGORY_TYPE_ARRAY values element by element. */
|
241
|
+
static inline int mongory_value_array_compare(mongory_value *a, mongory_value *b) {
|
242
|
+
if (b->type != MONGORY_TYPE_ARRAY || a->data.a == NULL || b->data.a == NULL) {
|
243
|
+
return mongory_value_compare_fail; // Must be two valid arrays.
|
244
|
+
}
|
245
|
+
struct mongory_array *array_a = a->data.a;
|
246
|
+
struct mongory_array *array_b = b->data.a;
|
247
|
+
|
248
|
+
// First, compare by array size. An array with fewer elements is "less than"
|
249
|
+
// an array with more elements.
|
250
|
+
if (array_a->count != array_b->count) {
|
251
|
+
return (array_a->count > array_b->count) - (array_a->count < array_b->count);
|
252
|
+
}
|
253
|
+
|
254
|
+
// Same count, compare element by element.
|
255
|
+
for (size_t i = 0; i < array_a->count; i++) {
|
256
|
+
mongory_value *item_a = array_a->get(array_a, i);
|
257
|
+
mongory_value *item_b = array_b->get(array_b, i);
|
258
|
+
|
259
|
+
// Handle NULL elements within arrays carefully.
|
260
|
+
bool a_item_is_null = (item_a == NULL || item_a->type == MONGORY_TYPE_NULL);
|
261
|
+
bool b_item_is_null = (item_b == NULL || item_b->type == MONGORY_TYPE_NULL);
|
262
|
+
|
263
|
+
if (a_item_is_null && b_item_is_null)
|
264
|
+
continue; // Both null, considered equal here.
|
265
|
+
if (a_item_is_null)
|
266
|
+
return -1; // Null is less than non-null.
|
267
|
+
if (b_item_is_null)
|
268
|
+
return 1; // Non-null is greater than null.
|
269
|
+
|
270
|
+
// Both items are non-null, compare them using their own comp functions.
|
271
|
+
if (!item_a->comp || !item_b->comp)
|
272
|
+
return mongory_value_compare_fail; // Should not happen for valid values
|
273
|
+
|
274
|
+
int cmp_result = item_a->comp(item_a, item_b);
|
275
|
+
if (cmp_result == mongory_value_compare_fail)
|
276
|
+
return mongory_value_compare_fail; // Incomparable elements
|
277
|
+
if (cmp_result != 0) {
|
278
|
+
return cmp_result; // First differing element determines array order.
|
279
|
+
}
|
280
|
+
}
|
281
|
+
return 0; // All elements are equal.
|
282
|
+
}
|
283
|
+
|
284
|
+
/** Wraps a mongory_array. */
|
285
|
+
mongory_value *mongory_value_wrap_a(mongory_memory_pool *pool, struct mongory_array *a_val) {
|
286
|
+
mongory_value *value = mongory_value_new(pool);
|
287
|
+
if (!value)
|
288
|
+
return NULL;
|
289
|
+
value->type = MONGORY_TYPE_ARRAY;
|
290
|
+
value->data.a = a_val;
|
291
|
+
value->comp = mongory_value_array_compare;
|
292
|
+
value->to_str = mongory_value_array_to_str;
|
293
|
+
return value;
|
294
|
+
}
|
295
|
+
|
296
|
+
/** Compares two MONGORY_TYPE_TABLE values. Currently unsupported (always fails). */
|
297
|
+
static inline int mongory_value_table_compare(mongory_value *a, mongory_value *b) {
|
298
|
+
(void)a; // Unused.
|
299
|
+
(void)b; // Unused.
|
300
|
+
// True table comparison is complex because the order of keys is not
|
301
|
+
// significant. It would require iterating over keys of one table and checking
|
302
|
+
// for their presence and value equality in the other. This is not
|
303
|
+
// implemented. Therefore, tables are considered incomparable.
|
304
|
+
return mongory_value_compare_fail;
|
305
|
+
}
|
306
|
+
|
307
|
+
/** Wraps a mongory_table. */
|
308
|
+
mongory_value *mongory_value_wrap_t(mongory_memory_pool *pool, struct mongory_table *t_val) {
|
309
|
+
mongory_value *value = mongory_value_new(pool);
|
310
|
+
if (!value)
|
311
|
+
return NULL;
|
312
|
+
value->type = MONGORY_TYPE_TABLE;
|
313
|
+
value->data.t = t_val;
|
314
|
+
value->comp = mongory_value_table_compare;
|
315
|
+
value->to_str = mongory_value_table_to_str;
|
316
|
+
return value;
|
317
|
+
}
|
318
|
+
|
319
|
+
/** Generic comparison for types not otherwise handled (e.g. POINTER, REGEX, UNSUPPORTED). Always fails. */
|
320
|
+
static inline int mongory_value_generic_ptr_compare(mongory_value *a, mongory_value *b) {
|
321
|
+
(void)a; // Unused.
|
322
|
+
(void)b; // Unused.
|
323
|
+
// Generic pointers are generally not comparable in a meaningful way beyond identity.
|
324
|
+
return mongory_value_compare_fail;
|
325
|
+
}
|
326
|
+
|
327
|
+
/** Wraps an unsupported/unknown type pointer. */
|
328
|
+
mongory_value *mongory_value_wrap_u(mongory_memory_pool *pool, void *u_val) {
|
329
|
+
mongory_value *value = mongory_value_new(pool);
|
330
|
+
if (!value)
|
331
|
+
return NULL;
|
332
|
+
value->type = MONGORY_TYPE_UNSUPPORTED;
|
333
|
+
value->data.u = u_val;
|
334
|
+
value->comp = mongory_value_generic_ptr_compare; // Unsupported types are not comparable.
|
335
|
+
value->to_str = mongory_value_generic_ptr_to_str;
|
336
|
+
return value;
|
337
|
+
}
|
338
|
+
|
339
|
+
static char *mongory_value_regex_to_str(mongory_value *value, mongory_memory_pool *pool) {
|
340
|
+
char *str = mongory_internal_regex_adapter.stringify_func(pool, value);
|
341
|
+
if (!str)
|
342
|
+
return NULL;
|
343
|
+
return str;
|
344
|
+
}
|
345
|
+
|
346
|
+
/** Wraps a regex type pointer. */
|
347
|
+
mongory_value *mongory_value_wrap_regex(mongory_memory_pool *pool, void *regex_val) {
|
348
|
+
mongory_value *value = mongory_value_new(pool);
|
349
|
+
if (!value)
|
350
|
+
return NULL;
|
351
|
+
value->type = MONGORY_TYPE_REGEX;
|
352
|
+
value->data.regex = regex_val;
|
353
|
+
value->comp = mongory_value_generic_ptr_compare; // Regex values are not directly comparable.
|
354
|
+
value->to_str = mongory_value_regex_to_str;
|
355
|
+
return value;
|
356
|
+
}
|
357
|
+
|
358
|
+
/** Wraps a generic void pointer. */
|
359
|
+
mongory_value *mongory_value_wrap_ptr(mongory_memory_pool *pool, void *ptr_val) {
|
360
|
+
mongory_value *value = mongory_value_new(pool);
|
361
|
+
if (!value)
|
362
|
+
return NULL;
|
363
|
+
value->type = MONGORY_TYPE_POINTER;
|
364
|
+
value->data.ptr = ptr_val;
|
365
|
+
value->comp = mongory_value_generic_ptr_compare; // Generic pointers are not comparable.
|
366
|
+
value->to_str = mongory_value_generic_ptr_to_str;
|
367
|
+
return value;
|
368
|
+
}
|
369
|
+
|
370
|
+
// ============================================================================
|
371
|
+
// Stringify Functions
|
372
|
+
//
|
373
|
+
// Each data type has a corresponding `to_str` function that converts the
|
374
|
+
// value to a string representation (often JSON-like) and appends it to a
|
375
|
+
// `mongory_string_buffer`.
|
376
|
+
// ============================================================================
|
377
|
+
|
378
|
+
static char *mongory_value_null_to_str(mongory_value *value, mongory_memory_pool *pool) {
|
379
|
+
(void)value;
|
380
|
+
return mongory_string_cpy(pool, "null");
|
381
|
+
}
|
382
|
+
|
383
|
+
static char *mongory_value_bool_to_str(mongory_value *value, mongory_memory_pool *pool) {
|
384
|
+
if (!MONGORY_VALIDATE_PTR(pool, &value->data.b)) {
|
385
|
+
return NULL;
|
386
|
+
}
|
387
|
+
return mongory_string_cpy(pool, value->data.b ? "true" : "false");
|
388
|
+
}
|
389
|
+
|
390
|
+
static char *mongory_value_int_to_str(mongory_value *value, mongory_memory_pool *pool) {
|
391
|
+
if (!MONGORY_VALIDATE_PTR(pool, &value->data.i)) {
|
392
|
+
return NULL;
|
393
|
+
}
|
394
|
+
return mongory_string_cpyf(pool, "%lld", (long long)value->data.i);
|
395
|
+
}
|
396
|
+
|
397
|
+
static char *mongory_value_double_to_str(mongory_value *value, mongory_memory_pool *pool) {
|
398
|
+
if (!MONGORY_VALIDATE_PTR(pool, &value->data.d)) {
|
399
|
+
return NULL;
|
400
|
+
}
|
401
|
+
return mongory_string_cpyf(pool, "%f", value->data.d);
|
402
|
+
}
|
403
|
+
|
404
|
+
static char *mongory_value_string_to_str(mongory_value *value, mongory_memory_pool *pool) {
|
405
|
+
if (!MONGORY_VALIDATE_PTR(pool, value->data.s)) {
|
406
|
+
return NULL;
|
407
|
+
}
|
408
|
+
return mongory_string_cpyf(pool, "\"%s\"", value->data.s);
|
409
|
+
}
|
410
|
+
|
411
|
+
typedef struct mongory_value_container_to_str_ctx {
|
412
|
+
size_t count;
|
413
|
+
size_t total;
|
414
|
+
mongory_string_buffer *buffer; /**< The string buffer to append to. */
|
415
|
+
} mongory_value_container_to_str_ctx;
|
416
|
+
|
417
|
+
static bool mongory_value_array_to_str_each(mongory_value *value, void *ctx) {
|
418
|
+
mongory_value_container_to_str_ctx *context = (mongory_value_container_to_str_ctx *)ctx;
|
419
|
+
mongory_string_buffer *buffer = context->buffer;
|
420
|
+
mongory_memory_pool *pool = buffer->pool;
|
421
|
+
MONGORY_VALIDATE_PTR(pool, value);
|
422
|
+
MONGORY_VALIDATE_PTR(pool, value->to_str);
|
423
|
+
if (pool->error != NULL) {
|
424
|
+
return false;
|
425
|
+
}
|
426
|
+
char *str = value->to_str(value, pool);
|
427
|
+
if (!str)
|
428
|
+
return false;
|
429
|
+
mongory_string_buffer_append(buffer, str);
|
430
|
+
context->count++;
|
431
|
+
if (context->count < context->total) {
|
432
|
+
mongory_string_buffer_append(buffer, ",");
|
433
|
+
}
|
434
|
+
return true;
|
435
|
+
}
|
436
|
+
|
437
|
+
static char *mongory_value_array_to_str(mongory_value *value, mongory_memory_pool *pool) {
|
438
|
+
mongory_string_buffer *buffer = mongory_string_buffer_new(pool);
|
439
|
+
if (!buffer)
|
440
|
+
return NULL;
|
441
|
+
mongory_array *array = value->data.a;
|
442
|
+
MONGORY_VALIDATE_PTR(pool, array);
|
443
|
+
MONGORY_VALIDATE_PTR(pool, array->each);
|
444
|
+
MONGORY_VALIDATE_PTR(pool, &array->count);
|
445
|
+
if (pool->error != NULL) {
|
446
|
+
return NULL;
|
447
|
+
}
|
448
|
+
mongory_string_buffer_append(buffer, "[");
|
449
|
+
mongory_value_container_to_str_ctx ctx = {.count = 0, .total = array->count, .buffer = buffer};
|
450
|
+
array->each(array, &ctx, mongory_value_array_to_str_each);
|
451
|
+
mongory_string_buffer_append(buffer, "]");
|
452
|
+
return mongory_string_buffer_cstr(buffer);
|
453
|
+
}
|
454
|
+
|
455
|
+
static bool mongory_value_table_to_str_each(char *key, mongory_value *value, void *ctx) {
|
456
|
+
mongory_value_container_to_str_ctx *context = (mongory_value_container_to_str_ctx *)ctx;
|
457
|
+
mongory_string_buffer *buffer = context->buffer;
|
458
|
+
mongory_memory_pool *pool = buffer->pool;
|
459
|
+
MONGORY_VALIDATE_PTR(pool, key);
|
460
|
+
MONGORY_VALIDATE_PTR(pool, value);
|
461
|
+
MONGORY_VALIDATE_PTR(pool, value->to_str);
|
462
|
+
if (pool->error != NULL) {
|
463
|
+
return false;
|
464
|
+
}
|
465
|
+
mongory_string_buffer_appendf(buffer, "\"%s\":", key);
|
466
|
+
char *str = value->to_str(value, buffer->pool);
|
467
|
+
if (!str)
|
468
|
+
return false;
|
469
|
+
mongory_string_buffer_append(buffer, str);
|
470
|
+
context->count++;
|
471
|
+
if (context->count < context->total) {
|
472
|
+
mongory_string_buffer_append(buffer, ",");
|
473
|
+
}
|
474
|
+
return true;
|
475
|
+
}
|
476
|
+
|
477
|
+
static char *mongory_value_table_to_str(mongory_value *value, mongory_memory_pool *pool) {
|
478
|
+
mongory_string_buffer *buffer = mongory_string_buffer_new(pool);
|
479
|
+
if (!buffer)
|
480
|
+
return NULL;
|
481
|
+
mongory_table *table = value->data.t;
|
482
|
+
if (!MONGORY_VALIDATE_PTR(pool, table) || !MONGORY_VALIDATE_PTR(pool, table->each) ||
|
483
|
+
!MONGORY_VALIDATE_PTR(pool, &table->count)) {
|
484
|
+
return NULL;
|
485
|
+
}
|
486
|
+
if (pool->error != NULL) {
|
487
|
+
return NULL;
|
488
|
+
}
|
489
|
+
mongory_string_buffer_append(buffer, "{");
|
490
|
+
mongory_value_container_to_str_ctx ctx = {.count = 0, .total = table->count, .buffer = buffer};
|
491
|
+
table->each(table, &ctx, mongory_value_table_to_str_each);
|
492
|
+
mongory_string_buffer_append(buffer, "}");
|
493
|
+
return mongory_string_buffer_cstr(buffer);
|
494
|
+
}
|
495
|
+
|
496
|
+
static char *mongory_value_generic_ptr_to_str(mongory_value *value, mongory_memory_pool *pool) {
|
497
|
+
if (!MONGORY_VALIDATE_PTR(pool, value->data.ptr))
|
498
|
+
return NULL;
|
499
|
+
return mongory_string_cpyf(pool, "%p", value->data.ptr);
|
500
|
+
}
|
@@ -0,0 +1,164 @@
|
|
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"
|
16
|
+
#include "../foundations/utils.h" // For mongory_try_parse_int
|
17
|
+
#include "base_matcher.h" // For mongory_matcher_base_new
|
18
|
+
#include "compare_matcher.h" // For mongory_matcher_equal_new
|
19
|
+
#include "composite_matcher.h" // For mongory_matcher_composite_new, $elemMatch, table_cond_new, or_match
|
20
|
+
#include "literal_matcher.h" // Potentially used by table_cond_new
|
21
|
+
#include "mongory-core/foundations/table.h"
|
22
|
+
#include "mongory-core/foundations/value.h"
|
23
|
+
#include <mongory-core.h> // General include
|
24
|
+
#include <stdio.h> // For NULL
|
25
|
+
#include <string.h> // For strcmp
|
26
|
+
|
27
|
+
/**
|
28
|
+
* @struct mongory_matcher_array_record_parse_table_context
|
29
|
+
* @brief Context used when parsing a table condition for array record matching.
|
30
|
+
*
|
31
|
+
* Helps separate parts of the condition table:
|
32
|
+
* - `parsed_table`: For explicit operators (e.g., $size, $all if implemented)
|
33
|
+
* or indexed conditions.
|
34
|
+
* - `elem_match_table`: For conditions that should apply to individual elements
|
35
|
+
* via an implicit or explicit $elemMatch.
|
36
|
+
*/
|
37
|
+
typedef struct mongory_matcher_array_record_parse_table_context {
|
38
|
+
mongory_table *parsed_table; /**< Stores operator or indexed conditions. */
|
39
|
+
mongory_table *elem_match_table; /**< Stores conditions for element matching. */
|
40
|
+
} mongory_matcher_array_record_parse_table_context;
|
41
|
+
|
42
|
+
/**
|
43
|
+
* @brief Callback to parse a condition table for array matching.
|
44
|
+
*
|
45
|
+
* Iterates through the main condition table:
|
46
|
+
* - If `key` is "$elemMatch" and `value` is a table, its contents are added to
|
47
|
+
* `context->elem_match_table`.
|
48
|
+
* - If `key` starts with '$' (another operator) or is numeric (array index),
|
49
|
+
* it's added to `context->parsed_table`.
|
50
|
+
* - Otherwise (plain field name), it's considered part of an implicit element
|
51
|
+
* match and added to `context->elem_match_table`.
|
52
|
+
*
|
53
|
+
* @param key Current key in the condition table.
|
54
|
+
* @param value Current value for the key.
|
55
|
+
* @param acc Pointer to `mongory_matcher_array_record_parse_table_context`.
|
56
|
+
* @return Always true to continue.
|
57
|
+
*/
|
58
|
+
static inline bool mongory_matcher_array_record_parse_table_foreach(char *key, mongory_value *value, void *acc) {
|
59
|
+
mongory_matcher_array_record_parse_table_context *context = (mongory_matcher_array_record_parse_table_context *)acc;
|
60
|
+
mongory_table *parsed_table_for_operators = context->parsed_table;
|
61
|
+
mongory_table *table_for_elem_match_conditions = context->elem_match_table;
|
62
|
+
|
63
|
+
if (strcmp(key, "$elemMatch") == 0 && value->type == MONGORY_TYPE_TABLE && value->data.t != NULL) {
|
64
|
+
// Explicit $elemMatch: iterate its sub-table and add to elem_match_table
|
65
|
+
mongory_table_merge(table_for_elem_match_conditions, value->data.t);
|
66
|
+
} else if (*key == '$' || mongory_try_parse_int(key, NULL)) {
|
67
|
+
// Operator (like $size) or numeric index: goes to parsed_table
|
68
|
+
parsed_table_for_operators->set(parsed_table_for_operators, key, value);
|
69
|
+
} else {
|
70
|
+
// Regular field name: implies a condition on elements, goes to
|
71
|
+
// elem_match_table
|
72
|
+
table_for_elem_match_conditions->set(table_for_elem_match_conditions, key, value);
|
73
|
+
}
|
74
|
+
return true;
|
75
|
+
}
|
76
|
+
|
77
|
+
/**
|
78
|
+
* @brief Parses a table `condition` intended for array matching.
|
79
|
+
*
|
80
|
+
* Separates the condition into parts for direct array operations (like $size)
|
81
|
+
* and parts for matching individual elements (implicit or explicit $elemMatch).
|
82
|
+
* If any element-matching conditions are found, they are grouped under an
|
83
|
+
* "$elemMatch" key in the returned `parsed_table`.
|
84
|
+
*
|
85
|
+
* @param condition The `mongory_value` (must be a table) to parse.
|
86
|
+
* @return A new `mongory_value` (table) containing the parsed and restructured
|
87
|
+
* condition. Returns NULL if input is not a table or on allocation failure.
|
88
|
+
*/
|
89
|
+
static inline mongory_value *mongory_matcher_array_record_parse_table(mongory_value *condition) {
|
90
|
+
if (!condition->data.t || !condition->pool) {
|
91
|
+
mongory_error *error = MG_ALLOC_PTR(condition->pool, mongory_error);
|
92
|
+
if (!error) {
|
93
|
+
condition->pool->error = &MONGORY_ALLOC_ERROR;
|
94
|
+
return NULL;
|
95
|
+
}
|
96
|
+
error->type = MONGORY_ERROR_INVALID_TYPE;
|
97
|
+
error->message = "Expected condition to be a table, got a non-table value";
|
98
|
+
condition->pool->error = error; // TODO: This is a hack, we should use a better error handling mechanism
|
99
|
+
return NULL; // Invalid input
|
100
|
+
}
|
101
|
+
mongory_memory_pool *pool = condition->pool;
|
102
|
+
mongory_table *parsed_table = mongory_table_new(pool);
|
103
|
+
mongory_table *elem_match_sub_table = mongory_table_new(pool);
|
104
|
+
|
105
|
+
if (!parsed_table || !elem_match_sub_table) {
|
106
|
+
// Cleanup if one allocation succeeded but other failed? Pool should handle.
|
107
|
+
return NULL;
|
108
|
+
}
|
109
|
+
|
110
|
+
mongory_matcher_array_record_parse_table_context parse_ctx = {parsed_table, elem_match_sub_table};
|
111
|
+
mongory_table *original_condition_table = condition->data.t;
|
112
|
+
|
113
|
+
original_condition_table->each(original_condition_table, &parse_ctx,
|
114
|
+
mongory_matcher_array_record_parse_table_foreach);
|
115
|
+
|
116
|
+
if (elem_match_sub_table->count > 0) {
|
117
|
+
// If there were conditions for element matching, add them as an $elemMatch
|
118
|
+
// clause to the main parsed_table.
|
119
|
+
parsed_table->set(parsed_table, "$elemMatch", mongory_value_wrap_t(pool, elem_match_sub_table));
|
120
|
+
}
|
121
|
+
// If elem_match_sub_table is empty, it's not added. The original parsed_table
|
122
|
+
// (which might contain $size etc.) is returned.
|
123
|
+
return mongory_value_wrap_t(pool, parsed_table);
|
124
|
+
}
|
125
|
+
|
126
|
+
/**
|
127
|
+
* @brief Main constructor for `mongory_matcher_array_record_new`.
|
128
|
+
*
|
129
|
+
* Constructs a two-part matcher (often combined with OR):
|
130
|
+
* - `left`: Handles element-wise conditions (like $elemMatch from various
|
131
|
+
* condition types).
|
132
|
+
* - `right`: Handles whole-array equality if the original `condition` was an
|
133
|
+
* array.
|
134
|
+
*
|
135
|
+
* If `right` is NULL (i.e., original `condition` was not an array), only the
|
136
|
+
* `left` matcher is returned. Otherwise, a composite OR matcher is created
|
137
|
+
* with `left` and `right` as children.
|
138
|
+
*
|
139
|
+
* @param pool Memory pool.
|
140
|
+
* @param condition The condition to apply to arrays.
|
141
|
+
* @return The constructed array_record_matcher, or NULL on failure.
|
142
|
+
*/
|
143
|
+
mongory_matcher *mongory_matcher_array_record_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx) {
|
144
|
+
if (!condition)
|
145
|
+
return NULL;
|
146
|
+
switch (condition->type) {
|
147
|
+
case MONGORY_TYPE_TABLE:
|
148
|
+
return mongory_matcher_table_cond_new(pool,
|
149
|
+
mongory_matcher_array_record_parse_table(condition),
|
150
|
+
extern_ctx
|
151
|
+
);
|
152
|
+
case MONGORY_TYPE_ARRAY:
|
153
|
+
return mongory_matcher_or_new(pool, MG_ARRAY_WRAP(pool, 2,
|
154
|
+
MG_TABLE_WRAP(pool, 1, "$eq", condition),
|
155
|
+
MG_TABLE_WRAP(pool, 1, "$elemMatch",
|
156
|
+
MG_TABLE_WRAP(pool, 1, "$eq", condition)
|
157
|
+
)
|
158
|
+
), extern_ctx);
|
159
|
+
case MONGORY_TYPE_REGEX:
|
160
|
+
return mongory_matcher_elem_match_new(pool, MG_TABLE_WRAP(pool, 1, "$regex", condition), extern_ctx);
|
161
|
+
default: // Literals (string, int, bool, etc.)
|
162
|
+
return mongory_matcher_elem_match_new(pool, MG_TABLE_WRAP(pool, 1, "$eq", condition), extern_ctx);
|
163
|
+
}
|
164
|
+
}
|