mongory 0.6.3 → 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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/CHANGELOG.md +42 -0
  4. data/README.md +82 -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 +196 -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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 78a3d73e3c078b41aeeeb727445229e4edce85ff089de2d7412c9e64bfc613c0
4
- data.tar.gz: d7a9191a78a75b00f7a3ef7aa5d0b8e5ed8e8cb53e6a839e0873548da341849f
3
+ metadata.gz: 479d442ab8a04a0b6901af9c4833c89babb9acea2a4a8d93756115ee4ebfbf5c
4
+ data.tar.gz: e796d56379b2d116d802d6b23b90caed2659e3761f5cfe315a37ce089e37ccd5
5
5
  SHA512:
6
- metadata.gz: 6cc1387d67ccf0559a5f12d2140b2f0349a16b8461ae3e6992a3ba199a1824b178236d91cd8fe31bdbd73a85d056074efac32440ef8827bb31e9aebb6fd544bf
7
- data.tar.gz: 8f34cab007b5c692b48634686aa2931036b3f928da72004512b7e0de744dbe009656cac108f9f2d6d3d59b8d6d7e997b1b1b3f3a5865998bc83168c2dce60ea0
6
+ metadata.gz: 072eab3cbdb45d9b0a28f42201eddcf3a542518f7b37af2bc7b58f529989b28fb12f5b4df30a7677b5b864db542d8dfbf7fe218065e1f1e0450c076cff63ba82
7
+ data.tar.gz: fcc7d74ffd269bf477d2013112bc8a777b527130208fe4b464234446c7364f217c0ae3bac1d4dcd73dcc93e2d1bfc407e0d389cc9c8b53e8a7d71b7391656c25
data/.rubocop.yml CHANGED
@@ -5,6 +5,8 @@ AllCops:
5
5
  - 'vendor/**/*'
6
6
  - 'node_modules/**/*'
7
7
  - 'tmp/**/*'
8
+ - 'examples/**/*'
9
+ - 'ext/**/*'
8
10
  TargetRubyVersion: 2.6
9
11
 
10
12
  Style/StringLiterals:
data/CHANGELOG.md CHANGED
@@ -1,4 +1,46 @@
1
1
  # Changelog
2
+
3
+ ## [0.7.0] - 2025-08-17
4
+
5
+ ### Major Changes
6
+ - Introduced Clang bridge for the C extension, wiring Ruby DSL to `mongory-core` via `Mongory::CMatcher` and `CQueryBuilder`.
7
+
8
+ ### Features
9
+ - New `Mongory::CMatcher` with `match?`, `explain`, `trace`, `enable_trace`, `disable_trace`, and `print_trace`.
10
+ - New `QueryBuilder#c` to switch a Ruby query into the C fast path seamlessly.
11
+ - Regex bridging uses Ruby's `Regexp` under the hood for compatibility.
12
+ - Custom matcher bridge: core can delegate to Ruby matchers (build/match/lookup) when needed.
13
+ - Context bridge: shares `Utils::Context` between Ruby and C during matching.
14
+
15
+ ### Performance
16
+ - Significant speedups for large datasets when using `CMatcher`/`CQueryBuilder`.
17
+
18
+ ### Build & Tooling
19
+ - `ext/mongory_ext/extconf.rb` compiles `mongory-core` sources directly and normalizes GCC/Clang flags.
20
+ - Added explicit submodule build rules to avoid copying/linking extra artifacts.
21
+
22
+ ### Docs
23
+ - Updated README to document Clang bridge usage, availability checks, and examples.
24
+
25
+ ## [0.6.3] - 2025-05-27
26
+
27
+ ### Major Changes
28
+ - Introduced `AbstractMultiMatcher` to unify logic for compound matchers (`$and`, `$or`, `$nor`)
29
+ - Matchers are now sorted by `priority` to improve performance (early exit in `$and`, etc.)
30
+
31
+ ### Features
32
+ - Each matcher can define its own `priority` to participate in execution optimization
33
+ - `$in` and `$nin` matchers now normalize `Range` conditions into consistent matcher structures
34
+
35
+ ### Fixes
36
+ - Explicitly use `::Hash` and `::Array` in `converted.rb` to avoid namespace conflicts
37
+
38
+ ### Tests
39
+ - Added edge case specs for `$in` and `$nin` with `nil` and empty values
40
+
41
+ ### Chores
42
+ - Ignore `.vscode/` in `.gitignore`
43
+
2
44
  ## [0.6.1] - 2025-04-24
3
45
 
4
46
  ### Major Changes
data/README.md CHANGED
@@ -4,31 +4,52 @@ A Mongo-like in-memory query DSL for Ruby.
4
4
 
5
5
  Mongory lets you filter and query in-memory collections using syntax and semantics similar to MongoDB. It is designed for expressive chaining, symbolic operators, and composable matchers.
6
6
 
7
- ## Positioning
8
-
9
- Mongory is designed to serve two types of users:
10
-
11
- 1. For MongoDB users:
12
- - Seamless integration with familiar query syntax
13
- - Extends query capabilities for non-indexed fields
14
- - No additional learning cost
15
-
16
- 2. For non-MongoDB users:
17
- - Initial learning cost for MongoDB-style syntax
18
- - Long-term benefits:
19
- - Improved code readability
20
- - Better development efficiency
21
- - Lower maintenance costs
22
- - Ideal for teams valuing code quality and maintainability
7
+ ## Table of Contents
8
+
9
+ - Overview & Positioning
10
+ - [Positioning](#positioning)
11
+ - Getting Started
12
+ - [Requirements](#requirements)
13
+ - [Installation & Quick Start](#installation--quick-start)
14
+ - [Integration with MongoDB](#integration-with-mongodb)
15
+ - Usage & Concepts
16
+ - [Core Concepts & API Reference](#core-concepts--api-reference)
17
+ - [Handling Dots in Field Names](docs/field_names.md)
18
+ - [Advanced Usage](docs/advanced_usage.md)
19
+ - [Debugging](#debugging)
20
+ - [Clang Bridge (C Extension)](docs/clang_bridge.md)
21
+ - Performance
22
+ - [Performance & Benchmarks](docs/performance.md)
23
+ - [Supported Operators](#supported-operators)
24
+ - Guides
25
+ - [Best Practices](#best-practices)
26
+ - [Limitations](#limitations)
27
+ - [FAQ](#faq)
28
+ - [Troubleshooting](#troubleshooting)
29
+ - [Migration Guide](docs/migration.md)
30
+ - Project
31
+ - [Contributing](#contributing)
32
+ - [Code of Conduct](#code-of-conduct)
33
+ - [License](#license)
23
34
 
24
35
  ## Requirements
25
36
 
26
37
  - Ruby >= 2.6.0
27
38
  - No external database required
28
39
 
29
- ## Quick Start
40
+ ## Installation & Quick Start
30
41
 
31
42
  ### Installation
43
+ Install manually:
44
+ ```bash
45
+ gem install mongory
46
+ ```
47
+
48
+ Or add to your Gemfile:
49
+ ```ruby
50
+ gem 'mongory'
51
+ ```
52
+
32
53
  #### Rails Generator
33
54
 
34
55
  You can install a starter configuration with:
@@ -42,16 +63,6 @@ This will generate `config/initializers/mongory.rb` and set up:
42
63
  - Class registration (e.g. `Array`, `ActiveRecord::Relation`, etc.)
43
64
  - Custom value/key converters for your ORM
44
65
 
45
- Or install manually:
46
- ```bash
47
- gem install mongory
48
- ```
49
-
50
- Or add to your Gemfile:
51
- ```ruby
52
- gem 'mongory'
53
- ```
54
-
55
66
  ### Basic Usage
56
67
  ```ruby
57
68
  records = [
@@ -73,6 +84,47 @@ limited = records.mongory
73
84
  .where(:age.gte => 18) # Conditions apply to limited set
74
85
  ```
75
86
 
87
+ ### C Extension (Optional but Recommended)
88
+
89
+ Mongory-rb includes an optional high-performance C extension powered by [mongory-core](https://github.com/mongoryhq/mongory-core):
90
+
91
+ **System Dependencies:**
92
+ - C99-compatible compiler (gcc/clang)
93
+ - CMake >= 3.12 (optional; only needed if you want to build `mongory-core` standalone or run its native tests)
94
+
95
+ **Installation:**
96
+ ```bash
97
+ # macOS
98
+ brew install cmake
99
+
100
+ # Ubuntu/Debian
101
+ sudo apt install cmake build-essential
102
+
103
+ # CentOS/RHEL
104
+ sudo yum install cmake gcc make
105
+ ```
106
+
107
+ The C extension provides significant performance improvements for large datasets. If not available, Mongory-rb automatically falls back to pure Ruby implementation.
108
+
109
+ Note: The Ruby C extension is built via Ruby's `mkmf` (see `ext/mongory_ext/extconf.rb`) and compiles `mongory-core` sources directly. You do not need CMake for normal gem installation.
110
+
111
+ ## Positioning
112
+
113
+ Mongory is designed to serve two types of users:
114
+
115
+ 1. For MongoDB users:
116
+ - Seamless integration with familiar query syntax
117
+ - Extends query capabilities for non-indexed fields
118
+ - No additional learning cost
119
+
120
+ 2. For non-MongoDB users:
121
+ - Initial learning cost for MongoDB-style syntax
122
+ - Long-term benefits:
123
+ - Improved code readability
124
+ - Better development efficiency
125
+ - Lower maintenance costs
126
+ - Ideal for teams valuing code quality and maintainability
127
+
76
128
  ### Integration with MongoDB
77
129
 
78
130
  Mongory is designed to complement MongoDB, not replace it. Here's how to use them together:
@@ -149,77 +201,7 @@ Mongory::Matchers.register(:class_in, '$classIn', ClassInMatcher)
149
201
  You can define any matcher behavior and attach it to a `$operator` of your choice.
150
202
  Matchers can be composed, validated, and traced just like built-in ones.
151
203
 
152
- ### Handling Dots in Field Names
153
-
154
- Mongory supports field names containing dots, which require escaping:
155
-
156
- ```ruby
157
- # Sample data
158
- records = [
159
- { "user.name" => "John", "age" => 25 }, # Field name contains a dot
160
- { "user" => { "name" => "Bob" }, "age" => 30 } # Nested field
161
- ]
162
-
163
- # Field name contains a dot
164
- records.mongory.where("user\\.name" => "John") # Two backslashes needed with double quotes
165
- # => [{ "user.name" => "John", "age" => 25 }]
166
-
167
- # or
168
- records.mongory.where('user\.name' => "John") # One backslash needed with single quotes
169
- # => [{ "user.name" => "John", "age" => 25 }]
170
-
171
- # Nested field (no escaping needed)
172
- records.mongory.where("user.name" => "Bob")
173
- # => [{ "user" => { "name" => "Bob" }, "age" => 30 }]
174
- ```
175
-
176
- Note:
177
- - With double quotes, backslashes need to be escaped (`\\`)
178
- - With single quotes, backslashes don't need to be escaped (`\`)
179
- - This behavior is consistent with MongoDB's query syntax
180
- - The escaped dot pattern (`\.`) matches fields where the dot is part of the field name
181
- - The unescaped dot pattern (`.`) matches nested fields in the document structure
182
-
183
- ### Advanced Usage
184
-
185
- #### Complex Queries
186
- ```ruby
187
- # Nested conditions
188
- users.mongory
189
- .where(
190
- :age.gte => 18,
191
- :$or => [
192
- { :status => 'active' },
193
- { :status => 'pending', :created_at.gte => 1.week.ago }
194
- ]
195
- )
196
-
197
- # Using any_of for nested OR conditions
198
- users.mongory
199
- .where(:age.gte => 18)
200
- .any_of(
201
- { :status => 'active' },
202
- { :status => 'pending', :created_at.gte => 1.week.ago }
203
- )
204
-
205
- # Array operations
206
- posts.mongory
207
- .where(:tags.elem_match => { :name => 'ruby', :priority.gt => 5 })
208
- .where(:comments.every => { :approved => true })
209
- ```
210
-
211
- #### Integration with ActiveRecord
212
- ```ruby
213
- class User < ActiveRecord::Base
214
- def active_friends
215
- friends.mongory
216
- .where(:status => 'active')
217
- .where(:last_seen.gte => 1.day.ago)
218
- end
219
- end
220
- ```
221
-
222
- ### Query API Reference
204
+ ## Core Concepts & API Reference
223
205
  #### Registering Models
224
206
 
225
207
  To allow calling `.mongory` on collections, use `register`:
@@ -262,7 +244,7 @@ records.mongory
262
244
  This will share a mutatable, but stable context object to all matchers in matcher tree.
263
245
  To get your custom option, using `@context.config` in your custom matcher.
264
246
 
265
- ### Debugging Queries
247
+ ## Debugging
266
248
 
267
249
  You can use `explain` to visualize the matcher tree structure:
268
250
  ```ruby
@@ -324,52 +306,6 @@ The debug output includes:
324
306
  - Field names highlighted in gray background
325
307
  - Detailed matching process for each record
326
308
 
327
- ### Performance Considerations
328
-
329
- 1. **Memory Usage**
330
- - Mongory operates entirely in memory
331
- - Consider your data size and memory constraints
332
- - Proc-based implementation reduces memory usage
333
- - Context system provides better memory management
334
-
335
- 2. **Query Optimization**
336
- - Complex conditions are evaluated in sequence
337
- - Use `explain` to analyze query performance
338
- - Empty conditions are optimized with cached Procs
339
- - Context system allows fine-grained control over conversion
340
-
341
- 3. **Benchmarks**
342
- ```ruby
343
- # Simple query (1000 records)
344
- records.mongory.where(:age.gte => 18) # ~0.8ms
345
-
346
- # Complex query (1000 records)
347
- records.mongory.where(:$or => [{:age.gte => 18}, {:status => 'active'}]) # ~0.9ms
348
-
349
- # Simple query (10000 records)
350
- records.mongory.where(:age.gte => 18) # ~6.5ms
351
-
352
- # Complex query (10000 records)
353
- records.mongory.where(:$or => [{:age.gte => 18}, {:status => 'active'}]) # ~7.5ms
354
-
355
- # Simple query (100000 records)
356
- records.mongory.where(:age.gte => 18) # ~64.7ms
357
-
358
- # Complex query (100000 records)
359
- records.mongory.where(:$or => [{:age.gte => 18}, {:status => 'active'}]) # ~75.0ms
360
-
361
- # Complex query with fast mode (100000 records)
362
- records.mongory.where(:$or => [{:age.gte => 18}, {:status => 'active'}]).fast # ~42.8ms, about 3x of plain ruby
363
- ```
364
-
365
- Note: Performance varies based on:
366
- - Data size
367
- - Query complexity
368
- - Hardware specifications
369
- - Ruby version
370
-
371
- Test in your environment to determine if performance meets your needs.
372
-
373
309
  ### Supported Operators
374
310
 
375
311
  | Category | Operators |
@@ -387,6 +323,7 @@ Note: Some operators are Mongory-specific and not available in MongoDB:
387
323
  - Example: `where(:name.present => true)`
388
324
  - `$every`: Checks if all elements in an array match the given condition
389
325
  - Similar to `$elemMatch` but requires all elements to match
326
+ - At least one element in an array, or returns false
390
327
  - Example: `where(:tags.every => { :priority.gt => 5 })`
391
328
 
392
329
  Example:
@@ -496,37 +433,6 @@ end
496
433
  - All operations are performed in memory
497
434
  - Consider memory constraints
498
435
 
499
- ## Migration Guide
500
-
501
- 1. **From Array#select**
502
- ```ruby
503
- # Before
504
- records.select { |r| r['age'] >= 18 && r['status'] == 'active' }
505
-
506
- # After
507
- records.mongory.where(:age.gte => 18, status: 'active')
508
- ```
509
-
510
- 2. **From ActiveRecord**
511
- ```ruby
512
- # Before
513
- indexed_query.where("age >= ? AND status = ?", 18, 'active')
514
-
515
- # After
516
- indexed_query.mongory.where(:age.gte => 18, status: 'active')
517
- ```
518
-
519
- 3. **From MongoDB**
520
- ```ruby
521
- # Before (MongoDB)
522
- users.where(:age.gte => 18, status: 'active')
523
-
524
- # After (Mongory)
525
- users.mongory.where(:age.gte => 18, status: 'active')
526
-
527
- # Just the same.
528
- ```
529
-
530
436
  ## Contributing
531
437
 
532
438
  Contributions are welcome! Here's how you can help:
data/Rakefile CHANGED
@@ -9,4 +9,81 @@ require 'rubocop/rake_task'
9
9
 
10
10
  RuboCop::RakeTask.new
11
11
 
12
+ # Add support for rake-compiler if available
13
+ begin
14
+ require 'rake/extensiontask'
15
+
16
+ Rake::ExtensionTask.new('mongory_ext') do |ext|
17
+ ext.lib_dir = 'ext/mongory_ext'
18
+ ext.ext_dir = 'ext/mongory_ext'
19
+ ext.source_pattern = '*.c'
20
+ end
21
+
22
+ # Add tasks for building with submodule
23
+ namespace :submodule do
24
+ desc 'Initialize/update the mongory-core submodule'
25
+ task :init do
26
+ sh 'git submodule update --init --recursive'
27
+ end
28
+
29
+ desc 'Update the mongory-core submodule to latest'
30
+ task :update do
31
+ sh 'git submodule update --remote'
32
+ end
33
+
34
+ desc 'Build mongory-core submodule'
35
+ task :build do
36
+ core_dir = 'ext/mongory_ext/mongory-core'
37
+ if Dir.exist?(core_dir)
38
+ Dir.chdir(core_dir) do
39
+ if File.exist?('build.sh')
40
+ sh 'chmod +x build.sh && ./build.sh'
41
+ else
42
+ sh 'mkdir -p build && cd build && cmake .. && make'
43
+ end
44
+ end
45
+ else
46
+ puts 'mongory-core submodule not found. Run rake submodule:init first.'
47
+ end
48
+ end
49
+ end
50
+
51
+ desc 'Build the project (without standalone mongory-core build)'
52
+ task build_all: ['submodule:init', :compile]
53
+
54
+ desc 'Clean all build artifacts including submodule'
55
+ task clean_all: :clean do
56
+ sh 'rm -rf ext/mongory_ext/mongory-core/build' if Dir.exist?('ext/mongory_ext/mongory-core/build')
57
+ end
58
+ rescue LoadError
59
+ puts 'rake-compiler not available. Install it with: gem install rake-compiler'
60
+
61
+ # Fallback tasks without rake-compiler
62
+ desc 'Build the C extension manually'
63
+ task :compile do
64
+ Dir.chdir('ext/mongory_ext') do
65
+ sh 'ruby extconf.rb && make'
66
+ end
67
+ end
68
+
69
+ desc 'Clean the C extension manually'
70
+ task :clean do
71
+ Dir.chdir('ext/mongory_ext') do
72
+ sh 'make clean' if File.exist?('Makefile')
73
+ sh 'rm -f Makefile *.o foundations/*.o matchers/*.o mongory_ext.so'
74
+ end
75
+ end
76
+ end
77
+
78
+ # Custom build task using our build script
79
+ desc 'Build using the custom build script'
80
+ task :build_with_script do
81
+ sh 'scripts/build_with_core.sh'
82
+ end
83
+
84
+ desc 'Build in debug mode'
85
+ task :build_debug do
86
+ sh 'scripts/build_with_core.sh --debug'
87
+ end
88
+
12
89
  task default: %i(spec rubocop)