mohair
Version:
mohair is a simple and flexible sql builder with a fluent interface
460 lines (334 loc) • 11.6 kB
Markdown
# mohair
[](http://badge.fury.io/js/mohair)
[](https://travis-ci.org/snd/mohair/branches)
[](https://david-dm.org/snd/mohair)
> mohair is a simple and flexible sql builder with a fluent interface.
[mesa](https://github.com/snd/mesa) builds on top of mohair and adds
methods to execute queries, to declare and include associations (`hasOne`, `belongsTo`, `hasMany`, `hasAndBelongsToMany`) and more:
[go check it out.](https://github.com/snd/mesa)
- [install](#install)
- [use](#use)
- [require](#require)
- [specify a table to use](#specify-a-table-to-use)
- [insert a record](#insert-a-record)
- [insert with some raw sql](#insert-with-some-raw-sql)
- [insert multiple records](#insert-multiple-records)
- [delete](#delete)
- [update](#update)
- [update with some raw sql](#update-with-some-raw-sql)
- [select](#select)
- [select with subquery](#select-with-subquery)
- [select without a table](#select-without-a-table)
- [select with criteria](#select-with-criteria)
- [order](#order)
- [limit and offset](#limit-and-offset)
- [join](#join)
- [join with criteria](#join-with-criteria)
- [group](#group)
- [mixins](#mixins)
- [extending](#extending)
- [common table expressions](#common-table-expressions)
- [license: MIT](#license-mit)
## background
## get started
```
npm install mohair
```
**or**
put this line in the dependencies section of your `package.json`:
```
"mohair": "0.12.0"
```
then run:
```
npm install
```
### use
mohair has a fluent interface where every method returns a new object.
no method ever changes the state of the object it is called on.
this enables a functional programming style:
```javascript
var visibleUsers = mohair.table('user').where({is_visible: true});
var updateUser = visibleUsers.update({name: 'bob'}).where({id: 3});
updateUser.sql(); // => 'UPDATE user SET name = ? WHERE (is_visible = ?) AND (id = ?)'
updateUser.params(); // => ['bob', true, 3]
var deleteUser = visibleUsers.where({name: 'alice'}).delete();
deleteUser.sql(); // => 'DELETE FROM user WHERE (is_visible = ?) AND (name = ?)'
deleteUser.params(); // => [true, 'alice']
```
##### require
```javascript
var mohair = require('mohair');
```
##### specify the table to use
```javascript
var userTable = mohair.table('user');
```
##### insert a record
```javascript
var query = userTable.insert({name: 'alice', email: 'alice@example.com'});
query.sql(); // => 'INSERT INTO user(name, email) VALUES (?, ?)'
query.params(); // => ['alice', 'alice@example.com']
```
##### insert with some raw sql
```javascript
var query = userTable.insert({name: 'alice', created_at: mohair.raw('NOW()')});
query.sql(); // => 'INSERT INTO user(name, created_at) VALUES (?, NOW())'
query.params(); // => ['alice']
```
##### insert multiple records
```javascript
var query = userTable.insertMany([{name: 'alice'}, {name: 'bob'}]);
query.sql(); // => 'INSERT INTO user(name) VALUES (?), (?)'
query.params(); // => ['alice', 'bob']
```
all records in the argument array must have the same properties.
##### delete
```javascript
var query = userTable.where({id: 3}).delete();
query.sql(); // => 'DELETE FROM user WHERE id = ?'
query.params(); // => [3]
```
`where` can take any valid [criterion](https://github.com/snd/criterion).
##### update
```javascript
var query = userTable.where({name: 'alice'}).update({name: 'bob'});
query.sql(); // => 'UPDATE user SET name = ? WHERE name = ?'
query.params(); // => ['bob', 'alice']
```
##### update with some raw sql
```javascript
var query = userTable.where({name: 'alice'}).update({age: mohair.raw('LOG(age, ?)', 4)});
query.sql(); // => 'UPDATE user SET age = LOG(age, ?) WHERE name = ?'
query.params(); // => [4, 'alice']
```
`where` can take any valid [criterion](https://github.com/snd/criterion).
##### select
```javascript
var query = userTable.select();
query.sql(); // => 'SELECT * FROM user'
query.params(); // => []
```
you can omit `select()` if you want to select `*`. select is the default action.
```javascript
var query = userTable.select('name, timestamp AS created_at');
query.sql(); // => 'SELECT name, timestamp AS created_at FROM user'
query.params(); // => []
```
```javascript
var query = userTable.select('name', 'timestamp AS created_at');
query.sql(); // => 'SELECT name, timestamp AS created_at FROM user'
query.params(); // => []
```
```javascript
var query = userTable.select('name', {created_at: 'timestamp'});
query.sql(); // => 'SELECT name, timestamp AS created_at FROM user'
query.params(); // => []
```
```javascript
var fragment = mohair.raw('SUM(total_sales/?)', 10);
var query = mohair
.table('regional_sales')
.select('region', {summed_sales: fragment});
query.sql(); // => 'SELECT region, (SUM(total_sales/?)) AS summed_sales FROM regional_sales'
query.params(); // => [10]
```
##### select with subquery
```javascript
var subquery = mohair
.table('order')
.where('user_id = user.id')
.select('count(1)');
var query = userTable.select('name', {order_count: subquery});
query.sql(); // => 'SELECT name, (SELECT count(1) FROM order WHERE user_id = user.id) AS order_count FROM user'
query.params(); // => []
```
##### select without a table
```javascript
var query = mohair.select('now()')
query.sql(); // => 'SELECT now()'
query.params(); // => []
```
##### select with criteria
```javascript
var query = userTable.where({id: 3}).where('name = ?', 'alice').select();
query.sql(); // => 'SELECT * FROM user WHERE (id = ?) AND (name = ?)'
query.params(); // => [3, 'alice']
```
`where` can take any valid [criterion](https://github.com/snd/criterion).
multiple calls to `where` are anded together.
##### order
```javascript
var query = userTable.order('created DESC, name ASC').select();
query.sql(); // => 'SELECT * FROM user ORDER BY created DESC, name ASC'
query.params(); // => []
```
##### limit and offset
```javascript
var query = userTable.limit(20).offset(10).select();
query.sql(); // => 'SELECT * FROM user LIMIT ? OFFSET ?'
query.params(); // => [20, 10]
```
##### join
```javascript
var query = userTable.join('JOIN project ON user.id = project.user_id');
query.sql(); // => 'SELECT * FROM user JOIN project ON user.id = project.user_id'
query.params(); // => []
```
##### join with criteria
```javascript
var query = userTable.join('JOIN project ON user.id = project.user_id', {'project.column': {$null: true}});
query.sql(); // => 'SELECT * FROM user JOIN project ON user.id = project.user_id AND (project.column IS NULL)'
query.params(); // => []
```
##### group
```javascript
var query = userTable
.select('user.*, count(project.id) AS project_count')
.join('JOIN project ON user.id = project.user_id')
.group('user.id');
query.sql(); // => 'SELECT user.*, count(project.id) AS project_count FROM user JOIN project ON user.id = project.user_id GROUP BY user.id'
query.params(); // => []
```
##### mixins
```javascript
var paginate = function(page, perPage) {
return this
.limit(perPage)
.offset(page * perPage);
};
var query = mohair.table('posts')
.mixin(paginate, 10, 100)
.where(is_public: true);
query.sql(); // => 'SELECT * FROM posts WHERE is_public = ? LIMIT ? OFFSET ?'
query.params(); // => [true, 100, 1000]
```
##### extending
```javascript
var posts = mohair.table('posts');
posts.paginate = function(page, perPage) {
return this
.limit(perPage)
.offset(page * perPage);
};
var query = mohair.table('posts')
.where(is_public: true)
.paginate(10, 100);
query.sql(); // => 'SELECT * FROM posts WHERE is_public = ? LIMIT ? OFFSET ?'
query.params(); // => [true, 100, 1000]
```
##### common table expressions
[see the postgres documentation](http://www.postgresql.org/docs/9.2/static/queries-with.html)
```javascript
var regionalSales = mohair
.select('region, SUM(amount) AS total_sales')
.table('orders')
.group('region');
var topRegions = mohair
.select('region')
.table('regional_sales')
.where('total_sales > (SELECT SUM(total_sales/10 FROM regional_sales))');
var query = mohair
.with(
regional_sales: regionalSales
top_regions: topRegions
)
.select("""
region,
product,
SUM(quantity) AS product_units,
SUM(amount) AS product_sales
""")
.table('orders')
.where('region IN (SELECT region FROM top_regions)')
.group('region, product');
```
```javascript
query.sql();
```
returns
```sql
WITH
regional_sales AS (
SELECT region, SUM(amount) AS total_sales
FROM orders
GROUP BY region
), top_regions AS (
SELECT region
FROM regional_sales
WHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales)
)
SELECT
region,
product,
SUM(quantity) AS product_units,
SUM(amount) AS product_sales
FROM orders
WHERE region IN (SELECT region FROM top_regions)
GROUP BY region, product;
```
## changelog
### 0.13.0
- now uses criterion@0.4.0: criterion changelog applies to mohair as well [click here to see it](https://github.com/snd/criterion#040)
- `.from()` supports selecting from multiple tables, selecting from subqueries and has syntax for aliases
- `.sql()`
- mohair now conforms to [sql-fragment interface](https://github.com/snd/criterion#the-sql-fragment-interface)
- escapes more things that are escapable: aliases, names for common table expressions, ...
- `.mixin` renamed to `.call`
## [license: MIT](LICENSE)
## TODO
- test offset and limit with raw
- fix union
- update from
- support raw everywhere
- fix js conversion
- better order
- table can be an alias expression
- test things in isolation
- .with should also have an effect for insert, update, delete
- test update from
- criterion.wrapped()
- better `.using`
- needs at least one table
- a seperate from object which is used to construct this
- support multiple tables in `.table`
- support alias syntax `{foo: 'table'}` in `.table`
- support subqueries in `.table`
- there must be at least one from item
- test better tables
- better testing of escaping
- test escaping for each and every query as in criterion !!!!
- q.escape(...)
- test that all parts of the queries get escaped
- select DONE
- insert
- returning
- update
- returning
- delete DONE
- returning
- test returning
- for update
- for delete
- support more select syntax
- better joins
- think about it !!! ...
- for lateral need to support subqueries
- similar to combination (union, ...)
- .join('LEFT JOIN LATERAL', subquery, 'ON', condition)
- support row locks
- join helper for select
- better errors
- check error message in tests for error conditions
- test for every possible error condition
- throw correct errors (`TypeError` for example)
- make `updateFrom` work
- https://github.com/snd/mohair/pull/29/files
- support insert with subquery
- `mohair.insert(['a', 'b', 'c'], mohair.table('user').select('id'))`
- README
- functional, immutable
- better documentation
- better description
- better keywords
- use lodash and replace helpers