UNPKG

mohair

Version:

mohair is a simple and flexible sql builder with a fluent interface

744 lines (547 loc) 22.9 kB
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()