strapi/docs/sql/orm.md
2016-03-22 18:11:11 +01:00

832 lines
14 KiB
Markdown

---
title: SQL ORM
---
The Strapi philosophy regarding databases and ORMs is very simple: a unique ORM for SQL databases and one selected ORM for each NoSQL database.
The SQL ORM aims to provide a simple library for common tasks when querying databases in JavaScript, and forming relations between these objects, taking a lot of ideas from the the [Data Mapper Pattern](http://en.wikipedia.org/wiki/Data_mapper_pattern).
With a concise, literate codebase, the Strapi SQL ORM is simple to read, understand, and extend. It doesn't force you to use any specific validation scheme, provides flexible and efficient relation/nested-relation loading, and first class transaction support.
It's a lean Object Relational Mapper, allowing you to drop down to the [raw interface](raw.md) whenever you need a custom query that doesn't quite fit with the stock conventions.
> In this document we assume the `connection` is one of the `connections` set in your config.
```js
const connection = strapi.connections.default;
```
## Promises
The Strapi SQL ORM uses its own copy of the "bluebird" promise library. That means methods are chainables and return promises.
```js
User.forge()
.where('reviews', '>', 100)
.limit(10)
.offset(30)
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
```
## Query language basics
### Fetch a record
Example:
```js
User.forge({id: 123})
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
```
The `console.log()` will print out:
```js
{
"id": 123,
"firstname": "John",
"lastname": "Doe",
"age": 20
}
```
It is also possible to eager loading any specified relations named on the model:
```js
User.forge({id: 123})
.fetch({
withRelated: ['friends', 'likes']
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
```
The `console.log()` will print out:
```js
{
"id": 123,
"firstname": "John",
"lastname": "Doe",
"age": 20,
"friends": [{
"id": 99,
"firstname": "Peter",
"lastname": "Arrow",
"age": 53
}, {
"id": 48,
"firstname": "Andrea",
"lastname": "Nelson",
"age": 32
}],
"likes": [{
"post_id": 1829,
"date": "2016-04-22T06:00:00Z"
}, {
"post_id": 7849,
"date": "2016-03-22T07:32:45Z"
}]
}
```
### Fetch records
Example:
```js
User.forge()
.fetchAll()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
```
The `console.log()` will print out:
```js
[{
"id": 123,
"firstname": "John",
"lastname": "Doe",
"age": 20
}, {
"id": 99,
"firstname": "Peter",
"lastname": "Arrow",
"age": 53
}, {
...
}]
```
It is also possible to eager loading any specified relations named on the model:
```js
User.forge()
.fetchAll({
withRelated: ['friends', 'likes']
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
```
### Create a record
Example:
```js
User.forge({
'firstname': 'John',
'lastname': 'Doe',
'age': 20
})
.save()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
```
The `console.log()` will print out:
```js
{
"id": 123,
"firstname": "John",
"lastname": "Doe",
"age": 20
}
```
### Create a record with one-to-one/one-to-many relationship
The only difference between the two relationship cases will be on the database constraints layer:
- *one-to-one*: it will allow only one user with the sponsor ID `849`
- *one-to-many*: it will allow more than one user with the sponsor ID `849`
Example:
```js
User.forge({
'firstname': 'John',
'lastname': 'Doe',
'age': 20,
'sponsor': 849
})
.save()
.then(function (model) {
return model.fetch({
withRelated: 'sponsor'
});
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
```
The `console.log()` will print out:
```js
{
"id": 123,
"firstname": "John",
"lastname": "Doe",
"age": 20,
"sponsor": {
"id": 849,
"firstname": "Peter",
"lastname": "Arrow",
"age": 53
}
}
```
### Create a record with many-to-many relationship
```js
const friend1 = User.forge({id: 1});
const friend2 = User.forge({id: 2});
User.forge({
'firstname': 'John',
'lastname': 'Doe',
'age': 20
})
.save()
.tap(function (model) {
return model.friends().attach([friend1, friend2]);
})
.then(function (model) {
return model.fetch({
withRelated: 'friends'
});
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
```
The `console.log()` will print out:
```js
{
"id": 123,
"firstname": "John",
"lastname": "Doe",
"age": 20,
"friends": [{
"id": 1,
"firstname": "Peter",
"lastname": "Arrow",
"age": 53
}, {
"id": 2,
"firstname": "Andrea",
"lastname": "Nelson",
"age": 32
}]
}
```
### Update a record
```js
User.forge({
id: 123
})
.save({
'lastname': 'Does',
'age': 40
}, {path: true})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function(err) {
reject(err);
});
```
The `console.log()` will print out:
```js
{
"id": 123,
"firstname": "John",
"lastname": "Does",
"age": 40,
}
```
### Update a record with a one-to-one/one-to-many relationship
This is exactly the same as update a record without relationship.
```js
User.forge({
id: 123
})
.save({
'sponsor': 756
}, {path: true})
.then(function (model) {
return model.fetch({
withRelated: 'sponsor'
});
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function(err) {
reject(err);
});
```
The `console.log()` will print out:
```js
{
"id": 123,
"firstname": "John",
"lastname": "Does",
"age": 40,
"sponsor": {
"id": 756,
"firstname": "Martin",
"lastname": "Terry",
"age": 37
}
}
```
### Update a record with a many-to-many relationship
This is also exactly the same as update a record without relationship. However, there are two cases:
- Add new relationship(s) to a record
- Remove current(s) relationship(s) to the record
#### Add new relationship(s) to a record
```js
const friend3 = User.forge({id: 3});
User.forge({
id: 123
})
.friends()
.attach([friend3])
.then(function (model) {
return model.fetch({
withRelated: 'friends'
});
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
```
The `console.log()` will print out:
```js
{
"id": 123,
"firstname": "John",
"lastname": "Doe",
"age": 20,
"friends": [{
"id": 1,
"firstname": "Peter",
"lastname": "Arrow",
"age": 53
}, {
"id": 2,
"firstname": "Andrea",
"lastname": "Nelson",
"age": 32
}, {
"id": 3,
"firstname": "Paul",
"lastname": "Thomas",
"age": 78
}]
}
```
#### Remove current(s) relationship(s) to a record
```js
const friend2 = User.forge({id: 2});
const friend3 = User.forge({id: 3});
User.forge({
id: 123
})
.friends()
.detach([friend2, friend3])
.then(function (model) {
return model.fetch({
withRelated: 'friends'
});
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
```
The `console.log()` will print out:
```js
{
"id": 123,
"firstname": "John",
"lastname": "Doe",
"age": 20,
"friends": [{
"id": 1,
"firstname": "Peter",
"lastname": "Arrow",
"age": 53
}]
}
```
### Delete a record
```js
User.forge({
id: 123
})
.destroy()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function(err) {
console.log(err);
});
```
The `console.log()` will print out:
```js
null
```
## Query options
### raw
```js
User.forge({
id: 123
})
.query(function (qb) {
qb.offset(0).limit(10);
}))
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
})
```
### count
Count the entries for a query:
```js
User.forge()
.where({
'age': 20
})
.count()
.then(function (count) {
console.log(count);
})
.catch(function (err) {
console.log(err);
})
```
### where
Adds a `where` clause to the query:
```js
User.forge()
.where({
'firstname': 'John',
'age': 20
})
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
```
### orderBy
Adds an `orderBy` clause to the query.
Using the object syntax:
```js
User.forge()
.query({
'orderBy': 'firstname'
})
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
```
This syntax doesn't allow to update `ASC` / `DESC` parameter. To do so, you have to use the function syntax below:
```js
User.forge()
.query(function (qb) {
qb.orderBy('firstname', 'desc');
}))
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
```
### groupBy
Adds an `groupBy` clause to the query:
```js
User.forge()
.query({
'groupBy': 'age'
})
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
```
### limit
Adds an `limit` clause to the query:
```js
User.forge()
.query({
'limit': 20
})
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
```
### offset
Adds an `offset` clause to the query:
```js
User.forge()
.query({
'offset': 10
})
.fetch()
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.log(err);
});
```
## Lifecycle events
Lifecycle callbacks are functions you can define to run at certain times in a query. They are hooks that you can tap into in order to change data. Strapi exposes a handful of lifecycle callbacks by default.
Lifecycle events must be placed in your JavaScript file of your models.
### Common callbacks
#### beforeSave
Fired before an `insert` or `update` query. A promise may be returned from the event handler for async behavior. Throwing an exception from the handler will cancel the save.
Code:
```js
beforeSave: (model, attrs, options) => {
return new Promise();
},
```
Parameters:
- `model`: The model firing the event.
- `attrs`: Attributes that will be inserted or updated.
- `options`: Options object passed to `save`.
Returns: Promise.
#### afterSave
Fired after an `insert` or `update` query.
Code:
```js
afterSave: (model, response, options) => {
return new Promise();
},
```
Parameters:
- `model`: The model firing the event.
- `response`: The database response.
- `options`: Options object passed to `save`.
Returns: Promise.
### Callbacks on fetch
#### beforeFetch
Fired before a `fetch` operation. A promise may be returned from the event handler for async behavior.
Code:
```js
beforeFetch: (model, columns, options) => {
return new Promise();
},
```
Parameters:
- `model`: The model firing the event.
- `columns`: The columns to be retrieved by the query.
- `options`: Options object passed to `fetch`.
Returns: Promise.
#### afterFetch
Fired after a `fetch` operation. A promise may be returned from the event handler for async behavior.
Code:
```js
afterFetch: (model, response, options) => {
return new Promise();
},
```
Parameters:
- `model`: The model firing the event.
- `response`: SQL query response.
- `options`: Options object passed to `fetch`.
Returns: Promise. *If the handler returns a promise, `fetch` will wait for it to be resolved.*
### Callbacks on create
#### beforeCreate
Fired before `insert` query. A promise may be returned from the event handler for async behavior. Throwing an exception from the handler will cancel the save operation.
Code:
```js
beforeCreate: (model, attrs, options) => {
return new Promise();
},
```
Parameters:
- `model`: The model firing the event.
- `attrs`: Attributes that will be inserted.
- `options`: Options object passed to `save`.
Returns: Promise.
#### afterCreate
Fired after an `insert` query.
Code:
```js
afterCreate: (model, attrs, options) => {
return new Promise();
},
```
Parameters:
- `model`: The model firing the event.
- `attrs`: Attributes inserted.
- `options`: Options object passed to `save`.
Returns: Promise.
### Callbacks on update
#### beforeUpdate
Fired before an `update` query. A promise may be returned from the event handler for async behavior. Throwing an exception from the handler will cancel the save operation.
Code:
```js
beforeUpdate: (model, attrs, options) => {
return new Promise();
},
```
Parameters:
- `model`: The model firing the event.
- `attrs`: Attributes that will be updated.
- `options`: Options object passed to `save`.
Returns: Promise.
#### afterUpdate
Fired after an `update` query.
Code:
```js
afterUpdate: (model, attrs, options) => {
return new Promise();
},
```
Parameters:
- `model`: The model firing the event.
- `attrs`: Attributes updated.
- `options`: Options object passed to `save`.
Returns: Promise.
### Callbacks on destroy
#### beforeDestroy
Fired before a `delete` query. A promise may be returned from the event handler for async behavior. Throwing an exception from the handler will reject the promise and cancel the deletion.
Code:
```js
beforeDestroy: (model, attrs, options) => {
return new Promise();
},
```
Parameters:
- `model`: The model firing the event.
- `attrs`: Attributes that will be destroyed.
- `options`: Options object passed to `save`.
Returns: Promise.
#### afterDestroy
Fired before a `delete` query. A promise may be returned from the event handler for async behavior.
Code:
```js
afterDestroy: (model, attrs, options) => {
return new Promise();
},
```
Parameters:
- `model`: The model firing the event.
- `attrs`: Attributes destroyed.
- `options`: Options object passed to `save`.
Returns: Promise.