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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/CHANGELOG.md +46 -0
  4. data/README.md +83 -176
  5. data/Rakefile +77 -0
  6. data/SUBMODULE_INTEGRATION.md +325 -0
  7. data/docs/advanced_usage.md +40 -0
  8. data/docs/clang_bridge.md +69 -0
  9. data/docs/field_names.md +30 -0
  10. data/docs/migration.md +30 -0
  11. data/docs/performance.md +61 -0
  12. data/examples/benchmark.rb +98 -19
  13. data/ext/mongory_ext/extconf.rb +91 -0
  14. data/ext/mongory_ext/mongory-core/LICENSE +3 -0
  15. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/array.h +105 -0
  16. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/config.h +206 -0
  17. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/error.h +82 -0
  18. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/memory_pool.h +95 -0
  19. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/table.h +108 -0
  20. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/value.h +175 -0
  21. data/ext/mongory_ext/mongory-core/include/mongory-core/matchers/matcher.h +76 -0
  22. data/ext/mongory_ext/mongory-core/include/mongory-core.h +12 -0
  23. data/ext/mongory_ext/mongory-core/src/foundations/array.c +246 -0
  24. data/ext/mongory_ext/mongory-core/src/foundations/array_private.h +18 -0
  25. data/ext/mongory_ext/mongory-core/src/foundations/config.c +406 -0
  26. data/ext/mongory_ext/mongory-core/src/foundations/config_private.h +30 -0
  27. data/ext/mongory_ext/mongory-core/src/foundations/error.c +30 -0
  28. data/ext/mongory_ext/mongory-core/src/foundations/memory_pool.c +298 -0
  29. data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.c +65 -0
  30. data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.h +49 -0
  31. data/ext/mongory_ext/mongory-core/src/foundations/table.c +458 -0
  32. data/ext/mongory_ext/mongory-core/src/foundations/value.c +459 -0
  33. data/ext/mongory_ext/mongory-core/src/matchers/array_record_matcher.c +309 -0
  34. data/ext/mongory_ext/mongory-core/src/matchers/array_record_matcher.h +47 -0
  35. data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.c +161 -0
  36. data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.h +115 -0
  37. data/ext/mongory_ext/mongory-core/src/matchers/compare_matcher.c +210 -0
  38. data/ext/mongory_ext/mongory-core/src/matchers/compare_matcher.h +83 -0
  39. data/ext/mongory_ext/mongory-core/src/matchers/composite_matcher.c +539 -0
  40. data/ext/mongory_ext/mongory-core/src/matchers/composite_matcher.h +125 -0
  41. data/ext/mongory_ext/mongory-core/src/matchers/existance_matcher.c +144 -0
  42. data/ext/mongory_ext/mongory-core/src/matchers/existance_matcher.h +48 -0
  43. data/ext/mongory_ext/mongory-core/src/matchers/external_matcher.c +121 -0
  44. data/ext/mongory_ext/mongory-core/src/matchers/external_matcher.h +46 -0
  45. data/ext/mongory_ext/mongory-core/src/matchers/inclusion_matcher.c +199 -0
  46. data/ext/mongory_ext/mongory-core/src/matchers/inclusion_matcher.h +46 -0
  47. data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.c +334 -0
  48. data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.h +97 -0
  49. data/ext/mongory_ext/mongory-core/src/matchers/matcher.c +198 -0
  50. data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.c +107 -0
  51. data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.h +50 -0
  52. data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.c +55 -0
  53. data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.h +23 -0
  54. data/ext/mongory_ext/mongory_ext.c +635 -0
  55. data/lib/mongory/c_query_builder.rb +44 -0
  56. data/lib/mongory/query_builder.rb +8 -0
  57. data/lib/mongory/utils/context.rb +7 -0
  58. data/lib/mongory/version.rb +1 -1
  59. data/lib/mongory.rb +7 -0
  60. data/mongory.gemspec +10 -4
  61. data/scripts/build_with_core.sh +292 -0
  62. metadata +69 -4
@@ -0,0 +1,635 @@
1
+ /**
2
+ * @file mongory_ext.c
3
+ * @brief Ruby C extension wrapper for mongory-core
4
+ *
5
+ * This file provides Ruby bindings for the mongory-core C library,
6
+ * allowing Ruby applications to use high-performance C-based matching
7
+ * operations while maintaining the elegant Ruby DSL.
8
+ */
9
+ #include "mongory-core.h"
10
+ #include <ruby.h>
11
+ #include <ruby/encoding.h>
12
+ #include <string.h>
13
+
14
+ // Ruby module and class definitions
15
+ static VALUE mMongory;
16
+ static VALUE cMongoryMatcher;
17
+ static VALUE cMongoryMatcherContext;
18
+ static VALUE mMongoryMatchers;
19
+
20
+ // Error classes
21
+ static VALUE eMongoryError;
22
+ static VALUE eMongoryTypeError;
23
+
24
+ // Converter instance
25
+ static VALUE inMongoryDataConverter;
26
+ static VALUE inMongoryConditionConverter;
27
+
28
+ // Matcher wrapper structure
29
+ typedef struct ruby_mongory_matcher_t {
30
+ mongory_matcher *matcher;
31
+ mongory_value *condition;
32
+ mongory_memory_pool *pool;
33
+ mongory_memory_pool *scratch_pool;
34
+ mongory_table *string_map;
35
+ mongory_table *symbol_map;
36
+ mongory_array *mark_list;
37
+ bool trace_enabled;
38
+ VALUE ctx;
39
+ } ruby_mongory_matcher_t;
40
+
41
+ typedef struct ruby_mongory_memory_pool_t {
42
+ mongory_memory_pool base;
43
+ ruby_mongory_matcher_t *owner;
44
+ } ruby_mongory_memory_pool_t;
45
+
46
+ typedef struct ruby_mongory_table_t {
47
+ mongory_table base;
48
+ VALUE rb_hash;
49
+ ruby_mongory_matcher_t *owner;
50
+ } ruby_mongory_table_t;
51
+
52
+ typedef struct ruby_mongory_array_t {
53
+ mongory_array base;
54
+ VALUE rb_array;
55
+ ruby_mongory_matcher_t *owner;
56
+ } ruby_mongory_array_t;
57
+
58
+ typedef struct {
59
+ mongory_table *table;
60
+ mongory_memory_pool *pool;
61
+ } hash_conv_ctx;
62
+
63
+ // Forward declarations
64
+ static void ruby_mongory_matcher_mark(void *ptr);
65
+ static void ruby_mongory_matcher_free(void *ptr);
66
+ mongory_value *ruby_to_mongory_value_deep(mongory_memory_pool *pool, VALUE rb_value);
67
+ mongory_value *ruby_to_mongory_value_shallow(mongory_memory_pool *pool, VALUE rb_value);
68
+ mongory_value *ruby_mongory_table_wrap(mongory_memory_pool *pool, VALUE rb_hash);
69
+ mongory_value *ruby_mongory_array_wrap(mongory_memory_pool *pool, VALUE rb_array);
70
+ static VALUE cache_fetch_string(ruby_mongory_matcher_t *owner, const char *key);
71
+ static VALUE cache_fetch_symbol(ruby_mongory_matcher_t *owner, const char *key);
72
+ static ruby_mongory_memory_pool_t *ruby_mongory_memory_pool_new();
73
+ static void rb_mongory_matcher_parse_argv(ruby_mongory_matcher_t *wrapper, int argc, VALUE *argv);
74
+
75
+ static const rb_data_type_t ruby_mongory_matcher_type = {
76
+ .wrap_struct_name = "mongory_matcher",
77
+ .function = {
78
+ .dmark = ruby_mongory_matcher_mark,
79
+ .dfree = ruby_mongory_matcher_free,
80
+ .dsize = NULL,
81
+ },
82
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
83
+ };
84
+
85
+ /**
86
+ * Ruby method implementations
87
+ */
88
+
89
+ // Mongory::CMatcher.new(condition)
90
+ static VALUE ruby_mongory_matcher_new(int argc, VALUE *argv, VALUE class) {
91
+ ruby_mongory_memory_pool_t *matcher_pool = ruby_mongory_memory_pool_new();
92
+ ruby_mongory_memory_pool_t *scratch_pool = ruby_mongory_memory_pool_new();
93
+ ruby_mongory_matcher_t *wrapper = ALLOC(ruby_mongory_matcher_t);
94
+ wrapper->pool = &matcher_pool->base;
95
+ wrapper->scratch_pool = &scratch_pool->base;
96
+ wrapper->string_map = mongory_table_new(wrapper->pool);
97
+ wrapper->symbol_map = mongory_table_new(wrapper->pool);
98
+ wrapper->mark_list = mongory_array_new(wrapper->pool);
99
+ wrapper->trace_enabled = false;
100
+ matcher_pool->owner = wrapper;
101
+ scratch_pool->owner = wrapper;
102
+ rb_mongory_matcher_parse_argv(wrapper, argc, argv);
103
+
104
+ mongory_matcher *matcher = mongory_matcher_new(wrapper->pool, wrapper->condition, wrapper->ctx);
105
+ if (wrapper->pool->error) {
106
+ rb_raise(eMongoryTypeError, "Failed to create matcher: %s", wrapper->pool->error->message);
107
+ return Qnil;
108
+ }
109
+
110
+ wrapper->matcher = matcher;
111
+ return TypedData_Wrap_Struct(class, &ruby_mongory_matcher_type, wrapper);
112
+ }
113
+
114
+ static void rb_mongory_matcher_parse_argv(ruby_mongory_matcher_t *wrapper, int argc, VALUE *argv) {
115
+ VALUE condition, kw_hash;
116
+ rb_scan_args(argc, argv, "1:", &condition, &kw_hash);
117
+ const ID ctx_id[1] = { rb_intern("context") };
118
+ VALUE kw_vals[1] = { Qundef };
119
+ if (kw_hash != Qnil) {
120
+ rb_get_kwargs(kw_hash, ctx_id, 1, 0, kw_vals);
121
+ }
122
+ if (kw_vals[0] != Qundef) {
123
+ wrapper->ctx = kw_vals[0];
124
+ } else {
125
+ wrapper->ctx = rb_funcall(cMongoryMatcherContext, rb_intern("new"), 0);
126
+ }
127
+ VALUE converted_condition = rb_funcall(inMongoryConditionConverter, rb_intern("convert"), 1, condition);
128
+ wrapper->condition = ruby_to_mongory_value_deep(wrapper->pool, converted_condition);
129
+ wrapper->mark_list->push(wrapper->mark_list, wrapper->condition);
130
+ mongory_value *store_ctx = mongory_value_wrap_u(wrapper->pool, (void *)wrapper->ctx);
131
+ store_ctx->origin = (void *)wrapper->ctx;
132
+ wrapper->mark_list->push(wrapper->mark_list, store_ctx);
133
+ }
134
+
135
+ // Mongory::CMatcher#match(data)
136
+ static VALUE ruby_mongory_matcher_match(VALUE self, VALUE data) {
137
+ ruby_mongory_matcher_t *wrapper;
138
+ TypedData_Get_Struct(self, ruby_mongory_matcher_t, &ruby_mongory_matcher_type, wrapper);
139
+ mongory_value *data_value;
140
+ if (wrapper->trace_enabled) {
141
+ data_value = ruby_to_mongory_value_deep(wrapper->scratch_pool, data);
142
+ } else {
143
+ data_value = ruby_to_mongory_value_shallow(wrapper->scratch_pool, data);
144
+ }
145
+ bool result = mongory_matcher_match(wrapper->matcher, data_value);
146
+ if (!wrapper->trace_enabled) {
147
+ wrapper->scratch_pool->reset(wrapper->scratch_pool->ctx);
148
+ }
149
+ return result ? Qtrue : Qfalse;
150
+ }
151
+
152
+ // Mongory::CMatcher#explain
153
+ static VALUE ruby_mongory_matcher_explain(VALUE self) {
154
+ ruby_mongory_matcher_t *wrapper;
155
+ TypedData_Get_Struct(self, ruby_mongory_matcher_t, &ruby_mongory_matcher_type, wrapper);
156
+ mongory_matcher_explain(wrapper->matcher, wrapper->scratch_pool);
157
+ wrapper->scratch_pool->reset(wrapper->scratch_pool->ctx);
158
+ return Qnil;
159
+ }
160
+
161
+ // Mongory::CMatcher#trace(data)
162
+ static VALUE ruby_mongory_matcher_trace(VALUE self, VALUE data) {
163
+ ruby_mongory_matcher_t *wrapper;
164
+ TypedData_Get_Struct(self, ruby_mongory_matcher_t, &ruby_mongory_matcher_type, wrapper);
165
+ mongory_value *data_value = ruby_to_mongory_value_deep(wrapper->scratch_pool, data);
166
+ bool matched = mongory_matcher_trace(wrapper->matcher, data_value);
167
+ wrapper->scratch_pool->reset(wrapper->scratch_pool->ctx);
168
+ return matched ? Qtrue : Qfalse;
169
+ }
170
+
171
+ // Mongory::CMatcher#enable_trace
172
+ static VALUE ruby_mongory_matcher_enable_trace(VALUE self) {
173
+ ruby_mongory_matcher_t *wrapper;
174
+ TypedData_Get_Struct(self, ruby_mongory_matcher_t, &ruby_mongory_matcher_type, wrapper);
175
+ mongory_matcher_enable_trace(wrapper->matcher, wrapper->scratch_pool);
176
+ wrapper->trace_enabled = true;
177
+ return Qnil;
178
+ }
179
+
180
+ // Mongory::CMatcher#disable_trace
181
+ static VALUE ruby_mongory_matcher_disable_trace(VALUE self) {
182
+ ruby_mongory_matcher_t *wrapper;
183
+ TypedData_Get_Struct(self, ruby_mongory_matcher_t, &ruby_mongory_matcher_type, wrapper);
184
+ mongory_matcher_disable_trace(wrapper->matcher);
185
+ wrapper->scratch_pool->reset(wrapper->scratch_pool->ctx);
186
+ wrapper->trace_enabled = false;
187
+ return Qnil;
188
+ }
189
+
190
+ // Mongory::CMatcher#print_trace
191
+ static VALUE ruby_mongory_matcher_print_trace(VALUE self) {
192
+ ruby_mongory_matcher_t *wrapper;
193
+ TypedData_Get_Struct(self, ruby_mongory_matcher_t, &ruby_mongory_matcher_type, wrapper);
194
+ mongory_matcher_print_trace(wrapper->matcher);
195
+ return Qnil;
196
+ }
197
+
198
+ // Mongory::CMatcher#condition
199
+ static VALUE ruby_mongory_matcher_condition(VALUE self) {
200
+ ruby_mongory_matcher_t *wrapper;
201
+ TypedData_Get_Struct(self, ruby_mongory_matcher_t, &ruby_mongory_matcher_type, wrapper);
202
+ return (VALUE)wrapper->condition->origin;
203
+ }
204
+
205
+ // Mongory::CMatcher#context
206
+ static VALUE ruby_mongory_matcher_context(VALUE self) {
207
+ ruby_mongory_matcher_t *wrapper;
208
+ TypedData_Get_Struct(self, ruby_mongory_matcher_t, &ruby_mongory_matcher_type, wrapper);
209
+ return wrapper->ctx == NULL ? Qnil : wrapper->ctx;
210
+ }
211
+
212
+ /**
213
+ * Create a new memory pool
214
+ */
215
+ static ruby_mongory_memory_pool_t *ruby_mongory_memory_pool_new() {
216
+ ruby_mongory_memory_pool_t *pool = malloc(sizeof(ruby_mongory_memory_pool_t));
217
+ mongory_memory_pool *base = mongory_memory_pool_new();
218
+ memcpy(&pool->base, base, sizeof(mongory_memory_pool));
219
+ free(base);
220
+
221
+ return pool;
222
+ }
223
+
224
+ /**
225
+ * Ruby GC management functions
226
+ */
227
+ static void ruby_mongory_matcher_free(void *ptr) {
228
+ ruby_mongory_matcher_t *wrapper = (ruby_mongory_matcher_t *)ptr;
229
+ mongory_memory_pool *pool = wrapper->pool;
230
+ mongory_memory_pool *scratch_pool = wrapper->scratch_pool;
231
+ pool->free(pool);
232
+ scratch_pool->free(scratch_pool);
233
+ xfree(wrapper);
234
+ }
235
+
236
+ /**
237
+ * GC marking callback for mongory_array
238
+ */
239
+ static bool gc_mark_array_cb(mongory_value *value, void *acc) {
240
+ (void)acc;
241
+ if (value && value->origin) rb_gc_mark((VALUE)value->origin);
242
+ return true;
243
+ }
244
+
245
+ /**
246
+ * GC marking callback for mongory_matcher
247
+ */
248
+ static void ruby_mongory_matcher_mark(void *ptr) {
249
+ ruby_mongory_matcher_t *wrapper = (ruby_mongory_matcher_t *)ptr;
250
+ if (!wrapper) return;
251
+ wrapper->mark_list->each(wrapper->mark_list, NULL, gc_mark_array_cb);
252
+ }
253
+
254
+ /**
255
+ * Helper functions for Ruby/C conversion
256
+ */
257
+
258
+ static mongory_value *ruby_to_mongory_value_primitive(mongory_memory_pool *pool, VALUE rb_value) {
259
+ mongory_value *mg_value = NULL;
260
+ switch (TYPE(rb_value)) {
261
+ case T_NIL:
262
+ mg_value = mongory_value_wrap_n(pool, NULL);
263
+ break;
264
+
265
+ case T_TRUE:
266
+ mg_value = mongory_value_wrap_b(pool, true);
267
+ break;
268
+
269
+ case T_FALSE:
270
+ mg_value = mongory_value_wrap_b(pool, false);
271
+ break;
272
+
273
+ case T_FIXNUM:
274
+ mg_value = mongory_value_wrap_i(pool, rb_num2long_inline(rb_value));
275
+ break;
276
+
277
+ case T_BIGNUM:
278
+ mg_value = mongory_value_wrap_i(pool, rb_num2ll_inline(rb_value));
279
+ break;
280
+
281
+ case T_FLOAT:
282
+ mg_value = mongory_value_wrap_d(pool, rb_num2dbl(rb_value));
283
+ break;
284
+
285
+ case T_STRING:
286
+ mg_value = mongory_value_wrap_s(pool, StringValueCStr(rb_value));
287
+ break;
288
+
289
+ case T_SYMBOL:
290
+ mg_value = mongory_value_wrap_s(pool, (char *)rb_id2name(rb_sym2id(rb_value)));
291
+ break;
292
+
293
+ case T_REGEXP:
294
+ mg_value = mongory_value_wrap_regex(pool, (void *)rb_value);
295
+ break;
296
+ }
297
+ return mg_value;
298
+ }
299
+ // Shallow conversion: Convert Ruby value to mongory_value (fully materialize arrays/tables)
300
+ static mongory_value *ruby_to_mongory_value_shallow_rec(mongory_memory_pool *pool, VALUE rb_value, bool converted) {
301
+ mongory_value *mg_value = ruby_to_mongory_value_primitive(pool, rb_value);
302
+ if (mg_value) {
303
+ mg_value->origin = rb_value;
304
+ return mg_value;
305
+ }
306
+
307
+ switch (TYPE(rb_value)) {
308
+ case T_ARRAY: {
309
+ mg_value = ruby_mongory_array_wrap(pool, rb_value);
310
+ break;
311
+ }
312
+
313
+ case T_HASH: {
314
+ mg_value = ruby_mongory_table_wrap(pool, rb_value);
315
+ break;
316
+ }
317
+
318
+ default:
319
+ if (converted) {
320
+ mg_value = mongory_value_wrap_u(pool, (void *)rb_value);
321
+ break;
322
+ } else {
323
+ VALUE converted_value = rb_funcall(inMongoryDataConverter, rb_intern("convert"), 1, rb_value);
324
+ return ruby_to_mongory_value_shallow_rec(pool, converted_value, true);
325
+ }
326
+ }
327
+ mg_value->origin = rb_value;
328
+ return mg_value;
329
+ }
330
+
331
+ // Shallow conversion: Convert Ruby value to mongory_value (fully materialize arrays/tables)
332
+ mongory_value *ruby_to_mongory_value_shallow(mongory_memory_pool *pool, VALUE rb_value) {
333
+ return ruby_to_mongory_value_shallow_rec(pool, rb_value, false);
334
+ }
335
+
336
+ static int hash_foreach_deep_convert_cb(VALUE key, VALUE val, VALUE ptr) {
337
+ hash_conv_ctx *ctx = (hash_conv_ctx *)ptr;
338
+ ruby_mongory_memory_pool_t *rb_pool = (ruby_mongory_memory_pool_t *)ctx->pool;
339
+ ruby_mongory_matcher_t *owner = rb_pool->owner;
340
+ mongory_table *store_map;
341
+ char *key_str;
342
+ if (SYMBOL_P(key)) {
343
+ key_str = (char *)rb_id2name(SYM2ID(key));
344
+ store_map = owner->symbol_map;
345
+ } else {
346
+ key_str = StringValueCStr(key);
347
+ store_map = owner->string_map;
348
+ }
349
+ mongory_value *store = mongory_value_wrap_u(ctx->pool, NULL);
350
+ store->origin = (void *)key;
351
+ store_map->set(store_map, key_str, store);
352
+ mongory_value *cval = ruby_to_mongory_value_deep(ctx->pool, val);
353
+ ctx->table->set(ctx->table, key_str, cval);
354
+ return ST_CONTINUE;
355
+ }
356
+
357
+ static mongory_value *ruby_to_mongory_value_deep_rec(mongory_memory_pool *pool, VALUE rb_value, bool converted) {
358
+ mongory_value *mg_value = ruby_to_mongory_value_primitive(pool, rb_value);
359
+ if (mg_value) {
360
+ mg_value->origin = rb_value;
361
+ return mg_value;
362
+ }
363
+ switch (TYPE(rb_value)) {
364
+ case T_ARRAY: {
365
+ mongory_array *array = mongory_array_new(pool);
366
+
367
+ for (long i = 0; i < RARRAY_LEN(rb_value); i++) {
368
+ array->push(array, ruby_to_mongory_value_deep(pool, RARRAY_AREF(rb_value, i)));
369
+ }
370
+ mg_value = mongory_value_wrap_a(pool, array);
371
+ break;
372
+ }
373
+
374
+ case T_HASH: {
375
+ mongory_table *table = mongory_table_new(pool);
376
+
377
+ hash_conv_ctx ctx = {table, pool};
378
+ rb_hash_foreach(rb_value, hash_foreach_deep_convert_cb, (VALUE)&ctx);
379
+
380
+ mg_value = mongory_value_wrap_t(pool, table);
381
+ break;
382
+ }
383
+
384
+ default:
385
+ if (converted) {
386
+ mg_value = mongory_value_wrap_u(pool, (void *)rb_value);
387
+ break;
388
+ } else {
389
+ VALUE converted_value = rb_funcall(inMongoryDataConverter, rb_intern("convert"), 1, rb_value);
390
+ mg_value = ruby_to_mongory_value_deep_rec(pool, converted_value, true);
391
+ break;
392
+ }
393
+ }
394
+ mg_value->origin = rb_value;
395
+ return mg_value;
396
+ }
397
+
398
+ mongory_value *ruby_to_mongory_value_deep(mongory_memory_pool *pool, VALUE rb_value) {
399
+ return ruby_to_mongory_value_deep_rec(pool, rb_value, false);
400
+ }
401
+
402
+ mongory_value *ruby_mongory_table_get(mongory_table *self, char *key) {
403
+ ruby_mongory_table_t *table = (ruby_mongory_table_t *)self;
404
+ VALUE rb_hash = table->rb_hash;
405
+ VALUE rb_value = Qundef;
406
+
407
+ // Use cached Ruby String key if possible
408
+ VALUE key_str = cache_fetch_string(table->owner, key);
409
+ rb_value = rb_hash_lookup2(rb_hash, key_str, Qundef);
410
+
411
+ if (rb_value == Qundef) {
412
+ // Fallback to Symbol key, using cache
413
+ VALUE key_sym = cache_fetch_symbol(table->owner, key);
414
+ rb_value = rb_hash_lookup2(rb_hash, key_sym, Qundef);
415
+ }
416
+
417
+ if (rb_value == Qundef) {
418
+ return NULL;
419
+ }
420
+ return ruby_to_mongory_value_shallow(table->base.pool, rb_value);
421
+ }
422
+
423
+ mongory_value *ruby_mongory_table_wrap(mongory_memory_pool *pool, VALUE rb_hash) {
424
+ ruby_mongory_table_t *table = MG_ALLOC_PTR(pool, ruby_mongory_table_t);
425
+ ruby_mongory_memory_pool_t *rb_pool = (ruby_mongory_memory_pool_t *)pool;
426
+ table->base.pool = pool;
427
+ table->base.get = ruby_mongory_table_get;
428
+ table->rb_hash = rb_hash;
429
+ table->base.count = RHASH_SIZE(rb_hash);
430
+ table->owner = rb_pool->owner;
431
+ return mongory_value_wrap_t(pool, &table->base);
432
+ }
433
+
434
+ static mongory_value *ruby_mongory_array_get(mongory_array *self, size_t index) {
435
+ ruby_mongory_array_t *array = (ruby_mongory_array_t *)self;
436
+ VALUE rb_array = array->rb_array;
437
+ if (index >= (size_t)RARRAY_LEN(rb_array)) {
438
+ return NULL;
439
+ }
440
+ VALUE rb_value = rb_ary_entry(rb_array, index);
441
+ return ruby_to_mongory_value_shallow(self->pool, rb_value);
442
+ }
443
+
444
+ static bool ruby_mongory_array_each(mongory_array *self, void *acc, mongory_array_callback_func func) {
445
+ ruby_mongory_array_t *array = (ruby_mongory_array_t *)self;
446
+ VALUE rb_array = array->rb_array;
447
+ for (long i = 0; i < RARRAY_LEN(rb_array); i++) {
448
+ VALUE rb_value = rb_ary_entry(rb_array, i);
449
+ mongory_value *cval = ruby_to_mongory_value_shallow(self->pool, rb_value);
450
+ if (!func(cval, acc)) {
451
+ return false;
452
+ }
453
+ }
454
+ return true;
455
+ }
456
+
457
+ mongory_value *ruby_mongory_array_wrap(mongory_memory_pool *pool, VALUE rb_array) {
458
+ ruby_mongory_array_t *array = MG_ALLOC_PTR(pool, ruby_mongory_array_t);
459
+ ruby_mongory_memory_pool_t *rb_pool = (ruby_mongory_memory_pool_t *)pool;
460
+ array->base.pool = pool;
461
+ array->base.get = ruby_mongory_array_get;
462
+ array->base.each = ruby_mongory_array_each;
463
+ array->rb_array = rb_array;
464
+ array->base.count = RARRAY_LEN(rb_array);
465
+ array->owner = rb_pool->owner;
466
+ return mongory_value_wrap_a(pool, &array->base);
467
+ }
468
+
469
+ // Convert mongory_value to Ruby value
470
+ void *mongory_value_to_ruby(mongory_memory_pool *pool, mongory_value *value) {
471
+ (void)pool;
472
+ if (!value)
473
+ return NULL;
474
+ return value->origin;
475
+ }
476
+
477
+ // ===== Cache helper implementations =====
478
+ static VALUE cache_fetch_string(ruby_mongory_matcher_t *owner, const char *key) {
479
+ if (!owner || !owner->string_map) return rb_utf8_str_new_cstr(key);
480
+ mongory_value *v = owner->string_map->get(owner->string_map, (char *)key);
481
+ if (v && v->origin) return (VALUE)v->origin;
482
+ VALUE s = rb_utf8_str_new_cstr(key);
483
+ mongory_value *store = mongory_value_wrap_u(owner->pool, NULL);
484
+ store->origin = (void *)s;
485
+ owner->string_map->set(owner->string_map, (char *)key, store);
486
+ owner->mark_list->push(owner->mark_list, store);
487
+ return s;
488
+ }
489
+
490
+ static inline VALUE char_key_to_symbol(const char *key, rb_encoding *enc) {
491
+ ID id = rb_check_id_cstr(key, (long)strlen(key), enc);
492
+ if (!id) id = rb_intern3(key, (long)strlen(key), enc);
493
+ return ID2SYM(id);
494
+ }
495
+
496
+ static VALUE cache_fetch_symbol(ruby_mongory_matcher_t *owner, const char *key) {
497
+ rb_encoding *enc = rb_utf8_encoding();
498
+ if (!owner || !owner->symbol_map) {
499
+ return char_key_to_symbol(key, enc);
500
+ }
501
+ mongory_value *v = owner->symbol_map->get(owner->symbol_map, (char *)key);
502
+ if (v && v->origin) return (VALUE)v->origin;
503
+ VALUE sym = char_key_to_symbol(key, enc);
504
+ mongory_value *store = mongory_value_wrap_u(owner->pool, NULL);
505
+ store->origin = (void *)sym;
506
+ owner->symbol_map->set(owner->symbol_map, (char *)key, store);
507
+ owner->mark_list->push(owner->mark_list, store);
508
+ return sym;
509
+ }
510
+
511
+ // Regex adapter bridging to Ruby's Regexp
512
+ static bool ruby_regex_match_adapter(mongory_memory_pool *pool, mongory_value *pattern, mongory_value *value) {
513
+ if (!pattern || !value) {
514
+ return false;
515
+ }
516
+ if (value->type != MONGORY_TYPE_STRING) {
517
+ return false;
518
+ }
519
+
520
+ VALUE rb_str = (VALUE)value->origin;
521
+ VALUE rb_re = Qnil;
522
+
523
+ if (pattern->type == MONGORY_TYPE_REGEX && pattern->data.regex) {
524
+ rb_re = (VALUE)pattern->data.regex;
525
+ } else if (pattern->type == MONGORY_TYPE_STRING) {
526
+ rb_re = rb_funcall(rb_cRegexp, rb_intern("new"), 1, (VALUE)pattern->origin);
527
+ mongory_value *temp_value = mongory_value_wrap_regex(pool, (void *)rb_re);
528
+ memcpy(pattern, temp_value, sizeof(mongory_value));
529
+ pattern->origin = (void *)rb_re;
530
+ } else {
531
+ return false;
532
+ }
533
+
534
+ VALUE matched = rb_funcall(rb_re, rb_intern("match?"), 1, rb_str);
535
+ return RTEST(matched);
536
+ }
537
+
538
+ static char *ruby_regex_stringify_adapter(mongory_memory_pool *pool, mongory_value *pattern) {
539
+ (void)pool;
540
+ if (pattern->type != MONGORY_TYPE_REGEX) {
541
+ return NULL;
542
+ }
543
+ VALUE rb_re = (VALUE)pattern->data.regex;
544
+ VALUE rb_str = rb_funcall(rb_re, rb_intern("inspect"), 0);
545
+ return StringValueCStr(rb_str);
546
+ }
547
+
548
+ static mongory_matcher_custom_context *ruby_custom_matcher_build(char *key, mongory_value *condition, void *ctx) {
549
+ mongory_memory_pool *pool = condition->pool;
550
+ ruby_mongory_memory_pool_t *rb_pool = (ruby_mongory_memory_pool_t *)pool;
551
+ ruby_mongory_matcher_t *owner = rb_pool->owner;
552
+ VALUE matcher_class = rb_funcall(mMongoryMatchers, rb_intern("lookup"), 1, cache_fetch_string(owner, key));
553
+ if (matcher_class == Qnil) {
554
+ return NULL;
555
+ }
556
+ VALUE kw_hash = rb_hash_new();
557
+ rb_hash_aset(kw_hash, ID2SYM(rb_intern("context")), (VALUE)ctx);
558
+
559
+ #ifdef RB_PASS_KEYWORDS
560
+ VALUE argv_new[2] = { (VALUE)condition->origin, kw_hash };
561
+ VALUE matcher = rb_funcallv_kw(matcher_class, rb_intern("new"), 2, argv_new, RB_PASS_KEYWORDS);
562
+ #else
563
+ VALUE matcher = rb_funcall(matcher_class, rb_intern("new"), 2, (VALUE)condition->origin, kw_hash);
564
+ #endif
565
+
566
+ if (matcher == Qnil) {
567
+ return NULL;
568
+ }
569
+ mongory_value *matcher_value = mongory_value_wrap_u(pool, NULL);
570
+ matcher_value->origin = (void *)matcher;
571
+ owner->mark_list->push(owner->mark_list, matcher_value);
572
+ VALUE class_name = rb_funcall(matcher_class, rb_intern("name"), 0);
573
+ mongory_matcher_custom_context *return_ctx = MG_ALLOC_PTR(pool, mongory_matcher_custom_context);
574
+ if (return_ctx == NULL) {
575
+ return NULL;
576
+ }
577
+ return_ctx->name = StringValueCStr(class_name);
578
+ return_ctx->external_matcher = (void *)matcher;
579
+ return return_ctx;
580
+ }
581
+
582
+ static bool ruby_custom_matcher_match(void *ruby_matcher, mongory_value *value) {
583
+ VALUE matcher = (VALUE)ruby_matcher;
584
+ VALUE match_result = rb_funcall(matcher, rb_intern("match?"), 1, value->origin);
585
+ return RTEST(match_result);
586
+ }
587
+
588
+ static bool ruby_custom_matcher_lookup(char *key) {
589
+ VALUE matcher_class = rb_funcall(mMongoryMatchers, rb_intern("lookup"), 1, rb_str_new_cstr(key));
590
+ return RTEST(matcher_class);
591
+ }
592
+
593
+ /**
594
+ * Extension initialization
595
+ */
596
+ void Init_mongory_ext(void) {
597
+ // Initialize mongory core
598
+ mongory_init();
599
+
600
+ // Define modules and classes
601
+ mMongory = rb_define_module("Mongory");
602
+ cMongoryMatcher = rb_define_class_under(mMongory, "CMatcher", rb_cObject);
603
+ mMongoryMatchers = rb_define_module_under(mMongory, "Matchers");
604
+ VALUE mMongoryUtils = rb_define_module_under(mMongory, "Utils");
605
+ cMongoryMatcherContext = rb_define_class_under(mMongoryUtils, "Context", rb_cObject);
606
+ // Mongory converters
607
+ inMongoryDataConverter = rb_funcall(mMongory, rb_intern("data_converter"), 0);
608
+ inMongoryConditionConverter = rb_funcall(mMongory, rb_intern("condition_converter"), 0);
609
+
610
+ // Define error classes
611
+ eMongoryError = rb_define_class_under(mMongory, "Error", rb_eStandardError);
612
+ eMongoryTypeError = rb_define_class_under(mMongory, "TypeError", eMongoryError);
613
+
614
+ // Define Matcher methods
615
+ rb_define_singleton_method(cMongoryMatcher, "new", ruby_mongory_matcher_new, -1);
616
+ rb_define_method(cMongoryMatcher, "match?", ruby_mongory_matcher_match, 1);
617
+ rb_define_method(cMongoryMatcher, "explain", ruby_mongory_matcher_explain, 0);
618
+ rb_define_method(cMongoryMatcher, "condition", ruby_mongory_matcher_condition, 0);
619
+ rb_define_method(cMongoryMatcher, "context", ruby_mongory_matcher_context, 0);
620
+ rb_define_method(cMongoryMatcher, "trace", ruby_mongory_matcher_trace, 1);
621
+ rb_define_method(cMongoryMatcher, "enable_trace", ruby_mongory_matcher_enable_trace, 0);
622
+ rb_define_method(cMongoryMatcher, "disable_trace", ruby_mongory_matcher_disable_trace, 0);
623
+ rb_define_method(cMongoryMatcher, "print_trace", ruby_mongory_matcher_print_trace, 0);
624
+ // Set regex adapter to use Ruby's Regexp
625
+ mongory_regex_func_set(ruby_regex_match_adapter);
626
+ mongory_regex_stringify_func_set(ruby_regex_stringify_adapter);
627
+ // Set value converter functions
628
+ mongory_value_converter_deep_convert_set(ruby_to_mongory_value_deep);
629
+ mongory_value_converter_shallow_convert_set(ruby_to_mongory_value_shallow);
630
+ mongory_value_converter_recover_set(mongory_value_to_ruby);
631
+ // Set custom matcher adapter
632
+ mongory_custom_matcher_match_func_set(ruby_custom_matcher_match);
633
+ mongory_custom_matcher_build_func_set(ruby_custom_matcher_build);
634
+ mongory_custom_matcher_lookup_func_set(ruby_custom_matcher_lookup);
635
+ }
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'query_builder'
4
+
5
+ module Mongory
6
+ # Mongory::CQueryBuilder is a query builder for Mongory::CMatcher.
7
+ # It is used to build a query for a Mongory::CMatcher.
8
+ class CQueryBuilder < QueryBuilder
9
+ def each
10
+ return to_enum(:each) unless block_given?
11
+
12
+ @records.each do |record|
13
+ @context.current_record = record
14
+ yield record if @matcher.match?(record)
15
+ end
16
+ end
17
+
18
+ alias_method :fast, :each
19
+
20
+ def explain
21
+ @matcher.match?(@records.first)
22
+ @matcher.explain
23
+ end
24
+
25
+ def trace
26
+ return to_enum(:trace) unless block_given?
27
+
28
+ @matcher.enable_trace
29
+ @records.each do |record|
30
+ @context.current_record = record
31
+ yield record if @matcher.match?(record)
32
+ end
33
+ ensure
34
+ @matcher.print_trace
35
+ @matcher.disable_trace
36
+ end
37
+
38
+ private
39
+
40
+ def set_matcher(condition = {})
41
+ @matcher = CMatcher.new(condition, context: @context)
42
+ end
43
+ end
44
+ end
@@ -199,6 +199,14 @@ module Mongory
199
199
  nil
200
200
  end
201
201
 
202
+ def c
203
+ return self unless defined?(Mongory::CMatcher)
204
+
205
+ c_builder = CQueryBuilder.new(@records, context: @context)
206
+ c_builder.send(:set_matcher, @matcher.condition)
207
+ c_builder
208
+ end
209
+
202
210
  private
203
211
 
204
212
  # @private
@@ -36,6 +36,13 @@ module Mongory
36
36
  new_context.config = @config.dup
37
37
  new_context
38
38
  end
39
+
40
+ def to_hash
41
+ {
42
+ config: @config,
43
+ need_convert: @need_convert
44
+ }
45
+ end
39
46
  end
40
47
  end
41
48
  end