A simple query language for mongoodb. It base on mongoosejs. Everything is too easy to getting done your job. Write LESS, do MORE and Let's RELAX (after done :v)
var ReviewSchema = Schema({
author: ObjectId,
localBiz: ObjectId,
postedTime: Date,
content: String,
rating: Number,
likes: [ObjectId] //Array of users id liked this review
});
const LocalBizSchema = new Schema({
name: String,
categories: [String],
address: String,
rating: Number
});
const UserSchema = new Schema({
email: String,
displayName: String
});
const BrandSchema = new Schema({
name: String,
localBizs: [ObjectId] //Array of localbizs id of this brand
});
var Review = mongoose.model('Review', ReviewSchema);
var LocalBiz = mongoose.model('LocalBiz', LocalBizSchema);
var User = mongoose.model('User', UserSchema);
var Brand = mongoose.model('Brand', BrandSchema);
module.exports = {
Review,
LocalBiz,
User,
Brand
}
var ql = require('relax-ql');
ql.add({
models: require('./db')
});
You want get 10 reviews from database.
With mongoosejs:
Review
.find()
.limit(10)
.lean()
.exec()
.then(reviews => console.log(reviews));
With relax-ql:
ql`*: Review().limit(10)`
.exec()
.then(result => console.log(result));
The code above is a simple query. And now, we will do with a more complex query. We will get 10 reviews from database and each review contain information of reviewer.
With mongoosejs:
Review
.find()
.limit(10)
.lean()
.exec()
.then(reviews => {
return Promise.all(
reviews.map(review => new Promise((resolve, reject) => {
User.findOne({
_id: review.author
})
.lean()
.exec()
.then(user => {
review.authorDetail = user;
resolve(review);
})
.catch(err => eject(err))
}))
)
})
.then(reviews => console.log(reviews));
With relax-ql:
ql`
*: Review().limit(10)
authorDetail: User[this.author]`
.exec()
.then(reviews => console.log(reviews));
Select and projection data
With mongoosejs:
Review
.find()
.limit(10)
.lean()
.exec()
.select({
content: true,
rating: true,
author: true,
likes: {
$slice: 3
}
})
.then(reviews => {
return Promise.all(
reviews.map(review => new Promise((resolve, reject) => {
User.findOne({
_id: review.author
})
.lean()
.exec()
.select({
displayName: true,
email: true
})
.then(user => {
review.author = user;
resolve(review);
})
.catch(err => eject(err))
}))
)
})
.then(reviews => console.log(reviews));
With relax-ql:
ql`
*: Review().limit(10)
content
rating
likes(slice: 3)
author:= User[this.author]
displayName
email`
.exec()
.then(reviews => console.log(reviews));
Find 10 reviews with rating = 5
ql`*: Review(rating == 5).limit(10)`
.exec()
.then(reviews => console.log(reviews));
or
ql`*: Review.find(rating == 5).limit(10)`
FindOne review with rating >= 3
ql`*: Review[rating == 5]`
or
ql`*: Review.findOne(rating == 5)`
== : $eq
!= : $ne
< : $lt
> : $gt
<= : $lte
>= : $gte
IN : $in
NIN : $nin
in : $in
nin : $nin
&& : $and
EXISTS : $exists
TYPE : $type
exists : $exists
type : $type
MOD : $mod
REGEX : $regex
TEXT : $text
WHERE : $where
mod : $mod
regex : $regex
text : $text
where : $where
ALL : $all
MATCH : $elemMatch
SIZE: $size
all : $all
match : $elemMatch
size: $size
likeNumber >= 5 && likeNumber < 100 && rating IN [2, 3] && comments EXISTS true
parse to
{
likeNumber: {
$gte: 5,
$lt: 100
},
rating: {
$in: [2, 3]
},
comments: {
$exists: true
}
}
The bellow is rules for write query selector.
- Attribute always is left side of operator and value to compare must be right side of operator
- Currently, relax-ql not support OR logical operation. Only use and. But we absolutely write a complex query selector. I will show to you in next session.
- relax-ql support some type like: String, Number, Boolean, Array, Object, Regex. Example: "abc" or 'abc' is string, [1, 2, 3] is array of number, {a : 1} is an object, true or TRUE or False is booleam type and /abcd/ig is regex type.
- Can pass value by params, also pass a query selector by params. I'll show to you in next session.
ql`reviews: Review(likeNumber >= 5 && likeNumber < 100 && rating IN [2, 3] && comments EXISTS true)`
ql`reviews: Review(${{
$or: [{
likeNumber: 5
}, {
likeNumber: 4
}]
}})`
or use param
var reviewQuery = {
$or: [{
likeNumber: 5
}, {
likeNumber: 4
}],
rating: {
$in: [1, 2]
},
comments: {
$all: [
{ "$elemMatch" : { likeNumber: { $gt: 50, $lt: 100} } },
{ "$elemMatch" : { content : { $regex: /abcd/i } } }
]
}
}
ql`reviews: Review(${reviewQuery})`
relax-ql support limit, skip, sort (ASC, DESC)
ql`reviews: Review(likeNumber >= 5).limit(10).skip(10).DESC('rating').ASC('createdAt')`
Note: Sepecial support unlean optional. Because the query default call .lean() function. When we use unlean option, the query will not call .lean(). It very usefull when you defined some virtual atributes. Example:
Model:
var PersonSchema = new Schema({
name: {
first: String,
last: String
}
}, {
toObject: { virtuals: true },
toJSON: { virtuals: true }
});
PersonSchema
.virtual('displayName')
.get(function () {
return this.name.first + ' ' + this.name.last;
});
var Person = mongoose.model('Person', PersonSchema);
Don't use unlean()
ql`*: Person[]`
.exec()
.then(p => console.log(p));
// display on screen
/*
{
name: {
first: 'abc',
last: 'def'
}
}
*/
Use unlean() optional
ql`*: Person[].unlean()`
.exec()
.then(p => console.log(p));
// display on screen
/*
{
name: {
first: 'abc',
last: 'def'
},
displayName: 'abc def'
}
*/
ql`
reviews: Review(likeNumber >= 5)
comments
content
rating
`
or
ql`
status: Status[]
content
user
name
gender
`
It'll be
Status.findOne()
.select({
content: true,
user: true,
'user.name': true,
'user.gender': true
})
Support
slice : $slice
SLICE : $slice
match : $elemMatch
MATCH : $elemMatch
meta : $meta
META : $meta
Example:
ql`
status: Status[]
content
likes(3)
comments(slice: 3)
`
=>
Status.findOne()
.select({
content: true,
'likes.$': 3,
comments: {
$slice: 3
}
})
Example:
ql`
reviews: Review(rating >= 3).limit(5)
author
authorDetail: User[this.author]
displayName
localBiz:= LocalBiz[this.localBiz]
name
address
likes
*: User[this.$value]
displayName
relatedReviews: Review(localBiz == this.localBiz).limit(3)
content
author:= User[this.author]
displayName
`
- this: The pointer to parent
- this.$value: The value of parent
- := is select this attribute and result of query will overide to old value.
- '*' is result of child query will overide to parent value
Can you see the format of relax-ql like this:
ql`
key_name: Model.function(query_selector).optional().optional()
attr_selected
attr_selected
key_name: Model.function(query_selector).optional().optional()
attr_selected
attr_selected
attr_selected:= Model.function(query_selector).optional().optional()
attr_selected
key_name: Model.function(query_selector).optional().optional()
attr_selected
attr_selected
attr_selected
*: Model.function(query_selector).optional().optional()
attr_selected
attr_selected
`
We have two way to access to parent data.
- Use this
- It will access to nearest parent data
- Use key_name
- It will access to first parent match with key_name
- Support all logical: AND, OR, NOT
- Optimize query findOne
- Fix projection: likes({$slice: 3})