Query Interface

The Waterline Query Interface allows you to interact with your models the same way no matter which adapter they are using. This means you can use the same Query Language whether your data lives in MySQL, MongoDB, PostgreSQL, etc.

Query Methods

Every model in Waterline will have a set of query methods exposed on it to allow you to interact with the database in a normalized fashion. These are known as the CRUD (Create, Read, Update and Delete) methods and is the primary way of interacting with your data.

There are also a special set of queries known as dynamic queries. These are special class methods that are dynamically generated when you initialize Waterline. We call them dynamic finders. They perform many of the same functions as the other class methods but you can call them directly on an attribute in your model.

For most class methods, the callback parameter is optional and if one is not supplied, it will return a chainable object.

.find(criteria, [callback])

find will return an array of records that match the supplied criteria. Criteria can be built using the Query Language.

  • The criteria is required and accepts {}, [{}], string and int data types.
  • The callback function is optional.

Any string arguments passed must be the ID of the record. This method will always return records in an array. If you are trying to find an attribute that is an array, you must wrap it in an additional set of brackets otherwise Waterline will think you want to perform an inQuery.

User.find({
    name: 'Walter Jr'
  })
  .exec(function (err, users) {
    if (err) {
      console.log(err);
    }
    console.log(users);
  });

.findOne(criteria, [callback])

findOne will return an object with the first matching result in the data store.

  • The criteria is required and accepts {}, [{}], string and int data types.
  • The callback function is optional.

Any string arguments passed must be the ID of the record. If you are trying to find an attribute that is an array, you must wrap it in an additional set of brackets otherwise Waterline will think you want to perform an inQuery.

User.findOne({
    name: 'Walter Jr'
  })
  .exec(function (err, user) {
    if (err) {
      console.log(err);
    }
    console.log(user);
  });

.create(criteria, [callback])

create will attempt to create a new record in the datastore. If the data is valid and passes all validations it will be sent to the adapters create method.

  • The criteria is required and accepts {} and [{}] data types.
  • The callback function is optional.
User.create({
    name: 'Walter Jr'
  })
  .exec(function (err, user) {
    if (err) {
      console.log(err);
    }
    console.log(user);
  });

.findOrCreate(criteria, [values, callback])

findOrCreate will return a single record if one was found or created, or an array of records if multiple get found/created via the supplied criteria or values. Criteria can be built using the Query Language.

  • The criteria is required and accepts {}, [{}], string and int data types.
  • The values is optional and accepts {} and [{}] data types.
  • The callback function is optional.

Any string arguments passed must be the ID of the record. This method can return a single record or an array of records. If a model is not found and creation values are omitted, it will get created with the supplied criteria values.

Unless an adapter implements its own version of findOrCreate, findOrCreate will do the find and create calls in two separate steps (not transactional). In a high frequency scenario it's possible for duplicates to be created if the query field(s) are not indexed.

Either user(s) with the name "Walter Jr" get returned or a single user gets created with the name "Walter Jr" and returned:

User.findOrCreate({
    name: 'Walter Jr'
  })
  .exec(function (err, users) {
    if (err) {
      console.log(err);
    }
    console.log(users);
  });

.update(search criteria, values, [callback])

update will attempt to update any records matching the criteria passed in. Criteria can be built using the Query Language.

  • The criteria is required and accepts {}, [{}], string and int data types.
  • The values is required and accepts {} and [{}] data types.
  • The callback function is optional.

Although you may pass .update() an object or an array of objects, it will always return an array of objects. Any string arguments passed must be the ID of the record. If you specify a primary key instead of a criteria object, any .where() filters will be ignored.

User.update({
    name: 'Walter Jr'
  }, {
    name: 'Flynn'
  })
  .exec(function (err, user) {
    if (err) {
      console.log(err);
    }
    console.log(user);
  });

.destroy(criteria, [callback])

destroy will destroy any records matching the provided criteria. Criteria can be built using the Query Language.

  • The criteria is required and accepts {}, [{}], string and int data types.
  • The callback function is optional.

If you want to confirm the record exists before you delete it, you must first perform a .find(). Any string arguments passed must be the ID of the record.

User.destroy({
    name: 'Flynn'
  })
  .exec(function (err) {
    if (err) {
      console.log(err);
    }
  });

.query(query, [data], callback)

Some adapters, such as sails-mysql and sails-postgresql, support the query function which will run the provided RAW query against the database. This can sometimes be useful if you want to run complex queries and performance is very important.

  • The query is required and accepts string data types.
  • The data is optional and accepts array data types.
  • The callback function is required.

The type of the results returned depend on your adapter: sails-mysql returns an array of objects and sails-postgresql returns an object containing metadata and the actual results within a 'rows' array. This function does currently not support promises.

Using PostgreSQL:

const title = "The King's Speech";
Movie.query('SELECT * FROM movie WHERE title = $1', [title], function (err, results) {
  console.log('Found the following movie: ', results.rows[0]);
});

Using MySQL:

const title = "The King's Speech";
Movie.query('SELECT * FROM movie WHERE title = $1', [title], function (err, results) {
  console.log('Found the following movie: ', results[0]);
});

Query Language

The Waterline Query Language is an object based criteria used to retrieve the records from any of the supported database adapters. This allows you to change your database without changing your codebase.

All queries inside of Waterline are case insensitive. This allows for easier querying but makes indexing strings tough. This is something to be aware of if you are indexing and searching on string fields.

Query Language Basics

The criteria objects are formed using one of four types of object keys. These are the top level keys used in a query object. It is loosely based on the criteria used in MongoDB with a few slight variations.

Queries can be built using either a where key to specify attributes, which will allow you to also use query options such as limit and skip or if where is excluded the entire object will be treated as a where criteria.

User.find({
  where: {
    name: 'John'
  },
  skip: 20,
  limit: 10,
  sort: 'name DESC'
});

Or:

User.find({
  name: 'John'
});

Key Pairs

A key pair can be used to search records for values matching exactly what is specified. This is the base of a criteria object where the key represents an attribute on a model and the value is a strict equality check of the records for matching values.

User.find({
  name: 'John'
});

They can be used together to search multiple attributes:

User.find({
  name: 'John',
  country: 'France'
});

Modified Pairs

Modified pairs also have model attributes for keys but they also use any of the supported criteria modifiers to perform queries where a strict equality check wouldn't work.

User.find({
  name: {
    contains: 'alt'
  }
})

In Pairs

In queries work similarly to MySQL in queries. Each element in the array is treated as or.

User.find({
  name: ['John', 'Walter']
});

Not-In Pairs

Not-In queries work similar to in queries, except for the nested object criteria.

User.find({
  name: {
    '!': ['John', 'Walter']
  }
});

Or Pairs

Performing OR queries is done by using an array of query pairs. Results will be returned that match any of the criteria objects inside the array.

User.find({
  or: [
    {
      name: 'John'
    },
    {
      occupation: 'Developer'
    }
  ]
});

Criteria Modifiers

The following modifiers are available to use when building queries:

  • < or lessThan
  • <= or lessThanOrEqual
  • > or greaterThan
  • >= or greaterThanOrEqual
  • ! or not
  • like
  • contains
  • startsWith
  • endsWith

< or lessThan

Searches for records where the value is less than the value specified.

User.find({
  age: {
    '<': 30
  }
});

<= or lessThanOrEqual

Searches for records where the value is less or equal to the value specified.

User.find({
  age: {
    '<=': 21
  }
});

> or greaterThan

Searches for records where the value is more than the value specified.

User.find({
  age: {
    '>': 18
  }
});

>= or greaterThanOrEqual

Searches for records where the value is more or equal to the value specified.

User.find({
  age: {
    '>=': 21
  }
});

! or not

Searches for records where the value is not equal to the value specified.

User.find({
  name: {
    '!': 'John'
  }
});

like

Searches for records using pattern matching with the % sign.

User.find({
  food: {
    'like': '%burgers'
  }
});

contains

A shorthand for pattern matching both sides of a string. Will return records where the value contains the string anywhere inside of it.

User.find({
  class: {
    'like': '%history%'
  }
});
User.find({
  class: {
    'contains': 'history'
  }
});

startsWith

A shorthand for pattern matching the right side of a string Will return records where the value starts with the supplied string value.

User.find({
  class: {
    'startsWith': 'french'
  }
});
User.find({
  class: {
    'like': 'french%'
  }
});

endsWith

A shorthand for pattern matching the left side of a string. Will return records where the value ends with the supplied string value.

User.find({
  class: {
    'endsWith': 'can'
  }
});
User.find({
  class: {
    'like': '%can'
  }
});

Date Ranges

You can do date range queries using the comparison operators.

User.find({
  date: {
    '>': new Date('2/4/2014'),
    '<': new Date('2/7/2014')
  }
});

Query Options

Query options allow you refine the results that are returned from a query.

Limit results

Limit the number of results returned from a query.

User.find({
  where: {
    name: 'John'
  },
  limit: 20
});

Skip results

Return all the results excluding the number of items to skip.

User.find({
  where: {
    name: 'John'
  },
  skip: 10
});

Pagination

skip and limit can be used together to build up a pagination system.

User.find({
  where: {
    name: 'John'
  },
  limit: 10,
  skip: 10
});

Sort results

Results can be sorted by attribute name. Simply specify an attribute name for natural (ascending) sort, or specify an asc or desc flag for ascending or descending orders respectively.

Sort by name in ascending order:

User.find({
  where: {
    name: 'John'
  },
  sort: 'name'
});

Sort by name in descending order:

User.find({
  where: {
    name: 'John'
  },
  sort: 'name DESC'
});

Sort by name in ascending order:

User.find({
  where: {
    name: 'John'
  },
  sort: 'name ASC'
});

Sort by binary notation

User.find({
  where: {
    name: 'John'
  },
  sort: {
    name: 1
  }
});

Sort by multiple attributes:

User.find({
  where: {
    name: 'John'
  },
  sort: {
    name:  1,
    age: 0
  }
});

Select a field

Apply a projection to a Waterline query.

This example only returns the field name:

User.find({
  where: {
    age: {
      '<': 30
    }
  },
  select: ['name']
});