relax-ql

1.3.5 • Public • Published

What is relax-ql?

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)

Models

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
}

Setup relax-ql

var ql = require('relax-ql');
ql.add({
  models: require('./db')
});

Example 1

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));

Example 2

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));

Example 3

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));

So, what is relax-ql can do?

Begin with Find or FindOne

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)`

Support query selectors

Comparison

== : $eq
!= : $ne
< : $lt
> : $gt
<= : $lte
>= : $gte
IN : $in
NIN : $nin
in : $in
nin : $nin

Logical

&& : $and

Element

EXISTS : $exists
TYPE : $type
exists : $exists
type : $type

Evaluation

MOD : $mod
REGEX : $regex
TEXT : $text
WHERE : $where
mod : $mod
regex : $regex
text : $text
where : $where

Array

ALL : $all
MATCH : $elemMatch
SIZE: $size
all : $all
match : $elemMatch
size: $size

Example query selectors

Example 1

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
  }
}

How to write a query selector

The bellow is rules for write query selector.

  1. Attribute always is left side of operator and value to compare must be right side of operator
  2. 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.
  3. 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.
  4. Can pass value by params, also pass a query selector by params. I'll show to you in next session.

Advance Find and FindOne

Find with complex query selector

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})`

Find with options

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'
}
*/

Selection and projection

Selection

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
  })

Projection

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
    }
  })

Nested query

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
`

Keyword:

  • 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

How to access data of parent

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.

  1. Use this
  • It will access to nearest parent data
  1. Use key_name
  • It will access to first parent match with key_name

Roadmap

  • Support all logical: AND, OR, NOT
  • Optimize query findOne
  • Fix projection: likes({$slice: 3})

Package Sidebar

Install

npm i relax-ql

Weekly Downloads

1

Version

1.3.5

License

ISC

Last publish

Collaborators

  • tunght91