mohair
Version:
mohair is a simple and flexible sql builder with a fluent interface
744 lines (547 loc) • 22.9 kB
text/coffeescript
mohair = require '../src/mohair'
combinationMap =
union: 'UNION'
unionAll: 'UNION ALL'
intersect: 'INTERSECT'
intersectAll: 'INTERSECT ALL'
except: 'EXCEPT'
exceptAll: 'EXCEPT ALL'
weirdEscape = (string) ->
"{{{#{string}}}}"
module.exports =
'getTable': (test) ->
test.equal 'user', mohair.table('user').getTable()
test.done()
'actions':
'select':
'default is select *': (test) ->
q = mohair.table('user')
test.equal q.sql(), 'SELECT * FROM user'
test.deepEqual q.params(), []
test.done()
'explicit select': (test) ->
q = mohair.table('user').select()
test.equal q.sql(), 'SELECT * FROM user'
test.deepEqual q.params(), []
test.done()
'specific fields': (test) ->
q = mohair.table('user').select('name, timestamp AS created_at')
test.equal q.sql(), 'SELECT name, timestamp AS created_at FROM user'
test.deepEqual q.params(), []
test.done()
'specific fields provided individually': (test) ->
q = mohair.table('user').select('name', 'timestamp AS created_at')
test.equal q.sql(), 'SELECT name, timestamp AS created_at FROM user'
test.deepEqual q.params(), []
test.done()
'with alias': (test) ->
q = mohair.table('user').select('name', {created_at: 'timestamp'})
test.equal q.sql(), 'SELECT name, timestamp AS created_at FROM user'
test.deepEqual q.params(), []
test.done()
'with raw': (test) ->
q = mohair.table('user').select('name', mohair.raw('count/?', 10))
test.equal q.sql(), 'SELECT name, count/? FROM user'
test.deepEqual q.params(), [10]
test.done()
'with raw and alias': (test) ->
q = mohair.table('user').select('name', {number: mohair.raw('count/?', 10)})
test.equal q.sql(), 'SELECT name, count/? AS number FROM user'
test.deepEqual q.params(), [10]
test.done()
'with subquery': (test) ->
subquery = mohair
.table('order')
.where('user_id = user.id')
.where('price > ?', 10)
.select('count(1)')
q = mohair
.table('user')
.select('name', {order_count: subquery})
test.equal q.sql(), 'SELECT name, (SELECT count(1) FROM order WHERE user_id = user.id AND price > ?) AS order_count FROM user'
test.deepEqual q.params(), [10]
test.done()
'from subquery': (test) ->
subquery = mohair
.table('order')
.where('user_id = user.id')
.where('price > ?', 10)
.select('count(1)')
q = mohair
.from(subquery)
.select('name')
test.equal q.sql(), 'SELECT name FROM (SELECT count(1) FROM order WHERE user_id = user.id AND price > ?)'
test.deepEqual q.params(), [10]
test.done()
'without table': (test) ->
q = mohair.select('now()')
test.equal q.sql(), 'SELECT now()'
test.deepEqual q.params(), []
test.done()
'raw without table': (test) ->
q = mohair.select(mohair.raw('1'))
test.equal q.sql(), 'SELECT 1'
test.deepEqual q.params(), []
test.done()
'with criteria': (test) ->
q = mohair.table('user').where(id: 3).select()
test.equal q.sql(), 'SELECT * FROM user WHERE id = ?'
test.deepEqual q.params(), [3]
test.done()
'criteria are anded together': (test) ->
q = mohair.table('user').where(id: 3).where('name = ?', 'foo').select()
test.equal q.sql(), 'SELECT * FROM user WHERE (id = ?) AND name = ?'
test.deepEqual q.params(), [3, 'foo']
test.done()
'order': (test) ->
q = mohair.table('user').order('created DESC, name ASC')
test.equal q.sql(), 'SELECT * FROM user ORDER BY created DESC, name ASC'
test.deepEqual q.params(), []
test.done()
'limit': (test) ->
q = mohair.table('user').limit(10)
test.equal q.sql(), 'SELECT * FROM user LIMIT ?'
test.deepEqual q.params(), [10]
test.done()
'offset': (test) ->
q = mohair.table('user').offset(5)
test.equal q.sql(), 'SELECT * FROM user OFFSET ?'
test.deepEqual q.params(), [5]
test.done()
'join': (test) ->
q = mohair.table('user')
.join('JOIN project ON user.id = project.user_id')
test.equal q.sql(), 'SELECT * FROM user JOIN project ON user.id = project.user_id'
test.deepEqual q.params(), []
test.done()
'join with object criterion': (test) ->
q = mohair.table('user')
.join('JOIN project ON user.id = project.user_id', {'project.foo': {$null: true}, 'project.bar': 10})
test.equal q.sql(),
'SELECT * FROM user JOIN project ON user.id = project.user_id AND ((project.foo IS NULL) AND (project.bar = ?))'
test.deepEqual q.params(), [10]
test.done()
'join with sql criterion': (test) ->
q = mohair.table('user')
.join('JOIN project ON user.id = project.user_id', 'project.foo = ?', 4)
test.equal q.sql(),
'SELECT * FROM user JOIN project ON user.id = project.user_id AND (project.foo = ?)'
test.deepEqual q.params(), [4]
test.done()
'multiple joins': (test) ->
q = mohair.table('user')
.join('OUTER JOIN project ON user.id = project.user_id', 'project.foo = ?', 4)
.join('INNER JOIN task ON project.id = task.project_id', {'task.bar': 10})
test.equal q.sql(),
'SELECT * FROM user OUTER JOIN project ON user.id = project.user_id AND (project.foo = ?) INNER JOIN task ON project.id = task.project_id AND (task.bar = ?)'
test.deepEqual q.params(), [4, 10]
test.done()
'having': (test) ->
q = mohair.table('user')
.select('user.id, (select project.id from project) projectId')
.having('projectId = user.id')
test.equal q.sql(),
'SELECT user.id, (select project.id from project) projectId FROM user HAVING projectId = user.id'
test.done()
'group': (test) ->
q = mohair.table('user')
.select('user.*, count(project.id) AS project_count')
.join('JOIN project ON user.id = project.user_id')
.group('user.id')
test.equal q.sql(), 'SELECT user.*, count(project.id) AS project_count FROM user JOIN project ON user.id = project.user_id GROUP BY user.id'
test.deepEqual q.params(), []
test.done()
'everything together': (test) ->
q = mohair.table('user')
.select('user.*, count(project.id) AS project_count')
.distinct('ON (name)')
.join('JOIN project ON user.id = project.user_id')
.where(id: 3)
.where('name = ?', 'foo')
.group('user.id')
.having(year: 2000)
.window('PARTITION BY id')
.order('created DESC', 'name ASC')
.limit(10)
.offset(20)
.for('UPDATE')
test.equal q.sql(), 'SELECT DISTINCT ON (name) user.*, count(project.id) AS project_count FROM user JOIN project ON user.id = project.user_id WHERE (id = ?) AND name = ? GROUP BY user.id HAVING year = ? WINDOW PARTITION BY id ORDER BY created DESC, name ASC LIMIT ? OFFSET ? FOR UPDATE'
test.deepEqual q.params(), [3, 'foo', 2000, 10, 20]
test.done()
'combinations':
'union': (test) ->
alpha = mohair.select('1')
bravo = mohair.select('2')
combination = alpha.union(bravo)
test.equal combination.sql(), "SELECT 1 UNION SELECT 2"
test.deepEqual combination.params(), []
test.done()
'2 very simple queries': (test) ->
test.expect 6 * 2
for k, v of combinationMap
alpha = mohair.select('1')
bravo = mohair.select('2')
combination = alpha[k](bravo)
test.equal combination.sql(), "SELECT 1 #{v} SELECT 2"
test.deepEqual combination.params(), []
test.done()
'3 normal queries': (test) ->
test.expect 6 * 2
for k, v of combinationMap
alpha = mohair.table('alpha').where({table: 'alpha', function: k})
bravo = mohair.table('bravo').where({table: 'bravo', function: k})
charlie = mohair.table('charlie').where({table: 'charlie', function: k})
combination = alpha[k](bravo)[k](charlie)
parts = [
'SELECT * FROM alpha WHERE (table = ?) AND (function = ?)'
v
'SELECT * FROM bravo WHERE (table = ?) AND (function = ?)'
v
'SELECT * FROM charlie WHERE (table = ?) AND (function = ?)'
]
test.equal combination.sql(), parts.join(' ')
test.deepEqual combination.params(), [
'alpha',
k
'bravo',
k
'charlie',
k
]
test.done()
'insert one':
'simple': (test) ->
q = mohair.table('user').insert {name: 'foo', user_id: 5}
test.equal q.sql(), 'INSERT INTO user(name, user_id) VALUES (?, ?)'
test.deepEqual q.params(), ['foo', 5]
test.done()
'with null values': (test) ->
q = mohair.table('user').insert {name: 'foo', user_id: null}
test.equal q.sql(), 'INSERT INTO user(name, user_id) VALUES (?, ?)'
test.deepEqual q.params(), ['foo', null]
test.done()
'with raw without params': (test) ->
q = mohair.table('user').insert
name: 'foo',
user_id: 5
created_at: mohair.raw('NOW()')
test.equal q.sql(), 'INSERT INTO user(name, user_id, created_at) VALUES (?, ?, NOW())'
test.deepEqual q.params(), ['foo', 5]
test.done()
'with raw with params': (test) ->
q = mohair.table('user').insert
name: 'foo',
user_id: 5
created_at: mohair.raw('LOG(x, ?)', 3)
test.equal q.sql(), 'INSERT INTO user(name, user_id, created_at) VALUES (?, ?, LOG(x, ?))'
test.deepEqual q.params(), ['foo', 5, 3]
test.done()
'with raw with params and returning': (test) ->
q = mohair
.table('user')
.returning('id', mohair.raw('x + ?', 7), {a: 'b'})
.insert
name: 'foo',
user_id: 5
created_at: mohair.raw('LOG(x, ?)', 3)
test.equal q.sql(), 'INSERT INTO user(name, user_id, created_at) VALUES (?, ?, LOG(x, ?)) RETURNING id, x + ?, b AS a'
test.deepEqual q.params(), ['foo', 5, 3, 7]
test.done()
'insert many':
'records with same key order': (test) ->
q = mohair.table('user').insert [
{name: 'foo', email: 'foo@example.com'}
{name: 'bar', email: 'bar@example.com'}
{name: 'baz', email: 'baz@example.com'}
]
test.equal q.sql(),
'INSERT INTO user(name, email) VALUES (?, ?), (?, ?), (?, ?)'
test.deepEqual q.params(),
['foo', 'foo@example.com', 'bar', 'bar@example.com', 'baz', 'baz@example.com']
test.done()
'records with different key order': (test) ->
q = mohair.table('user').insert [
{name: 'foo', email: 'foo@example.com', age: 16}
{email: 'bar@example.com', name: 'bar', age: 25}
{age: 30, name: 'baz', email: 'baz@example.com'}
]
test.equal q.sql(),
'INSERT INTO user(name, email, age) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)'
test.deepEqual q.params(),
['foo', 'foo@example.com', 16, 'bar', 'bar@example.com', 25, 'baz', 'baz@example.com', 30]
test.done()
'records with null values': (test) ->
q = mohair.table('user').insert [
{name: 'foo', email: 'foo@example.com', age: null}
{email: 'bar@example.com', name: null, age: 25}
{age: 30, name: 'baz', email: null}
]
test.equal q.sql(),
'INSERT INTO user(name, email, age) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)'
test.deepEqual q.params(),
['foo', 'foo@example.com', null, null, 'bar@example.com', 25, 'baz', null, 30]
test.done()
'with raw without params': (test) ->
q = mohair.table('user').insert [
{
name: 'foo',
user_id: 5
created_at: mohair.raw('BAR()')
}
{
user_id: 6
created_at: mohair.raw('BAZ()')
name: 'bar',
}
{
created_at: mohair.raw('FOO()')
name: 'baz',
user_id: 7
}
]
test.equal q.sql(), 'INSERT INTO user(name, user_id, created_at) VALUES (?, ?, BAR()), (?, ?, BAZ()), (?, ?, FOO())' # test.deepEqual q.params(), ['foo', 5, 'bar', 6, 'baz', 7]
test.done()
'with raw with params': (test) ->
q = mohair.table('user').insert [
{
name: 'foo',
user_id: 5
created_at: mohair.raw('BAR(?)', [10, 11, 12])
}
{
user_id: 6
created_at: mohair.raw('BAZ(?)', 20)
name: 'bar',
}
{
created_at: mohair.raw('FOO(?)', [30, 31])
name: 'baz',
user_id: 7
}
]
test.equal q.sql(), 'INSERT INTO user(name, user_id, created_at) VALUES (?, ?, BAR(?, ?, ?)), (?, ?, BAZ(?)), (?, ?, FOO(?, ?))'
test.deepEqual q.params(), ['foo', 5, 10, 11, 12, 'bar', 6, 20, 'baz', 7, 30 ,31]
test.done()
'update':
'without criteria': (test) ->
q = mohair.table('user').update {name: 'bar', email: 'bar@example.com'}
test.equal q.sql(), 'UPDATE user SET name = ?, email = ?'
test.deepEqual q.params(), ['bar', 'bar@example.com']
test.done()
'with criteria': (test) ->
q = mohair.table('user')
.where(id: 3, x: 5)
.update {name: 'bar', email: 'bar@example.com'}
test.equal q.sql(), 'UPDATE user SET name = ?, email = ? WHERE (id = ?) AND (x = ?)'
test.deepEqual q.params(), ['bar', 'bar@example.com', 3, 5]
test.done()
'with raw without params': (test) ->
q = mohair.table('user')
.where(id: 3, x: 5)
.update {
name: 'foo',
user_id: 6
modified_at: mohair.raw('NOW()')
}
test.equal q.sql(), 'UPDATE user SET name = ?, user_id = ?, modified_at = NOW() WHERE (id = ?) AND (x = ?)'
test.deepEqual q.params(), ['foo', 6, 3, 5]
test.done()
'with raw with params': (test) ->
q = mohair.table('user')
.where(id: 3, x: 5)
.update {
name: 'foo',
user_id: mohair.raw('LOG(user_id, ?)', 4)
modified_at: mohair.raw('NOW()')
}
test.equal q.sql(), 'UPDATE user SET name = ?, user_id = LOG(user_id, ?), modified_at = NOW() WHERE (id = ?) AND (x = ?)'
test.deepEqual q.params(), ['foo', 4, 3, 5]
test.done()
'with null values': (test) ->
q = mohair.table('user').update {name: 'foo', user_id: null}
test.equal q.sql(), 'UPDATE user SET name = ?, user_id = ?'
test.deepEqual q.params(), ['foo', null]
test.done()
'delete':
'without criteria': (test) ->
q = mohair.table('user').delete()
test.equal q.sql(), 'DELETE FROM user'
test.deepEqual q.params(), []
test.done()
'with criteria': (test) ->
q = mohair.table('user')
.delete()
.where('x BETWEEN ? AND ?', 50, 55)
.where($or: {x: 10, y: 6})
test.equal q.sql(), 'DELETE FROM user WHERE x BETWEEN ? AND ? AND ((x = ?) OR (y = ?))'
test.deepEqual q.params(), [50, 55, 10, 6]
test.done()
'call': (test) ->
paginate = (page, perPage) ->
this.limit(perPage)
.offset(page * perPage)
query = mohair.table('posts')
.call(paginate, 10, 100)
.where(is_public: true)
test.equal query.sql(), 'SELECT * FROM posts WHERE is_public = ? LIMIT ? OFFSET ?'
test.deepEqual query.params(), [true, 100, 1000]
test.done()
'actions overwrite previous actions': (test) ->
chain = mohair.table('user')
.where(id: 3)
.select('name')
query = chain.insert(name: 'foo').table('project')
test.equal chain.sql(), 'SELECT name FROM user WHERE id = ?'
test.deepEqual chain.params(), [3]
test.equal query.sql(), 'INSERT INTO project(name) VALUES (?)'
test.deepEqual query.params(), ['foo']
test.done()
'immutability': (test) ->
visible = mohair.table('project').where(is_visible: true)
updateQuery = visible.update({name: 'i am visible'}).where(id: 3)
test.equal updateQuery.sql(),
'UPDATE project SET name = ? WHERE (is_visible = ?) AND (id = ?)'
test.deepEqual updateQuery.params(), ['i am visible', true, 3]
deleteQuery = visible.where({name: 'foo'}).delete()
test.equal deleteQuery.sql(),
'DELETE FROM project WHERE (is_visible = ?) AND (name = ?)'
test.deepEqual deleteQuery.params(), [true, 'foo']
test.done()
'escape':
'select': (test) ->
subquery = mohair
.table('user')
.where(id: 7)
commentCount = mohair
.table('comment')
.where('project_id = comment_id')
.select('count(1)')
.group('id')
other = mohair
.table('todo')
.where(id: 8)
query = mohair
.with(user: subquery)
.escape((string) -> "\"#{string}\"")
.table('project')
.select(commentCount, {comment_count: commentCount})
.where(is_visible: true)
.having(count: 5)
.union(other)
test.equal query.sql(), [
'WITH "user" AS (SELECT * FROM "user" WHERE "id" = ?) SELECT'
'(SELECT count(1) FROM "comment" WHERE project_id = comment_id GROUP BY id),'
'(SELECT count(1) FROM "comment" WHERE project_id = comment_id GROUP BY id)'
'AS "comment_count"'
'FROM "project"'
'WHERE "is_visible" = ?'
'HAVING "count" = ?'
'UNION'
'SELECT * FROM "todo" WHERE "id" = ?'
].join(' ')
test.equal query.sql(weirdEscape), [
'WITH {{{user}}} AS (SELECT * FROM {{{user}}} WHERE {{{id}}} = ?) SELECT'
'(SELECT count(1) FROM {{{comment}}} WHERE project_id = comment_id GROUP BY id),'
'(SELECT count(1) FROM {{{comment}}} WHERE project_id = comment_id GROUP BY id)'
'AS {{{comment_count}}}'
'FROM {{{project}}}'
'WHERE {{{is_visible}}} = ?'
'HAVING {{{count}}} = ?'
'UNION'
'SELECT * FROM {{{todo}}} WHERE {{{id}}} = ?'
].join(' ')
test.deepEqual query.params(), [7, true, 5, 8]
test.done()
'insert': (test) ->
query = mohair
.escape((string) -> "\"#{string}\"")
.table('project')
.insert {first_key: 'first_value', second_key: 'second_value'}
test.equal query.sql(),
'INSERT INTO "project"("first_key", "second_key") VALUES (?, ?)'
test.equal query.sql(weirdEscape),
'INSERT INTO {{{project}}}({{{first_key}}}, {{{second_key}}}) VALUES (?, ?)'
test.deepEqual query.params(), ['first_value', 'second_value']
test.done()
'update': (test) ->
query = mohair
.escape((string) -> "\"#{string}\"")
.table('project')
.update {first_key: 'first_value', second_key: 'second_value'}
.where(id: 1)
test.equal query.sql(),
'UPDATE "project" SET "first_key" = ?, "second_key" = ? WHERE "id" = ?'
test.equal query.sql(weirdEscape),
'UPDATE {{{project}}} SET {{{first_key}}} = ?, {{{second_key}}} = ? WHERE {{{id}}} = ?'
test.deepEqual query.params(), ['first_value', 'second_value', 1]
test.done()
'delete': (test) ->
query = mohair
.escape((string) -> "\"#{string}\"")
.table('project')
.where(id: 1)
.delete()
test.equal query.sql(),
'DELETE FROM "project" WHERE "id" = ?'
test.equal query.sql(weirdEscape),
'DELETE FROM {{{project}}} WHERE {{{id}}} = ?'
test.deepEqual query.params(), [1]
test.done()
'common table expressions':
example: (test) ->
regionalSales = mohair
.select('region, SUM(amount) AS total_sales')
.table('orders')
.group('region')
topRegions = mohair
.select('region')
.table('regional_sales')
.where('total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales)')
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')
expected = "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"
test.equals query.sql(), expected
test.deepEqual query.params(), []
test.done()
params: (test) ->
regionalSales =
sql: -> 'regional_sales'
params: -> [1, 2, 3]
topRegions =
sql: -> 'top_regions'
params: -> [4, 5, 6]
query = mohair
.with(
regional_sales: regionalSales
top_regions: topRegions
)
.table('orders')
.where('type = ?', 'test')
expected = "WITH regional_sales AS (regional_sales), top_regions AS (top_regions) SELECT * FROM orders WHERE type = ?"
test.equals query.sql(), expected
test.deepEqual query.params(), [1, 2, 3, 4, 5, 6, 'test']
test.done()
'error conditions':
'insert without table': (test) ->
q = mohair.insert {name: 'foo', email: 'foo@example.com'}
test.throws -> q.sql()
test.done()
'create multiple records without matching keys': (test) ->
test.throws ->
mohair.table('user').insert [
{name: 'foo', email: 'foo@example.com'}
{name: 'bar'}
]
test.throws ->
mohair.table('user').insert [
{name: 'foo', email: 'foo@example.com'}
{name: 'bar', id: 9}
]
test.done()