mongory 0.8.0-x86_64-linux → 0.8.2-x86_64-linux

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/README.md +12 -0
  4. data/docs/advanced_usage.md +10 -0
  5. data/ext/mongory_ext/mongory-core/src/foundations/array.c +22 -15
  6. data/ext/mongory_ext/mongory-core/src/foundations/array_private.h +1 -0
  7. data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.c +0 -2
  8. data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.h +0 -6
  9. data/ext/mongory_ext/mongory-core/src/foundations/table.c +9 -10
  10. data/ext/mongory_ext/mongory-core/src/foundations/utils.c +21 -11
  11. data/ext/mongory_ext/mongory-core/src/foundations/utils.h +2 -2
  12. data/ext/mongory_ext/mongory-core/src/foundations/value.c +10 -2
  13. data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.h +2 -0
  14. data/ext/mongory_ext/mongory-core/src/matchers/existance_matcher.c +16 -3
  15. data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.c +4 -0
  16. data/ext/mongory_ext/mongory-core/src/matchers/matcher.c +43 -7
  17. data/ext/mongory_ext/mongory_ext.c +57 -19
  18. data/lib/core/2.6/mongory_ext.so +0 -0
  19. data/lib/core/2.7/mongory_ext.so +0 -0
  20. data/lib/core/3.0/mongory_ext.so +0 -0
  21. data/lib/core/3.1/mongory_ext.so +0 -0
  22. data/lib/core/3.2/mongory_ext.so +0 -0
  23. data/lib/core/3.3/mongory_ext.so +0 -0
  24. data/lib/core/3.4/mongory_ext.so +0 -0
  25. data/lib/core/4.0/mongory_ext.so +0 -0
  26. data/lib/mongory/c_matcher.rb +7 -7
  27. data/lib/mongory/converters/key_converter.rb +1 -1
  28. data/lib/mongory/matchers/array_record_matcher.rb +1 -1
  29. data/lib/mongory/matchers/field_matcher.rb +16 -3
  30. data/lib/mongory/query_builder.rb +6 -0
  31. data/lib/mongory/version.rb +1 -1
  32. data/lib/mongory.rb +1 -1
  33. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c4acb7e520b6b975f392b3b1e97d5a6300f288f623e5f8c53d75b9561d0519b1
4
- data.tar.gz: 47ced57b77a9b68208c948fdcb0348a1da58972b1795ca64c3893b586e864113
3
+ metadata.gz: 62fa18e529cd104d255956668e3acb8421d5d8a943d9c8505a6fec1213bed592
4
+ data.tar.gz: 75aefb02081fcd48af41b6d8f6cdb9c22cc066821a92b02e227ef3818c50b547
5
5
  SHA512:
6
- metadata.gz: 16796ae6a17cea54814d0a20da9d4322432b1c0983d726a124a2f1555e527572d81930b38436e3dc6f833dc696554d2c1495e0a0a407fb3e4d3b5bf187dcee60
7
- data.tar.gz: 2d379d2759e579050da26bfbf50887f0146800a48c1c750dbe04726fd5bb887e20d80834c54ad6f3ef05d5d5895e0a93e00a501ea5191827d3923a84eff7e518
6
+ metadata.gz: d1d092f65c6afcd16ec537795d08f03b60d66bce217281e9d854d42657de024b3230c93f5115e3552f60c51389dfec3b14f61657ebc03eb08effacffe108b66f
7
+ data.tar.gz: c9426e11587601a802b57a054665d9d43a7e393d00b397aa17c2d6e77598a21f8d9a2ee52941e11b00f6aeb2f656b6620da7b6eb14cf94acd0c5813e54f3b614
data/CHANGELOG.md CHANGED
@@ -1,4 +1,39 @@
1
1
  # Changelog
2
+
3
+ ## [0.8.2] - 2026-11-14
4
+
5
+ ### Fixes
6
+ - **Key & Array Index Matching (`QueryMatcher` & `CMatcher`)**:
7
+ - Normalize `Integer` condition keys to `String` in `KeyConverter` (consistent with `Symbol` keys), preventing `TypeError` in `CMatcher` and `NoMethodError` in `FieldMatcher`.
8
+ - Enforce strict integer index format (`\A(?:0|-?[1-9]\d*)\z`) when matching against arrays so keys with leading zeros (e.g., `"01"`, `"-0"`) or multiline strings are treated as document field keys rather than array indices.
9
+ - Treat valid numeric keys on `Array` records strictly as array indices in both `FieldMatcher` and `mongory-core`.
10
+ - **C Extension (`mongory_ext`)**:
11
+ - Fix GC use-after-free crash when `$regex` condition is given as a `String` pattern by registering the compiled `Regexp` in the matcher's GC `mark_list`.
12
+ - Fix NULL pointer dereference crash when calling `CMatcher#disable_trace` or `CMatcher#print_trace` before `enable_trace`.
13
+ - Fix memory pool type mismatch and out-of-bounds `owner` read in `CMatcher#trace` and uninitialized `owner` pointer in `CMatcher#enable_trace`.
14
+ - Prevent memory leaks when `CMatcher.new`, `trace`, `enable_trace`, or `disable_trace` raises `Mongory::TypeError`.
15
+ - Coerce non-string/non-symbol hash keys in `DataConverter`-converted condition values and register them in `mark_list`.
16
+ - **Core (`mongory-core` submodule)**:
17
+ - Fix off-by-one capacity growth condition in `mongory_array` and `mongory_table`.
18
+ - Propagate `extern_ctx` in `mongory_matcher_field_new` so nested custom matchers receive the Ruby matcher context.
19
+ - Sync trace state across `literal_matcher`'s `delegate_matcher` and `array_record_matcher`.
20
+ - Return `mongory_value_compare_fail` when comparing `NaN` float/double values instead of treating `NaN` as equal.
21
+
22
+ ## [0.8.1] - 2025-08-30
23
+
24
+ ### Fixes
25
+ - Use `require_relative` instead of `require` for internal file loading.
26
+
27
+ ### Docs & Chores
28
+ - Deprecate `QueryBuilder#fast` and update YARD documentation and README.
29
+
30
+ ## [0.8.0] - 2025-08-30
31
+
32
+ ### Features
33
+ - Add trace mode support to `QueryBuilder` and `CQueryBuilder`, printing trace output and resetting trace pool on each match.
34
+ - Expose `CMatcher.trace_result_colorful=` configuration option.
35
+ - Support cross-platform precompiled native gems for Linux (`x86_64`, `aarch64`, musl/glibc), macOS (`x86_64-darwin`, `arm64-darwin`), and Windows (`x64-mingw32`, `x64-mingw-ucrt`).
36
+
2
37
  ## [0.7.1] - 2025-08-17
3
38
  ### Fixes
4
39
  - Linter error
data/README.md CHANGED
@@ -110,6 +110,18 @@ records = [
110
110
  result = records.mongory
111
111
  .where(:age.gte => 18)
112
112
  .or({ :name => /J/ }, { :name.eq => 'Bob' })
113
+ .to_a
114
+
115
+ # Or input condition via json string from front-end
116
+ result = records.mongory
117
+ .where(JSON.parse('{
118
+ "age": {"$gt": 18},
119
+ "$or": [
120
+ {"name": {"$regex": "J"}},
121
+ {"name": "Bob"}
122
+ ]
123
+ }'))
124
+ .to_a
113
125
 
114
126
  # Using limit to restrict results
115
127
  # Note: limit executes immediately and affects subsequent conditions
@@ -25,6 +25,16 @@ users.mongory
25
25
  posts.mongory
26
26
  .where(:tags.elem_match => { :name => 'ruby', :priority.gt => 5 })
27
27
  .where(:comments.every => { :approved => true })
28
+
29
+ # Since Matcher provide `to_proc` method, you could treat Matcher as a block
30
+ matcher = Mongory::QueryMatcher.new({
31
+ :tags.elem_match => { :name => 'ruby', :priority.gt => 5 },
32
+ :comments.every => { :approved => true }
33
+ })
34
+
35
+ # Will filter
36
+ posts_1.select!(&matcher)
37
+ posts_2.select!(&matcher)
28
38
  ```
29
39
 
30
40
  ## Integration with ActiveRecord
@@ -146,8 +146,8 @@ static inline mongory_value *mongory_array_get(mongory_array *self, size_t index
146
146
  */
147
147
  static inline bool mongory_array_set(mongory_array *self, size_t index, mongory_value *value) {
148
148
  mongory_array_private *internal = (mongory_array_private *)self;
149
- // Ensure capacity for the given index (index + 1 elements).
150
- if (!mongory_array_grow_if_needed(self, index + 1)) {
149
+ // Ensure capacity for the given 0-based index.
150
+ if (!mongory_array_grow_if_needed(self, index)) {
151
151
  return false;
152
152
  }
153
153
 
@@ -163,17 +163,10 @@ static inline bool mongory_array_set(mongory_array *self, size_t index, mongory_
163
163
  return true;
164
164
  }
165
165
 
166
- /**
167
- * @brief Creates and initializes a new mongory_array.
168
- *
169
- * Allocates memory for the array structure itself and its initial item storage
170
- * using the provided memory pool. Assigns function pointers for array
171
- * operations.
172
- *
173
- * @param pool The memory pool to use for allocations.
174
- * @return Pointer to the new mongory_array, or NULL on allocation failure.
175
- */
176
- mongory_array *mongory_array_new(mongory_memory_pool *pool) {
166
+ mongory_array *mongory_array_new_with_capacity(mongory_memory_pool *pool, size_t capacity) {
167
+ if (capacity == 0) {
168
+ capacity = MONGORY_ARRAY_INIT_SIZE;
169
+ }
177
170
  // First, allocate the private structure that holds all array metadata.
178
171
  mongory_array_private *internal = MG_ALLOC_PTR(pool, mongory_array_private);
179
172
  if (!internal) {
@@ -182,7 +175,7 @@ mongory_array *mongory_array_new(mongory_memory_pool *pool) {
182
175
  }
183
176
 
184
177
  // Then, allocate the initial block of memory for the array items.
185
- mongory_value **items = MG_ALLOC_ARY(pool, mongory_value*, MONGORY_ARRAY_INIT_SIZE);
178
+ mongory_value **items = MG_ALLOC_ARY(pool, mongory_value*, capacity);
186
179
  if (!items) {
187
180
  // If this fails, the 'internal' struct allocated above will be cleaned up
188
181
  // by the pool, assuming it's a tracing pool.
@@ -200,11 +193,25 @@ mongory_array *mongory_array_new(mongory_memory_pool *pool) {
200
193
 
201
194
  // Initialize the private part of the array structure.
202
195
  internal->items = items;
203
- internal->capacity = MONGORY_ARRAY_INIT_SIZE;
196
+ internal->capacity = capacity;
204
197
 
205
198
  return &internal->base; // Return pointer to the public structure.
206
199
  }
207
200
 
201
+ /**
202
+ * @brief Creates and initializes a new mongory_array.
203
+ *
204
+ * Allocates memory for the array structure itself and its initial item storage
205
+ * using the provided memory pool. Assigns function pointers for array
206
+ * operations.
207
+ *
208
+ * @param pool The memory pool to use for allocations.
209
+ * @return Pointer to the new mongory_array, or NULL on allocation failure.
210
+ */
211
+ mongory_array *mongory_array_new(mongory_memory_pool *pool) {
212
+ return mongory_array_new_with_capacity(pool, MONGORY_ARRAY_INIT_SIZE);
213
+ }
214
+
208
215
  /**
209
216
  * @brief Creates a new mongory_array instance with a nested array.
210
217
  *
@@ -6,6 +6,7 @@
6
6
  #include <stdbool.h>
7
7
 
8
8
  bool mongory_array_resize(mongory_array *self, size_t size); // resize array
9
+ mongory_array *mongory_array_new_with_capacity(mongory_memory_pool *pool, size_t capacity);
9
10
 
10
11
  typedef struct mongory_array_private {
11
12
  mongory_array base; // public array
@@ -16,8 +16,6 @@ mongory_string_buffer *mongory_string_buffer_new(mongory_memory_pool *pool) {
16
16
  return buffer;
17
17
  }
18
18
 
19
- void mongory_string_buffer_free(mongory_string_buffer *buffer) { buffer->pool->free(buffer->pool->ctx); }
20
-
21
19
  static inline void mongory_string_buffer_grow(mongory_string_buffer *buffer, size_t additional_size) {
22
20
  bool trigger_grow = false;
23
21
  while (buffer->size + additional_size + 1 > buffer->capacity) {
@@ -40,10 +40,4 @@ char *mongory_string_buffer_cstr(mongory_string_buffer *buffer);
40
40
  */
41
41
  void mongory_string_buffer_clear(mongory_string_buffer *buffer);
42
42
 
43
- /**
44
- * @brief Frees the buffer.
45
- * @param buffer The buffer to free.
46
- */
47
- void mongory_string_buffer_free(mongory_string_buffer *buffer);
48
-
49
43
  #endif
@@ -178,11 +178,12 @@ static inline bool mongory_table_rehash(mongory_table *self) {
178
178
  // might copy elements if count is not zero. We are managing elements manually.
179
179
  internal->array->count = 0;
180
180
 
181
- if (!mongory_array_resize(internal->array, new_capacity)) {
181
+ if (!mongory_array_resize(internal->array, new_capacity) ||
182
+ !internal->array->set(internal->array, new_capacity - 1, NULL)) {
182
183
  // Error: Rehashing failed because the underlying array could not be resized.
183
184
  // The table remains functional but may have a suboptimal load factor.
184
185
  self->pool->error = &MONGORY_ALLOC_ERROR;
185
- internal->array->count = self->count; // Try to restore roughly
186
+ internal->array->count = original_capacity; // Restore previous bucket count
186
187
  return false;
187
188
  }
188
189
  // After resize, internal->array (and old_array_private_view) points to the
@@ -416,14 +417,12 @@ mongory_table *mongory_table_new(mongory_memory_pool *pool) {
416
417
  return NULL; // Must have a valid pool.
417
418
 
418
419
  size_t init_capacity = MONGORY_TABLE_INIT_SIZE;
419
- mongory_array *bucket_array = mongory_array_new(pool);
420
-
421
- // Initialize the bucket array. We resize it to the initial capacity and
422
- // then set the last element to NULL. The `mongory_array_set` function
423
- // will automatically fill all intermediate slots with NULL if the index is
424
- // out of bounds, which perfectly initializes our bucket array.
425
- bool array_init_success = bucket_array && mongory_array_resize(bucket_array, init_capacity) &&
426
- bucket_array->set(bucket_array, init_capacity - 1, NULL);
420
+ mongory_array *bucket_array = mongory_array_new_with_capacity(pool, init_capacity);
421
+
422
+ // Initialize the bucket array by setting the last element to NULL.
423
+ // The `mongory_array_set` function will automatically fill all intermediate
424
+ // slots with NULL without triggering a resize.
425
+ bool array_init_success = bucket_array && bucket_array->set(bucket_array, init_capacity - 1, NULL);
427
426
 
428
427
  if (!array_init_success) {
429
428
  // If array initialization fails, we cannot proceed.
@@ -25,30 +25,40 @@
25
25
  * `false` otherwise. `errno` may be set by `strtol`.
26
26
  */
27
27
  bool mongory_try_parse_int(const char *key, int *out) {
28
- if (key == NULL || *key == '\0') {
28
+ if (key == NULL) {
29
29
  return false; // Invalid input string.
30
30
  }
31
- if (out == NULL) {
32
- return false; // Output pointer must be valid.
31
+
32
+ // Strictly match "0" or "-?[1-9][0-9]*" (no leading whitespace, '+', "-0", or leading zeros like "01").
33
+ if (key[0] == '0') {
34
+ if (key[1] != '\0') {
35
+ return false; // Disallow leading zeros like "00" or "01".
36
+ }
37
+ if (out != NULL) {
38
+ *out = 0;
39
+ }
40
+ return true;
41
+ }
42
+
43
+ const char *p = (key[0] == '-') ? key + 1 : key;
44
+ if (*p < '1') {
45
+ return false; // Disallow "", "-", "-0", "-01", leading whitespace, or '+'.
33
46
  }
34
47
 
35
48
  char *endptr = NULL;
36
49
  errno = 0; // Clear errno before calling strtol.
37
50
  long val = strtol(key, &endptr, 10); // Base 10 conversion.
38
51
 
39
- // Check for parsing errors reported by strtol.
40
- if (endptr == key) {
41
- return false; // No digits were found.
42
- }
43
52
  if (*endptr != '\0') {
44
53
  return false; // Additional characters after the number.
45
54
  }
46
- if (errno == ERANGE || val < INT_MIN || val > INT_MAX) {
47
- // Value out of range for int. ERANGE is set by strtol for overflow/underflow.
48
- return false;
55
+ if (val < INT_MIN || val > INT_MAX) {
56
+ return false; // Value out of range for int.
49
57
  }
50
58
 
51
- *out = (int)val; // Successfully parsed and within int range.
59
+ if (out != NULL) {
60
+ *out = (int)val; // Successfully parsed and within int range.
61
+ }
52
62
  return true;
53
63
  }
54
64
 
@@ -13,8 +13,8 @@
13
13
  * range (`INT_MIN`, `INT_MAX`).
14
14
  *
15
15
  * @param key The null-terminated string to parse. Must not be NULL or empty.
16
- * @param out A pointer to an integer where the parsed value will be stored if
17
- * successful. Must not be NULL.
16
+ * @param out Optional pointer to an integer where the parsed value will be
17
+ * stored if successful. May be NULL if only validation is needed.
18
18
  * @return bool True if the string was successfully parsed as an integer and is
19
19
  * within `int` range, false otherwise. `errno` might be set by `strtol` on
20
20
  * failure (e.g. `ERANGE`).
@@ -154,6 +154,9 @@ static inline int mongory_value_int_compare(mongory_value *a, mongory_value *b)
154
154
  // Compare int (a) as double with double (b).
155
155
  double a_as_double = (double)a->data.i;
156
156
  double b_value = b->data.d;
157
+ if (b_value != b_value) {
158
+ return mongory_value_compare_fail; // NaN is not comparable.
159
+ }
157
160
  return (a_as_double > b_value) - (a_as_double < b_value);
158
161
  }
159
162
  if (b->type == MONGORY_TYPE_INT) {
@@ -179,14 +182,19 @@ mongory_value *mongory_value_wrap_i(mongory_memory_pool *pool, int64_t i_val) {
179
182
 
180
183
  /** Compares MONGORY_TYPE_DOUBLE with DOUBLE or INT. */
181
184
  static inline int mongory_value_double_compare(mongory_value *a, mongory_value *b) {
185
+ double a_value = a->data.d;
186
+ if (a_value != a_value) {
187
+ return mongory_value_compare_fail; // NaN is not comparable.
188
+ }
182
189
  if (b->type == MONGORY_TYPE_DOUBLE) {
183
- double a_value = a->data.d;
184
190
  double b_value = b->data.d;
191
+ if (b_value != b_value) {
192
+ return mongory_value_compare_fail; // NaN is not comparable.
193
+ }
185
194
  return (a_value > b_value) - (a_value < b_value);
186
195
  }
187
196
  if (b->type == MONGORY_TYPE_INT) {
188
197
  // Compare double (a) with int (b) as double.
189
- double a_value = a->data.d;
190
198
  double b_as_double = (double)b->data.i;
191
199
  return (a_value > b_as_double) - (a_value < b_as_double);
192
200
  }
@@ -97,4 +97,6 @@ mongory_matcher *mongory_matcher_always_true_new(mongory_memory_pool *pool, mong
97
97
  */
98
98
  mongory_matcher *mongory_matcher_always_false_new(mongory_memory_pool *pool, mongory_value *condition, void *extern_ctx);
99
99
 
100
+ void mongory_matcher_enable_trace_at(mongory_matcher *matcher, mongory_array *trace_stack, int level);
101
+
100
102
  #endif /* MONGORY_MATCHER_BASE_H */
@@ -95,7 +95,7 @@ static inline bool mongory_matcher_present_match(mongory_matcher *matcher, mongo
95
95
  return !condition_expects_presence;
96
96
  }
97
97
 
98
- bool actual_value_is_present;
98
+ bool actual_value_is_present = false;
99
99
  switch (value->type) {
100
100
  case MONGORY_TYPE_ARRAY:
101
101
  actual_value_is_present = (value->data.a != NULL && value->data.a->count > 0);
@@ -103,9 +103,22 @@ static inline bool mongory_matcher_present_match(mongory_matcher *matcher, mongo
103
103
  case MONGORY_TYPE_TABLE:
104
104
  actual_value_is_present = (value->data.t != NULL && value->data.t->count > 0);
105
105
  break;
106
- case MONGORY_TYPE_STRING:
107
- actual_value_is_present = (value->data.s != NULL && *(value->data.s) != '\0');
106
+ case MONGORY_TYPE_STRING: {
107
+ // If the string is NULL, it is not "present".
108
+ // If the strint is " " or more than one space, it is not "present".
109
+ char *s = value->data.s;
110
+ if (s != NULL && *s != '\0') {
111
+ char *p = s;
112
+ while (*p != '\0') {
113
+ if (*p != ' ') {
114
+ actual_value_is_present = true;
115
+ break;
116
+ }
117
+ p++;
118
+ }
119
+ }
108
120
  break;
121
+ }
109
122
  case MONGORY_TYPE_NULL:
110
123
  actual_value_is_present = false; // An explicit BSON-style Null is not "present".
111
124
  break;
@@ -62,6 +62,9 @@ static inline bool mongory_matcher_literal_match(mongory_matcher *matcher, mongo
62
62
  // Lazily create the array_record_matcher if needed.
63
63
  // The condition for array_record_new is the original condition of the literal matcher.
64
64
  literal->array_record_matcher = mongory_matcher_array_record_new(literal->base.pool, literal->base.condition, literal->base.extern_ctx);
65
+ if (literal->array_record_matcher && literal->base.trace_stack) {
66
+ mongory_matcher_enable_trace_at(literal->array_record_matcher, literal->base.trace_stack, literal->base.trace_level + 1);
67
+ }
65
68
  }
66
69
  // If right child exists (or was successfully created), use it.
67
70
  return literal->array_record_matcher ? literal->array_record_matcher->match(literal->array_record_matcher, value) : false;
@@ -207,6 +210,7 @@ mongory_matcher *mongory_matcher_field_new(mongory_memory_pool *pool, char *fiel
207
210
  field_m->literal.base.name = mongory_string_cpy(pool, "Field");
208
211
  field_m->literal.base.explain = mongory_matcher_field_explain;
209
212
  field_m->literal.base.traverse = mongory_matcher_literal_traverse;
213
+ field_m->literal.base.extern_ctx = extern_ctx;
210
214
  // The 'left' child of the composite is the actual matcher for the field's value,
211
215
  // determined by the type of 'condition_for_field'.
212
216
  field_m->literal.delegate_matcher = mongory_matcher_literal_delegate(pool, condition_for_field, extern_ctx);
@@ -147,13 +147,38 @@ static bool mongory_matcher_enable_trace_cb(mongory_matcher *matcher, mongory_ma
147
147
  matcher->trace_stack = (mongory_array *)ctx->acc;
148
148
  matcher->trace_level = ctx->level;
149
149
  matcher->match = mongory_matcher_traced_match;
150
+ if (matcher->traverse == mongory_matcher_literal_traverse) {
151
+ mongory_literal_matcher *literal = (mongory_literal_matcher *)matcher;
152
+ if (literal->array_record_matcher && literal->delegate_matcher) {
153
+ mongory_matcher_traverse_context delegate_ctx = {
154
+ .pool = ctx->pool,
155
+ .level = ctx->level + 1,
156
+ .count = 0,
157
+ .total = 1,
158
+ .acc = ctx->acc,
159
+ .callback = ctx->callback,
160
+ };
161
+ literal->delegate_matcher->traverse(literal->delegate_matcher, &delegate_ctx);
162
+ }
163
+ }
150
164
  return true;
151
165
  }
152
166
 
153
167
  static bool mongory_matcher_disable_trace_cb(mongory_matcher *matcher, mongory_matcher_traverse_context *ctx) {
154
- (void)ctx;
155
168
  matcher->match = matcher->original_match;
156
169
  matcher->trace_stack = NULL;
170
+ if (matcher->traverse == mongory_matcher_literal_traverse) {
171
+ mongory_literal_matcher *literal = (mongory_literal_matcher *)matcher;
172
+ if (literal->array_record_matcher && literal->delegate_matcher) {
173
+ mongory_matcher_traverse_context delegate_ctx = {
174
+ .level = ctx->level + 1,
175
+ .count = 0,
176
+ .total = 1,
177
+ .callback = ctx->callback,
178
+ };
179
+ literal->delegate_matcher->traverse(literal->delegate_matcher, &delegate_ctx);
180
+ }
181
+ }
157
182
  return true;
158
183
  }
159
184
 
@@ -192,15 +217,17 @@ static mongory_array *mongory_matcher_traces_sort(mongory_array *self, int level
192
217
  return sorted_array;
193
218
  }
194
219
 
195
- void mongory_matcher_enable_trace(mongory_matcher *matcher, mongory_memory_pool *temp_pool) {
196
- MONGORY_VALIDATE_PTR(temp_pool, matcher) && MONGORY_VALIDATE_PTR(temp_pool, matcher->traverse);
197
- if (temp_pool->error != NULL) {
220
+ void mongory_matcher_enable_trace_at(mongory_matcher *matcher, mongory_array *trace_stack, int level) {
221
+ if (!matcher || !trace_stack) {
222
+ return;
223
+ }
224
+ MONGORY_VALIDATE_PTR(trace_stack->pool, matcher->traverse);
225
+ if (trace_stack->pool->error != NULL) {
198
226
  return;
199
227
  }
200
- mongory_array *trace_stack = mongory_array_new(temp_pool);
201
228
  mongory_matcher_traverse_context ctx = {
202
- .pool = temp_pool,
203
- .level = 0,
229
+ .pool = trace_stack->pool,
230
+ .level = level,
204
231
  .count = 0,
205
232
  .total = 0,
206
233
  .acc = (void *)trace_stack,
@@ -209,6 +236,15 @@ void mongory_matcher_enable_trace(mongory_matcher *matcher, mongory_memory_pool
209
236
  matcher->traverse(matcher, &ctx);
210
237
  }
211
238
 
239
+ void mongory_matcher_enable_trace(mongory_matcher *matcher, mongory_memory_pool *temp_pool) {
240
+ MONGORY_VALIDATE_PTR(temp_pool, matcher) && MONGORY_VALIDATE_PTR(temp_pool, matcher->traverse);
241
+ if (temp_pool->error != NULL) {
242
+ return;
243
+ }
244
+ mongory_array *trace_stack = mongory_array_new(temp_pool);
245
+ mongory_matcher_enable_trace_at(matcher, trace_stack, 0);
246
+ }
247
+
212
248
  void mongory_matcher_disable_trace(mongory_matcher *matcher) {
213
249
  MONGORY_VALIDATE_PTR(matcher->pool, matcher) && MONGORY_VALIDATE_PTR(matcher->pool, matcher->traverse);
214
250
  if (matcher->pool->error != NULL) {
@@ -108,11 +108,14 @@ static VALUE rb_mongory_matcher_new(int argc, VALUE *argv, VALUE class) {
108
108
  rb_mongory_matcher_parse_argv(wrapper, argc, argv);
109
109
 
110
110
  mongory_matcher *matcher = mongory_matcher_new(matcher_pool_base, wrapper->condition, wrapper->ctx);
111
- if (rb_mongory_error_handling(matcher_pool_base, "Failed to create matcher")) {
111
+ if (matcher_pool_base->error) {
112
+ char err_msg[256];
113
+ snprintf(err_msg, sizeof(err_msg), "%s",
114
+ matcher_pool_base->error->message ? matcher_pool_base->error->message : "Unknown error");
112
115
  matcher_pool_base->free(matcher_pool_base);
113
116
  scratch_pool_base->free(scratch_pool_base);
114
117
  xfree(wrapper);
115
- return Qnil;
118
+ rb_raise(eMongoryTypeError, "Failed to create matcher: %s", err_msg);
116
119
  }
117
120
 
118
121
  wrapper->matcher = matcher;
@@ -182,16 +185,21 @@ static VALUE rb_mongory_matcher_explain(VALUE self) {
182
185
  static VALUE rb_mongory_matcher_trace(VALUE self, VALUE data) {
183
186
  rb_mongory_matcher_t *wrapper;
184
187
  TypedData_Get_Struct(self, rb_mongory_matcher_t, &rb_mongory_matcher_type, wrapper);
185
- mongory_memory_pool *trace_pool = mongory_memory_pool_new();
186
- mongory_value *data_value = rb_to_mongory_value_shallow(trace_pool, data);
187
-
188
- if (rb_mongory_error_handling(trace_pool, "Trace failed")) {
189
- trace_pool->free(trace_pool);
190
- return Qnil;
188
+ rb_mongory_memory_pool_t *trace_pool = rb_mongory_memory_pool_new();
189
+ trace_pool->owner = wrapper;
190
+ mongory_memory_pool *trace_pool_base = &trace_pool->base;
191
+ mongory_value *data_value = rb_to_mongory_value_shallow(trace_pool_base, data);
192
+
193
+ if (trace_pool_base->error) {
194
+ char err_msg[256];
195
+ snprintf(err_msg, sizeof(err_msg), "%s",
196
+ trace_pool_base->error->message ? trace_pool_base->error->message : "Unknown error");
197
+ trace_pool_base->free(trace_pool_base);
198
+ rb_raise(eMongoryTypeError, "Trace failed: %s", err_msg);
191
199
  }
192
200
 
193
201
  bool matched = mongory_matcher_trace(wrapper->matcher, data_value);
194
- trace_pool->free(trace_pool);
202
+ trace_pool_base->free(trace_pool_base);
195
203
 
196
204
  return matched ? Qtrue : Qfalse;
197
205
  }
@@ -200,14 +208,23 @@ static VALUE rb_mongory_matcher_trace(VALUE self, VALUE data) {
200
208
  static VALUE rb_mongory_matcher_enable_trace(VALUE self) {
201
209
  rb_mongory_matcher_t *wrapper;
202
210
  TypedData_Get_Struct(self, rb_mongory_matcher_t, &rb_mongory_matcher_type, wrapper);
203
- mongory_memory_pool *trace_pool = rb_mongory_memory_pool_new();
204
- mongory_matcher_enable_trace(wrapper->matcher, trace_pool);
205
-
206
- if (rb_mongory_error_handling(trace_pool, "Enable trace failed")) {
207
- trace_pool->free(trace_pool);
208
- return Qnil;
211
+ if (wrapper->trace_pool) {
212
+ wrapper->trace_pool->free(wrapper->trace_pool);
213
+ wrapper->trace_pool = NULL;
209
214
  }
210
- wrapper->trace_pool = trace_pool;
215
+ rb_mongory_memory_pool_t *trace_pool = rb_mongory_memory_pool_new();
216
+ trace_pool->owner = wrapper;
217
+ mongory_memory_pool *trace_pool_base = &trace_pool->base;
218
+ mongory_matcher_enable_trace(wrapper->matcher, trace_pool_base);
219
+
220
+ if (trace_pool_base->error) {
221
+ char err_msg[256];
222
+ snprintf(err_msg, sizeof(err_msg), "%s",
223
+ trace_pool_base->error->message ? trace_pool_base->error->message : "Unknown error");
224
+ trace_pool_base->free(trace_pool_base);
225
+ rb_raise(eMongoryTypeError, "Enable trace failed: %s", err_msg);
226
+ }
227
+ wrapper->trace_pool = trace_pool_base;
211
228
 
212
229
  return Qnil;
213
230
  }
@@ -218,9 +235,17 @@ static VALUE rb_mongory_matcher_disable_trace(VALUE self) {
218
235
  TypedData_Get_Struct(self, rb_mongory_matcher_t, &rb_mongory_matcher_type, wrapper);
219
236
  mongory_memory_pool *trace_pool = wrapper->trace_pool;
220
237
  mongory_matcher_disable_trace(wrapper->matcher);
221
- rb_mongory_error_handling(trace_pool, "Disable trace failed");
222
- trace_pool->free(trace_pool);
223
- wrapper->trace_pool = NULL;
238
+ if (trace_pool) {
239
+ wrapper->trace_pool = NULL;
240
+ if (trace_pool->error) {
241
+ char err_msg[256];
242
+ snprintf(err_msg, sizeof(err_msg), "%s",
243
+ trace_pool->error->message ? trace_pool->error->message : "Unknown error");
244
+ trace_pool->free(trace_pool);
245
+ rb_raise(eMongoryTypeError, "Disable trace failed: %s", err_msg);
246
+ }
247
+ trace_pool->free(trace_pool);
248
+ }
224
249
 
225
250
  return Qnil;
226
251
  }
@@ -229,6 +254,9 @@ static VALUE rb_mongory_matcher_disable_trace(VALUE self) {
229
254
  static VALUE rb_mongory_matcher_print_trace(VALUE self) {
230
255
  rb_mongory_matcher_t *wrapper;
231
256
  TypedData_Get_Struct(self, rb_mongory_matcher_t, &rb_mongory_matcher_type, wrapper);
257
+ if (!wrapper->trace_pool) {
258
+ return Qnil;
259
+ }
232
260
  mongory_matcher_print_trace(wrapper->matcher);
233
261
  rb_mongory_error_handling(wrapper->trace_pool, "Print trace failed");
234
262
 
@@ -266,6 +294,7 @@ static rb_mongory_memory_pool_t *rb_mongory_memory_pool_new() {
266
294
  rb_mongory_memory_pool_t *pool = malloc(sizeof(rb_mongory_memory_pool_t));
267
295
  mongory_memory_pool *base = mongory_memory_pool_new();
268
296
  memcpy(&pool->base, base, sizeof(mongory_memory_pool));
297
+ pool->owner = NULL;
269
298
  free(base);
270
299
 
271
300
  return pool;
@@ -407,12 +436,16 @@ static int hash_foreach_deep_convert_cb(VALUE key, VALUE val, VALUE ptr) {
407
436
  key_str = (char *)rb_id2name(SYM2ID(key));
408
437
  store_map = owner->symbol_map;
409
438
  } else {
439
+ if (!RB_TYPE_P(key, T_STRING)) {
440
+ key = rb_obj_as_string(key);
441
+ }
410
442
  key_str = StringValueCStr(key);
411
443
  store_map = owner->string_map;
412
444
  }
413
445
  mongory_value *store = mongory_value_wrap_u(ctx->pool, NULL);
414
446
  store->origin = (void *)key;
415
447
  store_map->set(store_map, key_str, store);
448
+ owner->mark_list->push(owner->mark_list, store);
416
449
  mongory_value *cval = rb_to_mongory_value_deep(ctx->pool, val);
417
450
  ctx->table->set(ctx->table, key_str, cval);
418
451
  return ST_CONTINUE;
@@ -591,6 +624,10 @@ static bool rb_mongory_regex_match_adapter(mongory_memory_pool *pool, mongory_va
591
624
  mongory_value *temp_value = mongory_value_wrap_regex(pool, (void *)rb_re);
592
625
  memcpy(pattern, temp_value, sizeof(mongory_value));
593
626
  pattern->origin = (void *)rb_re;
627
+ rb_mongory_memory_pool_t *rb_pool = (rb_mongory_memory_pool_t *)pool;
628
+ if (rb_pool->owner) {
629
+ rb_pool->owner->mark_list->push(rb_pool->owner->mark_list, pattern);
630
+ }
594
631
  } else {
595
632
  return false;
596
633
  }
@@ -678,6 +715,7 @@ void Init_mongory_ext(void) {
678
715
  // Define modules and classes
679
716
  mMongory = rb_define_module("Mongory");
680
717
  cMongoryMatcher = rb_define_class_under(mMongory, "CMatcher", rb_cObject);
718
+ rb_undef_alloc_func(cMongoryMatcher);
681
719
  mMongoryMatchers = rb_define_module_under(mMongory, "Matchers");
682
720
  VALUE mMongoryUtils = rb_define_module_under(mMongory, "Utils");
683
721
  cMongoryMatcherContext = rb_define_class_under(mMongoryUtils, "Context", rb_cObject);
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -10,9 +10,13 @@ module Mongory
10
10
  # collection.mongory.c.where(condition).to_a
11
11
  class CMatcher
12
12
  # @!method self.new(condition)
13
- # @param condition [Object] the condition
13
+ # @param condition [Hash] the condition
14
14
  # @return [Mongory::CMatcher] a new matcher
15
15
  # @note This method is implemented in the C extension
16
+ # @!method self.trace_result_colorful=(colorful)
17
+ # @param colorful [Boolean] whether to enable colorful trace result
18
+ # @return [void]
19
+ # @note This method is implemented in the C extension
16
20
  #
17
21
  # @!method match?(record)
18
22
  # @param record [Object] the record to match against
@@ -20,7 +24,7 @@ module Mongory
20
24
  # @note This method is implemented in the C extension
21
25
  # @!method explain
22
26
  # @return [void]
23
- # @note This method will print metcher tree structure
27
+ # @note This method will print matcher tree structure
24
28
  # @note This method is implemented in the C extension
25
29
  # @!method trace
26
30
  # @return [Boolean] true if the record matches the condition, false otherwise
@@ -39,15 +43,11 @@ module Mongory
39
43
  # @note This method will print trace result
40
44
  # @note This method is implemented in the C extension
41
45
  # @!method condition
42
- # @return [Object] the condition
46
+ # @return [Hash] the condition
43
47
  # @note This method is implemented in the C extension
44
48
  # @!method context
45
49
  # @return [Utils::Context] the context
46
50
  # @note This method is implemented in the C extension
47
- # @!method trace_result_colorful=
48
- # @param colorful [Boolean] whether to enable colorful trace result
49
- # @return [Boolean]
50
- # @note This method is implemented in the C extension
51
51
 
52
52
  # @return [Proc] a Proc that performs the matching operation
53
53
  def to_proc
@@ -42,7 +42,7 @@ module Mongory
42
42
  case target
43
43
  when String
44
44
  convert_string_key(target, other)
45
- when Symbol
45
+ when Symbol, Integer
46
46
  convert_string_key(target.to_s, other)
47
47
  when QueryOperator
48
48
  # Handle special case for QueryOperator
@@ -74,7 +74,7 @@ module Mongory
74
74
  h_elem_match = {}
75
75
  @condition.each_pair do |key, value|
76
76
  case key
77
- when Integer, /^-?\d+$/
77
+ when Integer, FieldMatcher::INTEGER_INDEX_REGEX
78
78
  h_parsed[key.to_i] = value
79
79
  when '$elemMatch'
80
80
  h_elem_match.merge!(value)
@@ -33,6 +33,8 @@ module Mongory
33
33
  ::Symbol
34
34
  ].freeze
35
35
 
36
+ INTEGER_INDEX_REGEX = /\A(?:0|-?[1-9]\d*)\z/.freeze
37
+
36
38
  # Initializes a new field matcher.
37
39
  #
38
40
  # @param field [String, Symbol] the field to match against
@@ -84,6 +86,7 @@ module Mongory
84
86
  def raw_proc
85
87
  super_proc = super
86
88
  field = @field
89
+ str_field, sym_field, int_field = parse_field_keys(field)
87
90
  need_convert = @context.need_convert
88
91
  data_converter = Mongory.data_converter
89
92
 
@@ -91,11 +94,11 @@ module Mongory
91
94
  sub_record =
92
95
  case record
93
96
  when Hash
94
- record.fetch(field) do
95
- record.fetch(field.to_sym, KEY_NOT_FOUND)
97
+ record.fetch(str_field) do
98
+ record.fetch(sym_field, KEY_NOT_FOUND)
96
99
  end
97
100
  when Array
98
- record.fetch(field, KEY_NOT_FOUND)
101
+ int_field ? record.fetch(int_field, KEY_NOT_FOUND) : KEY_NOT_FOUND
99
102
  when KEY_NOT_FOUND, *CLASSES_NOT_ALLOW_TO_DIG
100
103
  next false
101
104
  else
@@ -124,6 +127,16 @@ module Mongory
124
127
 
125
128
  private
126
129
 
130
+ def parse_field_keys(field)
131
+ str_field = field.to_s
132
+ int_field = if field.is_a?(Integer)
133
+ field
134
+ elsif field.is_a?(String) && INTEGER_INDEX_REGEX.match?(field)
135
+ field.to_i
136
+ end
137
+ [str_field, str_field.to_sym, int_field]
138
+ end
139
+
127
140
  # Returns a single-line summary of the field matcher including the field and condition.
128
141
  #
129
142
  # @return [String] a formatted title for tree display
@@ -55,10 +55,16 @@ module Mongory
55
55
  # Iterates through all records that match the current matcher.
56
56
  # Uses a compiled Proc for faster matching.
57
57
  #
58
+ # @deprecated
59
+ # Since C extension has implemented, the fast mode is no longer needed. Use C extension instead.
60
+ #
61
+ # This method is deprecated and will be removed in future versions.
62
+ #
58
63
  # @yieldparam record [Object] each matching record
59
64
  # @return [Enumerator] if no block given
60
65
  # @return [void] if block given
61
66
  def fast
67
+ warn('Deprecated: Since C extension has implemented, the fast mode is no longer needed. Use C extension instead.')
62
68
  return to_enum(:fast) unless block_given?
63
69
 
64
70
  @context.need_convert = false
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mongory
4
- VERSION = '0.8.0'
4
+ VERSION = '0.8.2'
5
5
  end
data/lib/mongory.rb CHANGED
@@ -118,7 +118,7 @@ end
118
118
 
119
119
  begin
120
120
  abi = RUBY_VERSION.split('.').first(2).join('.')
121
- require "core/#{abi}/mongory_ext"
121
+ require_relative "core/#{abi}/mongory_ext"
122
122
  require_relative 'mongory/c_query_builder'
123
123
  require_relative 'mongory/c_matcher'
124
124
  rescue LoadError => e
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.2
5
5
  platform: x86_64-linux
6
6
  authors:
7
7
  - koten0224
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-08-30 00:00:00.000000000 Z
11
+ date: 2026-11-14 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A Mongo-like in-memory query DSL for Ruby
14
14
  email:
@@ -84,6 +84,7 @@ files:
84
84
  - lib/core/3.2/mongory_ext.so
85
85
  - lib/core/3.3/mongory_ext.so
86
86
  - lib/core/3.4/mongory_ext.so
87
+ - lib/core/4.0/mongory_ext.so
87
88
  - lib/generators/mongory/install/install_generator.rb
88
89
  - lib/generators/mongory/install/templates/initializer.rb.erb
89
90
  - lib/generators/mongory/matcher/matcher_generator.rb