Query

A query is created when you call a method on a Model or by calling

var query = new thinky.Query(Model, rawQuery);

A Query object is a wrapper of an actual ReQL query and a model.
The model is used only if you call the run method. The results returned by the database will be converted to instances of the model stored.


getJoin

query.getJoin(modelToGet) -> query

Retrieve the joined documents of the given model.

By default, if modelToGet is not provided, getJoin will keep recursing and will retrieve all the joined documents.
To avoid infinite recursion, getJoin will not recurse in a field that contains a document from a model that was previously fetched.

The option modelToGet can be an object where each field is a joined document that will also be retrieved. Two options are also available:

  • _apply: The function to apply on the joined sequence/document
  • _array: Set it to false to not coerce the joined sequence to an array

For example you can have

Users.getJoin({
    account: true // retrieve the joined document that will be stored in account
})
Users.getJoin({
    accounts: {
        _apply: function(sequence) {
            return sequence.orderBy("sold") // Retrieve all the accounts ordered by sold
        }
    },
    company: true // Retrieve the company of the user
})

Example: Retrieve a user and its account

var User = thinky.createModel("User", {
    id: type.string(),
    name: type.string()
});

var Account = thinky.createModel("Account", {
    id: type.string(),
    userId: type.string(),
    sold: type.number()
});

User.hasOne(Account, "account", "id", "userId")

User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a").getJoin({account: true})
    .run().then(function(user) {

    /*
     * user = {
     *     id: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
     *     name: "Michel",
     *     account: {
     *         id: "3851d8b4-5358-43f2-ba23-f4d481358901",
     *         userId: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
     *         sold: 2420
     *     }
     * }
     */
});

Example: Retrieve a user and the number of accounts

var User = thinky.createModel("User", {
    id: type.string(),
    name: type.string()
});

var Account = thinky.createModel("Account", {
    id: type.string(),
    userId: type.string(),
    sold: type.number()
});

User.hasMany(Account, "accounts", "id", "userId")

User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a").getJoin({
  _apply: function(seq) { return seq.count() },
  _array: false
}).run().then(function(user) {

    /*
     * user = {
     *     id: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
     *     name: "Michel",
     *     account: 3
     * }
     */
});

Example: Retrieve a user and only its account.

var User = thinky.createModel("User", {
    id: type.string(),
    name: type.string()
});

var Account = thinky.createModel("Account", {
    id: type.string(),
    userId: type.string(),
    sold: type.number()
});

User.hasOne(Account, "account", "id", "userId")

User.hasAndBelongsToMany(User, "friends", "id", "id")


User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a").getJoin({account: true})
    .run().then(function(user) {

    /*
     * user = {
     *     id: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
     *     name: "Michel",
     *     account: {
     *         id: "3851d8b4-5358-43f2-ba23-f4d481358901",
     *         userId: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
     *         sold: 2420
     *     }
     * }
     */
});

Example Retrieve a user and all two accounts with the smallest sold.

var User = thinky.createModel("User", {
    id: type.string(),
    name: type.string()
});

var Account = thinky.createModel("Account", {
    id: type.string(),
    userId: type.string(),
    sold: type.number()
});

User.hasMany(Account, "accounts", "id", "userId")

User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a").getJoin({
    accounts: {
        _apply: function(sequence) {
            return sequence.orderBy('sold').limit(2)
        }
    }
}).run().then(function(user) {
    /*
     * user = {
     *     id: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
     *     name: "Michel",
     *     accounts: [{
     *         id: "3851d8b4-5358-43f2-ba23-f4d481358901",
     *         userId: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
     *         sold: 2420
     *     }, {
     *         id: "db7ac1e8-0160-4e57-bf98-144ad5f93feb",
     *         userId: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
     *         sold: 5832
     *     }]
     * }
     */
});

Example: Retrieve a user, its account and its solds.

var User = thinky.createModel("User", {
    id: type.string(),
    name: type.string()
});

var Account = thinky.createModel("Account", {
    id: type.string(),
    userId: type.string(),
    sold: type.number()
});

User.hasOne(Account, "account", "id", "userId");
User.hasAndBelongsToMany(User, "friends", "id", "id");

var Bill = thinky.createModel("Bill", {
    id: type.string(),
    sold: type.number(),
    accountId: type.string()
});

Account.hasMany(Bill, "bills", "id", "accountId");

User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a").getJoin({account: {bills: true}})
    .run().then(function(user) {

    /*
     * user = {
     *     id: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
     *     name: "Michel",
     *     account: {
     *         id: "3851d8b4-5358-43f2-ba23-f4d481358901",
     *         userId: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
     *         sold: 2420
     *         bills: [
     *             {
     *                 id: "6b48ca51-6363-4065-8acf-497454da9616",
     *                 sold: 421,
     *                 accountId: "3851d8b4-5358-43f2-ba23-f4d481358901"
     *             },
     *             {
     *                 id: "d2d79e33-e65f-4043-b214-ca190f5e7d52",
     *                 sold: 921,
     *                 accountId: "3851d8b4-5358-43f2-ba23-f4d481358901"
     *             },
     *             {
     *                 id: "279ac0f5-b249-4a34-a873-ecd318468dac",
     *                 sold: 185,
     *                 accountId: "3851d8b4-5358-43f2-ba23-f4d481358901"
     *             }
     *         ]
     *     }
     * }
     */
});

addRelation

query.addRelation(field, joinedDocument) -> Promise

Add a relation for the given joinedDocument returned by query. This method does not saved both documents, only create the relation between the two of them. The value for joinedDocument must be an object with different requirement depending of the relation:

  • for a hasOne relation, the primary key of the document must be defined
  • for a hasMany relation, the primary key of the document must be defined
  • for a belongsTo relation, the primary key of the document or the value of the right key must be defined
  • for a hasAndBelongsToMany relation, the primary key of the document or the value of the right key must be defined

The promise get resolved with different value depending of the relation:

  • for a hasOne relation, the joined document is returned
  • for a hasMany relation, the joined document is returned
  • for a belongsTo relation, the document returned by query is returned.
  • for a hasAndBelongsToMany relation, true is returned.

Note that query must return a unique object, not a sequence.

Example: Link the user with id 0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a with the account which id is 3851d8b4-5358-43f2-ba23-f4d481358901 for a hasOne relation.

var User = thinky.createModel("User", {
    id: type.string(),
    name: type.string()
});

var Account = thinky.createModel("Account", {
    id: type.string(),
    userId: type.string(),
    sold: type.number()
});

User.hasOne(Account, "account", "id", "userId")

User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a")
    .addRelation("account", {id: "3851d8b4-5358-43f2-ba23-f4d481358901"})

Example: Link the user with id 0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a with the account which id is 3851d8b4-5358-43f2-ba23-f4d481358901 for a belongsTo relation

var User = thinky.createModel("User", {
    id: type.string(),
    name: type.string(),
    _account: type.string()
});

var Account = thinky.createModel("Account", {
    id: type.string(),
    sold: type.number(),
    accountNumber: type.string()
});

User.hasOne(Account, "account", "_account", "accountNumber")

User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a")
    .addRelation("account", {accountNumber: "XXXX-XXXX-XXXX"})

// Or with the primary key
User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a")
    .addRelation("account", {id: "3851d8b4-5358-43f2-ba23-f4d481358901"})

Example: Make two users friends

var User = thinky.createModel("User", {
    id: type.string(),
    name: type.string()
});

User.hasAndBelongsToMany(User, "friends", "id", "id")

User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a")
    .addRelation("friends", {id: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a"})

removeRelation

query.removeRelation(field[, joinedDocument]) -> query

Remove the relation for the joinedDocument returned by query stored in field. By default, all the relations are destroyed. The argument joinedDocument can be defined for hasMany and hasAndBelongsToMany relations to remove a unique relation.

  • For hasMany, the joinedDocument must be an object with the primary key of the joined document.
  • For hasAndBelongsToMany, the joinedDocument must be an object with the primary key of the joined document or the right key.

Example: Remove an account from a user

var User = thinky.createModel("User", {
    id: type.string(),
    name: type.string()
});

var Account = thinky.createModel("Account", {
    id: type.string(),
    userId: type.string(),
    sold: type.number()
});

User.hasOne(Account, "account", "id", "userId")

User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a")
    .addRelation("account")
    .run()

Example: Remove all the friends of a given user

var User = thinky.createModel("User", {
    id: type.string(),
    name: type.string()
});

User.hasAndBelongsToMany(User, "friends", "id", "id")

User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a")
    .removeRelation("friends")
    .run()

Example: Delete one friendship

var User = thinky.createModel("User", {
    id: type.string(),
    name: type.string()
});

User.hasAndBelongsToMany(User, "friends", "id", "id")

User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a")
    .removeRelation("friends", {id: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a"})
    .run()

run

query.run([options][, callback]) -> Promise

Execute the query and convert the results as instances of the model. A cursor will automatically be replaced by the array of all the results.

If you do not want to use a promise, you can directly pass a callback to run.

Example: Retrieve all the users

User.run().then(function(result) {
    // process `result`
})

execute

query.execute([options][, callback]) -> Promise

Execute the query but do not parse the response from the server, for example a raw cursor can be returned.

If you do not want to use a promise, you can directly pass a callback to execute.

Example: Return all the ids of the users

User.map(r.row("id")).execute().then(function(cursor) {
    cursor.each(function(err, userId) {
        console.log(userId);
    }
})

Note: The same query with run would have thrown an error because a string cannot be converted to an instance of User.


ReQL methods

All the methods defined in ReQL can be called on a Query object.

The methods filter and orderBy can be automatically optimized. A model will automatically fetch the indexes of its table, and at the time you call filter or orderBy (and not when you call run), if an index is available, thinky will automatically use an index.

The command filter is optimized only in case an object is passed as the first argument. The first field in lexicographic order that match an index will be replaced with getAll.

The command orderBy is optimized only when a string is passed as the first argument.

Note: The current behavior is to look at the indexes of the model stored in the query. If you use r.table(Model.getTableName()) instead of Model in a nested query, you may have unexpected/broken optimizations. Use an anonymous function if you need to prevent thinky from optimizing your query.

Note: If you created your table with {init: false} indexes will not be fetched, and the optimizer will not be as efficient as it could without the init option.

Example: Return the number of users

User.count().execute().then(function(total) {
    console.log(total+" users in the database");
});

Example: Return the users who are exactly 18 years old.

User.filter({age: 18}).run().then(function(result) {
    // result is an array of instances of `User`
});

Overwritten ReQL methods

A few methods have slightly different behavior than the original ReQL commands.

  • The get command will return an error if no document is found (instead of null). This lets you easily chain commands after get without having to use r.branch.
User.get(1).run().then(function(result) {
    // ...
});
  • The commands update and replace will have their first argument partially validated, if the first argument is an object. The validation will be performed with all fields set as optional. Once the queries has been executed, thinky will validate the returned values, and if an error occur during validation, the changes will be reverted (so another query will be issued).

Note: Because reverting the changes require a round trip, this operation is not atomic and may overwrite another write.

Note: If your queries does update something, you will get for a point-write (.get(...).update/replace(...)):

  • the updated document if the query did update the document
  • undefined if no document is updated

For a range write (table.update/replace, table.filter(...).update/replace), you will get an array of the updated documents. If no document is updated, you will get an empty array.

Typically, this may result in the document being {id: 1, foo: "bar"}.

Model = thinky.createModel("User", {
    id: type.number(),
    age: type.number()
});

var promises = [];
// Suppose that the document is {id: 1, age: 18}
promises.push(Model.get(1).update({age: r.expr("string")}).run());
promises.push(Model.get(1).update({age: 20).run());

/*
What may happen is:
- The document becomes {id: 1, age: "string"}
- The document becomes {id: 1, age: 20}
- The document is reverted to {id: 1, age: 18}
*/

Functional Utilities

Thinky provides some Functional instance methods for easily plugging Queries into functional computation chains or pipelines that handle promises or node-style callbacks such as Ramda.js or async for example.

  • Query.prototype.bindRun()

returns an instance of Query.prototype.run bound to the current instance of Query

  • Query.prototype.bindExecute()

returns an instance of Query.prototype.execute bound to the current instance of Query

A contrived example might look like:

var R = require('ramda');

var User = thinky.createModel("User", {
    id: type.number(),
    age: type.number(),
    admin: type.boolean().default(false)
});

/* Somewhere else */

R.composeP(
    User.get(req.session.id).bindRun(), // No need to declare an extraneous variable to bind the instance of Query to   
    isAdmin                          // Some other promised function
).then(function(allowed){
   // Do your admin stuff here; 
});

/* async example */

async.parallel({
    users: User.orderBy('id').bindRun(),
    posts: Post.orderby('id').bindRun()
}, function (err, accumulator) {
    /*
      accumulator example:
        {
            users: [{your users}]
            posts: [{your posts}]
        }
    */
});