mongory 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +42 -0
- data/README.md +82 -176
- data/Rakefile +77 -0
- data/SUBMODULE_INTEGRATION.md +325 -0
- data/docs/advanced_usage.md +40 -0
- data/docs/clang_bridge.md +69 -0
- data/docs/field_names.md +30 -0
- data/docs/migration.md +30 -0
- data/docs/performance.md +61 -0
- data/examples/benchmark.rb +98 -19
- data/ext/mongory_ext/extconf.rb +91 -0
- data/ext/mongory_ext/mongory-core/LICENSE +3 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/array.h +105 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/config.h +206 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/error.h +82 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/memory_pool.h +95 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/table.h +108 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/value.h +175 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/matchers/matcher.h +76 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core.h +12 -0
- data/ext/mongory_ext/mongory-core/src/foundations/array.c +246 -0
- data/ext/mongory_ext/mongory-core/src/foundations/array_private.h +18 -0
- data/ext/mongory_ext/mongory-core/src/foundations/config.c +406 -0
- data/ext/mongory_ext/mongory-core/src/foundations/config_private.h +30 -0
- data/ext/mongory_ext/mongory-core/src/foundations/error.c +30 -0
- data/ext/mongory_ext/mongory-core/src/foundations/memory_pool.c +298 -0
- data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.c +65 -0
- data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.h +49 -0
- data/ext/mongory_ext/mongory-core/src/foundations/table.c +458 -0
- data/ext/mongory_ext/mongory-core/src/foundations/value.c +459 -0
- data/ext/mongory_ext/mongory-core/src/matchers/array_record_matcher.c +309 -0
- data/ext/mongory_ext/mongory-core/src/matchers/array_record_matcher.h +47 -0
- data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.c +161 -0
- data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.h +115 -0
- data/ext/mongory_ext/mongory-core/src/matchers/compare_matcher.c +210 -0
- data/ext/mongory_ext/mongory-core/src/matchers/compare_matcher.h +83 -0
- data/ext/mongory_ext/mongory-core/src/matchers/composite_matcher.c +539 -0
- data/ext/mongory_ext/mongory-core/src/matchers/composite_matcher.h +125 -0
- data/ext/mongory_ext/mongory-core/src/matchers/existance_matcher.c +144 -0
- data/ext/mongory_ext/mongory-core/src/matchers/existance_matcher.h +48 -0
- data/ext/mongory_ext/mongory-core/src/matchers/external_matcher.c +121 -0
- data/ext/mongory_ext/mongory-core/src/matchers/external_matcher.h +46 -0
- data/ext/mongory_ext/mongory-core/src/matchers/inclusion_matcher.c +199 -0
- data/ext/mongory_ext/mongory-core/src/matchers/inclusion_matcher.h +46 -0
- data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.c +334 -0
- data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.h +97 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher.c +196 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.c +107 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.h +50 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.c +55 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.h +23 -0
- data/ext/mongory_ext/mongory_ext.c +635 -0
- data/lib/mongory/c_query_builder.rb +44 -0
- data/lib/mongory/converters/converted.rb +2 -2
- data/lib/mongory/matchers/abstract_matcher.rb +4 -0
- data/lib/mongory/matchers/abstract_multi_matcher.rb +44 -0
- data/lib/mongory/matchers/and_matcher.rb +2 -23
- data/lib/mongory/matchers/array_record_matcher.rb +2 -23
- data/lib/mongory/matchers/elem_match_matcher.rb +4 -0
- data/lib/mongory/matchers/eq_matcher.rb +4 -0
- data/lib/mongory/matchers/every_matcher.rb +4 -0
- data/lib/mongory/matchers/exists_matcher.rb +4 -0
- data/lib/mongory/matchers/field_matcher.rb +4 -0
- data/lib/mongory/matchers/gt_matcher.rb +4 -0
- data/lib/mongory/matchers/gte_matcher.rb +4 -0
- data/lib/mongory/matchers/hash_condition_matcher.rb +2 -23
- data/lib/mongory/matchers/in_matcher.rb +13 -4
- data/lib/mongory/matchers/literal_matcher.rb +4 -0
- data/lib/mongory/matchers/lt_matcher.rb +4 -0
- data/lib/mongory/matchers/lte_matcher.rb +4 -0
- data/lib/mongory/matchers/ne_matcher.rb +4 -0
- data/lib/mongory/matchers/nin_matcher.rb +14 -5
- data/lib/mongory/matchers/not_matcher.rb +4 -0
- data/lib/mongory/matchers/or_matcher.rb +2 -23
- data/lib/mongory/matchers/present_matcher.rb +4 -0
- data/lib/mongory/matchers/regex_matcher.rb +4 -0
- data/lib/mongory/matchers/size_matcher.rb +4 -0
- data/lib/mongory/query_builder.rb +8 -0
- data/lib/mongory/utils/context.rb +7 -0
- data/lib/mongory/utils.rb +1 -1
- data/lib/mongory/version.rb +1 -1
- data/lib/mongory.rb +7 -0
- data/mongory.gemspec +10 -4
- data/scripts/build_with_core.sh +292 -0
- metadata +70 -5
@@ -0,0 +1,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
|