Usage
NoSQLBooster for MongoDB supports mongoose-like fluent query builder API that enables you to build up a query using chaining syntax, rather than specifying a JSON object. All MongoDB query, projection and aggregate stage operators have a corresponding chainable method. NoSQLBooster for MongoDB also adds a few cursor extension methods to return the translated MongoDB Query shell script(cursor.getShellScript) and saves the underlying aggregation pipeline as a MongoDB readonly view (cursor.saveAsView).
// instead of writing:
db.user.find({age:{$gte:18,$lte:65}},{name:1,age:1,_id:0}).sort({age:-1, name:1});
// we can write:
db.user.where('age').gte(18).lte(65).select('name age -_id').sort("-age name");
// passing query conditions is permitted too
db.collection.find().where({ name: 'nosqlbooster' })
// chaining
db.collection
.where('age').gte(18).lte(65)
.where({ 'name': /^nosqlbooster/i })
.where('friends').slice(10)
Chaining aggregate stage operator and use cursor.getShellScript to get translated mongo shell script. The $.xxx1().xxx2() method translates chaining MongoDB query operators method to a JSON object.
// aggregation
db.companies.aggregate()
.match($.where('founded_year').gte(2000).lte(2010)) //$: operator helper
.group({_id:"$category_code",count:{$sum:1}})
.sort('-count')
.limit(100)
.getShellScript() //get translated mongo shell script
Operator Helper ($)
NoSQLBooster exposes a new global variable, $, which converts chained MongoDB operator methods into a JSON object. We call it the operator helper. It upgrades the query builder(qb) from previous versions to support all MongoDB operators, not just operators for the db.collection.find methods. You can use it directly in any mongosh method. NoSQLBooster provides a large number of code templates to simplify the input of method parameters. The form of the chained method is also easier to read and error check.
Here are a few simple rules for using operator helper
- Except for the $.where("field") method, all the other methods are simple and correspond to an operator of MongoDB. e.g. $.gte(xxx) -> { $gte: xxx }
- If the method has only one parameter, a key-value pair is generated directly. e.g. $.gte(5) -> {$gte: 5}.
- If the method has more than one parameter, the parameters is converted to an array in the generated JSON object. e.g. $.gte("$f1", 5) -> {$gte: ["$f1", 5] }
Let's take a look at some examples.
print($.gte(18).lte(65)); // Operator Helper -> { $gte:18, $lte:65 }
print($.gte(18).lte(65).build()); // got plain JSON object, Can be used in non mongosh method, { $gte:18, $lte:65 }
print($.gte("$age", 18)); // -> { $gte: ["$age", 18] }
// instead of writing:
db.users.findOne({age:{$gte:18,$lte:65}};
// we can write:
db.users.findOne($.where("age").gte(18).lte(65));
// instead of writing:
db.users.update({_id:1}, {$set: {lastLoginTime: new Date()}, $inc: {numberOfLogins: 1}});
// we can write:
db.users.update({_id:1}, $.set({lastLoginTime: new Date()}).inc({numberOfLogins: 1}));
//instead of writing:
db.inventory.aggregate([{
$project: {
item: 1,
qty: 1,
qtyGte250Lt500: {
$and: [{
$gte: ["$qty", 250]
}, {
$lt: ["$qty", 300]
}]
},
_id: 0
}
}, {
$limit: 1000
}])
//we can write:
db.inventory.aggregate().project({
item: 1,
qty: 1,
qtyGte250Lt500: $.and($.gte("$qty",250), $.lt("$qty", 300))
_id: 0
}).limit(1000)
//or
db.inventory.aggregate().project(
$.where("item",1)
.where("qty",1)
.where("qtyGte250Lt500").and($.gte("$qty",250), $.lt("$qty", 300))
.where("_id",0)
).limit(1000)
We can use getShellScript() method to get equivalent MongoShell Syntax
db.survey.where("results").elemMatch($.where("item").eq("b").where("score").gt(8))
.getShellScript();
Here is the output
db.survey.find({
"results" : {
"$elemMatch" : {
"item" : "b",
"score" : {
"$gt" : 8
}
}
}
})
Pipeline Builder ($.pipelineBuilder)
In previous versions, we have implemented chained aggregate pipeline builder, all MongoDB aggregate stage have a corresponding chainable method.
To create an aggregation pipeline, use the following syntax in the MongoDB Shell:
db.<collection>.aggregate([
{
<$stage1>
},
{
<$stage2>
}
{
<$stage3>
}
//...
])
We can write in NoSQLBooster:
db.<collection>.aggregate()
.stage1()
.stage2()
.stage3()
In version 8+, we enhanced the use scenario of the chained methods, which you can use it to build the pipeline parameter required in some mongodb shell methods or use it in your code.
//instead of writing:
let pipeline=[
{
<$stage1>
},
{
<$stage2>
}
{
<$stage3>
}
//...
]
//we can write:
pipeline= $.pipelineBuilder.stage1().stage2().stage3()
Let's take a look at an example of creating a readonly view using $.pipelineBuilder.
//instead of writing:
db.createView(
"firstYearsScoreDesc",
"students",
[ { $match: { year: 1 } }, {$sort :{score: -1}} ] //pipeline
)
//we can write:
db.createView(
"firstYearsScoreDesc",
"students",
$.pipelineBuilder.match({year:1}).sort({score: -1}) //pipeline
)
Cursor Extension Methods
cursor.getShellScript()
Cursor.getShellScript() method return the MongoDB find shell script produced by the NoSQLBooster for MongoDB Fluent Query Builder.
For example:
Run the following Fluent query script in NoSQLBooster for MongoDB:
db.user.where('age').gte(18).lte(65)
.select('name age -_id')
.sort("-age name")
.limit(5)
.skip(100)
.getShellScript()
We get the MongoDB shell find script :
db.user.find({
"age" : {
"$gte" : 18,
"$lte" : 65
}
}, {
"name" : 1,
"age" : 1,
"_id" : 0
})
.sort({ "age" : -1, "name" : 1 })
.limit(5)
.skip(100)
Run the following aggregate Fluent Query script in NoSQLBooster for MongoDB:
db.companies.aggregate($.where('founded_year').gte(2000).lte(2010))
.group({_id:"$category_code",count:{$sum:1}})
.sort('-count')
.skip(100)
.limit(5)
.getShellScript()
We get the MongoDB shell aggregate script :
db.companies.aggregate(
[
{
"$match" : {
"founded_year" : {
"$gte" : 2000,
"$lte" : 2010
}
}
},
{
"$group" : {
"_id" : "$category_code",
"count" : {
"$sum" : 1
}
}
},
{
"$sort" : {
"count" : -1
}
},
{
"$skip" : 100
},
{
"$limit" : 5
}
])
###cursor.getAggregationPipeline() Cursor.getAggregationPipeline() returns the aggregation pipeline produced by the NoSQLBooster for MongoDB Fluent Query Builder.
db.user.where('age').gte(18).lte(65)
.select('name age -_id')
.sort("-age name")
.limit(5)
.skip(100)
.getAggregationPipeline()
We get the following MongoDB shell aggregate pipeline:
[
{
"$match" : {
"age" : {
"$gte" : 18,
"$lte" : 65
}
}
},
{
"$project" : {
"name" : 1,
"age" : 1,
"_id" : 0
}
},
{
"$sort" : {
"age" : -1,
"name" : 1
}
},
{
"$skip" : 100
},
{
"$limit" : 5
}
]
###cursor.saveAsView()
Cursor.saveAsView() method saves find/aggregate cursor as a MongoDB read-only view. The method requires MongoDB 3.4 and above.
db.user.where('age').gte(18).lte(65)
.select('name age -_id')
.sort("-age name")
.limit(5)
.skip(100)
.saveAsView("user_view", {dropIfExists:true}) //drop view if it exists.
db.companies.aggregate($.where('founded_year').gte(2000).lte(2010))
.group({_id:"$category_code",count:{$sum:1}})
.sort('-count')
.skip(100)
.limit(5)
.saveAsView("companies_view", {dropIfExists:true}) //drop view if it exists.
Query and Projection
###all()
Specifies a $all query condition
db.collection.where('permission').all(['read', 'write'])
###and()
Specifies arguments for a $and condition
db.collection.and([{ color: 'green' }, { status: 'ok' }])
###bitsAllSet()
Specifies arguments for a $bitsAllSet condition, $bitsAllSet matches documents where all of the bit positions given by the query are set (i.e., 1) in the field.
db.collection.where("a").bitsAllSet([ 1, 5 ])
###bitsAnySet()
Specifies arguments for a $bitsAnySet condition, $bitsAnySet matches documents where any of the bit positions given by the query are set (i.e., 1) in the field.
db.collection.where("a").bitsAnySet([ 1, 5 ])
###bitsAllClear()
Specifies arguments for a $bitsAllClear condition, $bitsAllClear matches documents where all of the bit positions given by the query are clear (i.e., 0) in the field.
db.collection.where("a").bitsAllClear([ 1, 5 ])
###bitsAnyClear()
Specifies arguments for a $bitsAnyClear condition, $bitsAnyClear matches documents where any of the bit positions given by the query are clear (i.e., 0) in the field.
db.collection.where("a").bitsAllClear([ 1, 5 ])
###box()
Specifies a $box condition
var lowerLeft = [40.73083, -73.99756]
var upperRight= [40.741404, -73.988135]
db.collection.where('location').within().box(lowerLeft, upperRight)
###circle()
Specifies a $center or $centerSphere condition.
var area = { center: [50, 50], radius: 10, unique: true }
query.where('loc').within().circle(area)
query.circle('loc', area);
// for spherical calculations
var area = { center: [50, 50], radius: 10, unique: true, spherical: true }
query.where('loc').within().circle(area)
query.circle('loc', area);
###elemMatch()
Specifies an $elemMatch condition
query.where('comment').elemMatch({ author: 'autobot', votes: {$gte: 5}})
query.elemMatch('comment', function (elem) {
elem.where('author').equals('autobot');
elem.where('votes').gte(5);
})
###eq()
Specifies the equivalent comparison value for the path specified with where().
db.collection.where('age').eq(49);
db.collection.where('age').equals(49);
// is the same as
db.collection.where({ 'age': 49 });
###equals()
Specifies the equivalent comparison value for the path specified with where().
db.collection.where('age').equals(49);
db.collection.where('age').eq(49);
// is the same as
db.collection.where({ 'age': 49 });
###exists()
Specifies an $exists condition
// { name: { $exists: true }}
db.collection.where('name').exists()
db.collection.where('name').exists(true)
db.collection.exists('name')
// { name: { $exists: false }}
db.collection.where('name').exists(false);
db.collection.exists('name', false);
###expr()
Specifies a $expr condition
query.expr({ $gt: [ "$spent" , "$budget" ] })
###geoWithin()
Sets a $geoWithin argument for geo-spatial queries.
alias within
db.collection.geoWithin().box()
db.collection.geoWithin().circle()
db.collection.geoWithin().geometry()
db.collection.where('loc').geoWithin({ center: [50,50], radius: 10, unique: true, spherical: true });
db.collection.where('loc').geoWithin({ box: [[40.73, -73.9], [40.7, -73.988]] });
db.collection.where('loc').geoWithin({ polygon: [[],[],[],[]] });
db.collection.where('loc').geoWithin([], [], []) // polygon
db.collection.where('loc').geoWithin([], []) // box
db.collection.where('loc').geoWithin({ type: 'LineString', coordinates: [...] }); // geometry
Must be used after where().
###geometry()
Specifies a $geometry condition
var polyA = [[[ 10, 20 ], [ 10, 40 ], [ 30, 40 ], [ 30, 20 ]]]
query.where('loc').within().geometry({ type: 'Polygon', coordinates: polyA })
// or
var polyB = [[ 0, 0 ], [ 1, 1 ]]
query.where('loc').within().geometry({ type: 'LineString', coordinates: polyB })
// or
var polyC = [ 0, 0 ]
query.where('loc').within().geometry({ type: 'Point', coordinates: polyC })
// or
query.where('loc').intersects().geometry({ type: 'Point', coordinates: polyC })
// or
query.where('loc').near().geometry({ type: 'Point', coordinates: [3,5] })
geometry() must come after intersects(), within(), or near().
The object argument must contain type and coordinates properties.
- type
String - coordinates
Array
###gt()
Specifies a $gt query condition.
db.collection.where('clicks').gt(999)
###gte()
Specifies a $gte query condition.
db.collection.where('clicks').gte(1000)
###in()
Specifies a $in query condition.
db.collection.where('author_id').in([3, 48901, 761])
###intersects()
Declares an $geoIntersects query for geometry().
query.where('path').intersects().geometry({
type: 'LineString'
, coordinates: [[180.0, 11.0], [180, 9.0]]
})
// geometry arguments are supported
query.where('path').intersects({
type: 'LineString'
, coordinates: [[180.0, 11.0], [180, 9.0]]
})
Must be used after where().
###jsonSchema()
Specifies a $jsonSchema query condition. The $jsonSchema operator matches documents that satisfy the specified JSON Schema.
let myschema = {
required: [ "item", "qty", "instock" ],
properties: {
item: { bsonType: "string" },
qty: { bsonType: "int" },
instock: { bsonType: "bool" }
}
}
query.jsonSchema(myschema); //satisfy the schema
query.nor($.jsonSchema(myschema)); //do not satisfy the schema
###lt()
Specifies a $lt query condition.
db.collection.where('clicks').lt(50)
###lte()
Specifies a $lte query condition.
db.collection.where('clicks').lte(49)
###maxDistance()
Specifies a $maxDistance query condition.
db.collection.where('location').near({ center: [139, 74.3] }).maxDistance(5)
###mod()
Specifies a $mod condition
db.collection.where('count').mod(2, 0)
###ne()
Specifies a $ne query condition.
db.collection.where('status').ne('ok')
###nin()
Specifies a $nin query condition.
db.collection.where('author_id').nin([3, 48901, 761])
###nor()
Specifies arguments for a $nor condition.
db.collection.nor([{ color: 'green' }, { status: 'ok' }])
###not()
Specifies arguments for a $not condition.
db.inventory.where("price").not({ $gt: 1.99 });
db.inventory.where("item").not(/^p.*/);
###near()
Specifies arguments for a $near or $nearSphere condition.
These operators return documents sorted by the distance.
####Example
query.where('loc').near({ center: [10, 10] });
query.where('loc').near({ center: [10, 10], maxDistance: 5 });
query.near('loc', { center: [10, 10], maxDistance: 5 });
// GeoJSON
query.where('loc').near({ center: { type: 'Point', coordinates: [10, 10] }});
query.where('loc').near({ center: { type: 'Point', coordinates: [10, 10] }, maxDistance: 5, spherical: true });
query.where('loc').near().geometry({ type: 'Point', coordinates: [10, 10] });
// For a $nearSphere condition, pass the `spherical` option.
query.near({ center: [10, 10], maxDistance: 5, spherical: true });
###or()
Specifies arguments for a $or condition.
db.collection.or([{ color: 'red' }, { status: 'emergency' }])
###polygon()
Specifies a $polygon condition
db.collection.where('loc').within().polygon([10,20], [13, 25], [7,15])
db.collection.polygon('loc', [10,20], [13, 25], [7,15])
###regex()
Specifies a $regex query condition.
db.collection.where('name').regex(/^sixstepsrecords/)
###select()
Specifies which document fields to include or exclude
// 1 means include, 0 means exclude
db.collection.select({ name: 1, address: 1, _id: 0 })
// or
db.collection.select('name address -_id')
#####String syntax
When passing a string, prefixing a path with - will flag that path as excluded. When a path does not have the - prefix, it is included.
// include a and b, exclude c
query.select('a b -c');
// or you may use object notation, useful when
// you have keys already prefixed with a "-"
query.select({a: 1, b: 1, c: 0});
Cannot be used with distinct().
###$size()
Specifies a $size query condition.
db.collection.where('someArray').$size(6)
###slice()
Specifies a $slice projection for a path
db.collection.where('comments').slice(5)
db.collection.where('comments').slice(-5)
db.collection.where('comments').slice([-10, 5])
###type()
Specifies a $type projection for a path
db.addressBook.where("zipCode").type(2);
db.addressBook.where("zipCode").type("string");
BSON supports data types as values in documents.
###text()
Specifies a $text query condition. The $text operator performs a text search on the content of the fields indexed with a text index.
query.text({ $search: "Coffee", $caseSensitive: true })
###within()
Sets a $geoWithin or $within argument for geo-spatial queries.
db.collection.where('loc').within({ center: [50,50], radius: 10, unique: true, spherical: true });
db.collection.where('loc').within({ box: [[40.73, -73.9], [40.7, -73.988]] });
db.collection.where('loc').within({ polygon: [[],[],[],[]] });
db.collection.where('loc').geoWithin({ polygon: [[],[],[],[]] });
db.collection.where('loc').within([], [], []) // polygon
db.collection.where('loc').within([], []) // box
db.collection.where('loc').within({ type: 'LineString', coordinates: [...] }); // geometry
db.collection.where('loc').geoWithin({ type: 'LineString', coordinates: [...] }); // geometry
Must be used after where().
###where()
Specifies a path for use with chaining
// instead of writing:
db.collection.find({age: {$gte: 18, $lte: 65}});
// we can instead write:
db.collection.where('age').gte(18).lte(65);
// passing query conditions is permitted too
db.collection.find().where({ name: 'vonderful' })
// chaining
db.collection
.where('age').gte(18).lte(65)
.where({ 'name': /^vonderful/i })
.where('friends').slice(10)
###$where()
Specifies a $where condition.
Use $where when you need to select documents using a JavaScript expression.
query.$where('this.comments.length > 10 || this.name.length > 5')
query.$where(function () {
return this.comments.length > 10 || this.name.length > 5;
})
Only use $where when you have a condition that cannot be met using other MongoDB operators like $lt. Be sure to read about all of its caveats before using.
###batchSize()
Specifies the batch size option.
query.batchSize(100)
Cannot be used with distinct().
###comment()
Specifies the comment option.
query.comment('login query');
Cannot be used with distinct().
###hint()
Sets query hints.
db.collection.hint({ indexA: 1, indexB: -1 })
Cannot be used with distinct().
###limit()
Specifies the limit option.
query.limit(20)
Cannot be used with distinct().
###maxScan()
Specifies the maxScan option.
query.maxScan(100)
Cannot be used with distinct().
###maxTime()
Specifies the maxTimeMS option.
query.maxTime(100)
###skip()
Specifies the skip option.
query.skip(100).limit(20)
Cannot be used with distinct().
###sort()
Sets the query sort order.
query.sort({ field: 'asc', test: -1 });
query.sort('field -test');
query.sort([['field', 1], ['test', -1]]);
Cannot be used with distinct().
###snapshot()
Specifies this query as a snapshot query.
db.collection.snapshot() // true
db.collection.snapshot(true)
db.collection.snapshot(false)
Cannot be used with distinct().
###tailable()
Sets tailable option.
db.collection.tailable() <== true
db.collection.tailable(true)
db.collection.tailable(false)
Cannot be used with distinct().
Aggregation Pipeline
All MongoDB Aggregate Stage Operators have a corresponding chainable method. You can use it as currently documented or via the chainable methods.
Calling aggregate without an array of operations or $operations will make it a match.
// matches every document
db.collection.aggregate()
db.collection.aggregate({})
// matches documents where the "a" is equal to 1
db.collection.aggregate({a: 1})
// matches documents where "a" is greater than 7
db.collection.aggregate({a: {$gt: 7}})
Additional methods can then be chained on top of the initial match to make more complicated aggregations.
// Match and project
db.collection.aggregate(<querydoc>).project(<projection>)
db.collection.aggregate({a: 1}).project({a: 1, _id: 0})
// Match, group and sort
db.collection.aggregate({<match>}).group({<group>}).sort({<sort>})
db.test.aggregate().group({_id: '$a', 'sum': {'$sum': 1}}).sort({sum: -1})
db.companies.aggregate()
.match($.where('founded_year').gte(2000).lte(2010)) //qb:querybuilder
.group({_id:"$category_code",count:{$sum:1}})
.sort('-count')
.limit(100)
###match()
Add a $match stage to the aggregation pipeline. Often, we use operator helper($) to construct a match condition JSON doc.
db.orders.aggregate().match($.where("status").eq("A")).project("gender _id"))
###project()
Add a $project stage to the aggregation pipeline. Specify which document fields to include or exclude
// 1 means include, 0 means exclude
db.collection.project({ name: 1, address: 1, _id: 0 })
//or prefixing a path with - will flag that path as excluded.
db.collection.project('name address -_id')
###group()
Add a $group stage to the aggregation pipeline. Group documents by some specified expression and output to the next stage a document for each distinct grouping. The output documents contain a _id field which contains the distinct group by key.
db.orders.aggregate().group({ _id: "$gender", count: { $sum: 1 } })
###sort()
Add a $sort stage to the aggregation pipeline. Sort all input documents and return them to the pipeline in sorted order.
// these are equivalent
db.collection.aggregate().sort({ field: 'asc', test: -1 });
db.collection.aggregate().sort('field -test');
db.collection.aggregate().sort([['field', 1], ['test', -1]]);
###Other stage operators All MongoDB Aggregate Stage Operators have a corresponding chainable method. Please refer to MongoDB documents
db.scores.aggregate()
.match($.where("status").eq("A"))
.project("gender _id")
.addFields({ totalScore:
{ $add: [ "$totalHomework", "$totalQuiz", "$extraCredit" ] } }
})
.unwind("$arrayField")
.group({ _id: "$gender", count: { $sum: 1 } })
.sort("-count")
.limit(5)
