DefinitelyTyped/types/sql-bricks/sql-bricks-tests.ts

950 lines
42 KiB
TypeScript

// Test file from sql-briks package tests
// https://github.com/CSNW/sql-bricks/blob/master/tests/tests.js
// taked the 03/29/2018. Some dtslint rules has been deabled to be
// close as possible of the original js file.
import sql = require("sql-bricks");
// tslint:disable-next-line:prefer-const
let assert: any;
const select = sql.select;
const insertInto = sql.insertInto;
const insert = sql.insert;
const update = sql.update;
const del = sql.delete;
const and = sql.and;
const or = sql.or;
const like = sql.like;
const not = sql.not;
const $in = sql.in;
const isNull = sql.isNull;
const isNotNull = sql.isNotNull;
const equal = sql.equal;
const eq = sql.eq;
const notEq = sql.notEq;
const lt = sql.lt;
const lte = sql.lte;
const gt = sql.gt;
const gte = sql.gte;
const between = sql.between;
const exists = sql.exists;
const eqAny = sql.eqAny;
const notEqAny = sql.notEqAny;
const alias_expansions = { usr: 'user', psn: 'person', addr: 'address' };
const table_to_alias: sql.OnCriteria = alias_expansions;
sql.aliasExpansions(alias_expansions);
sql.joinCriteria(function(left_tbl: string, left_alias: string, right_tbl: string, right_alias: string) {
const criteria: sql.OnCriteria = {};
criteria[`${left_alias}.${table_to_alias[right_tbl]}_fk`] = right_alias + '.pk';
return criteria;
});
describe('SQL Bricks', function() {
describe('parameterized sql', function() {
it('should generate for insert statements', function() {
const values = { first_name: 'Fred', last_name: 'Flintstone' };
checkParams(insert('user', values),
'INSERT INTO "user" (first_name, last_name) VALUES ($1, $2)',
['Fred', 'Flintstone']);
});
it('should generate for UPDATEs', function() {
const values = { first_name: 'Fred', last_name: 'Flintstone' };
checkParams(update('user', values),
'UPDATE "user" SET first_name = $1, last_name = $2',
['Fred', 'Flintstone']);
});
it('should generate for WHERE clauses', function() {
checkParams(select().from('user').where({
removed: 0,
name: 'Fred Flintstone'
}), 'SELECT * FROM "user" WHERE removed = $1 AND name = $2',
[0, 'Fred Flintstone']);
});
it('should generate WHERE clauses with arbitrary SQL', function() {
const stmt = select().from('time_limits');
stmt.where(sql(
"tsrange(start_date_time, end_date_time, '[]') @> tsrange($1, $2, '[]')"));
check(stmt,
"SELECT * FROM time_limits WHERE tsrange(start_date_time, end_date_time, '[]') @> tsrange($1, $2, '[]')");
});
it('should generate WHERE clauses with arbitrary SQL and parameters', function() {
const stmt = select().from('time_limits');
const time1 = '2014-12-06T22:35:00';
const time2 = '2014-12-06T22:36:00';
stmt.where(sql(
"tsrange(start_date_time, end_date_time, '[]') @> tsrange($, $, '[]')", time1, time2));
checkParams(stmt,
"SELECT * FROM time_limits WHERE tsrange(start_date_time, end_date_time, '[]') @> tsrange($1, $2, '[]')",
['2014-12-06T22:35:00', '2014-12-06T22:36:00']);
});
it('should handle WHERE clauses with sql() and a null check', function() {
check(select().from('test').where(sql('my_col_val()'), null),
"SELECT * FROM test WHERE my_col_val() IS NULL");
});
it('should not escape single quotes in the values returned by toParams()', function() {
checkParams(update('user', { name: "Muad'Dib" }),
'UPDATE "user" SET name = $1',
["Muad'Dib"]);
});
it('should not attempt to serialize arrays (should be done by subsequent layer)', function() {
checkParams(update('user', { name: ["Paul", "Muad'Dib"] }),
'UPDATE "user" SET name = $1',
[["Paul", "Muad'Dib"]]);
});
it('should not attempt to serialize dates', function() {
const dt = new Date();
checkParams(update('user', { birthdate: dt }),
'UPDATE "user" SET birthdate = $1',
[dt]);
});
it('should generate node-sqlite3 style params', function() {
const values = { first_name: 'Fred', last_name: 'Flintstone' };
const result = insert('user', values).toParams({ placeholder: '?%d' });
assert.equal(result.text, 'INSERT INTO "user" (first_name, last_name) VALUES (?1, ?2)');
assert.deepEqual(result.values, ['Fred', 'Flintstone']);
});
it('should generate node-mysql style params', function() {
const values = { first_name: 'Fred', last_name: 'Flintstone' };
const result = insert('user', values).toParams({ placeholder: '?' });
assert.equal(result.text, 'INSERT INTO "user" (first_name, last_name) VALUES (?, ?)');
assert.deepEqual(result.values, ['Fred', 'Flintstone']);
});
it('should output non-numeric params in SQL order', function() {
const result = select().from('user').where($in(sql.val(5), [3, 5, 10])).toParams({ placeholder: '?' });
assert.equal(result.text, 'SELECT * FROM "user" WHERE ? IN (?, ?, ?)');
assert.deepEqual(result.values, [5, 3, 5, 10]);
});
it('should properly parameterize subqueries', function() {
const values = { first_name: 'Fred' };
checkParams(select(select('last_name').from('user').where(values)),
'SELECT (SELECT last_name FROM "user" WHERE first_name = $1)',
['Fred']);
});
it('should properly parameterize subqueries with a join', function() {
const values = { first_name: 'Fred' };
const query = select().from(select().from('user').where(values).as('subuser'))
.join('other_table', { 'other_table.id': 'subuser.id' });
checkParams(query,
'SELECT * FROM (SELECT * FROM "user" WHERE first_name = $1) subuser INNER JOIN other_table ON other_table.id = subuser.id',
['Fred']);
});
it('should properly parameterize subqueries in updates', function() {
const addr_id_for_usr = select('id').from('address').where('usr_id', sql('"user".id')).and('active', true);
checkParams(update('user').set('addr_id', addr_id_for_usr),
'UPDATE "user" SET addr_id = (SELECT id FROM address WHERE usr_id = "user".id AND active = $1)',
[true]);
});
it('should properly merge parameterized sub-expressions with $%d placeholders', function() {
checkParams(select().from('tbl').where(or(sql('a = $1', 444), sql('b = $1', 555), sql('c = $1', 666))),
'SELECT * FROM tbl WHERE a = $1 OR b = $2 OR c = $3',
[444, 555, 666]);
});
});
describe('value handling', function() {
it('should escape single quotes when toString() is used', function() {
check(update('user', { name: "Muad'Dib" }),
"UPDATE \"user\" SET name = 'Muad''Dib'");
});
it('should escape multiple single quotes in the same string', function() {
check(update('address', { city: "Liu'e, Hawai'i" }),
"UPDATE address SET city = 'Liu''e, Hawai''i'");
});
it('should support sql.val() to pass in values where columns are expected', function() {
check(select().from('user').where(sql.val('Fred'), sql('first_name')),
"SELECT * FROM \"user\" WHERE 'Fred' = first_name");
});
it('should support value objects with differing field orders', function() {
check(insert('user').values([{ id: 1, name: 'Fred' }, { name: 'Barney', id: 2 }]),
"INSERT INTO \"user\" (id, name) VALUES (1, 'Fred'), (2, 'Barney')");
});
describe('.toString() value conversions', function() {
it('should, by default, convert dates to SQL TIMESTAMP WITH TIME ZONE format', function() {
check(update('user', { birthdate: new Date('1980-01-01T00:00:00Z') }),
"UPDATE \"user\" SET birthdate = TIMESTAMP WITH TIME ZONE '1980-01-01 00:00:00.000+00:00'");
});
it('should work with sql() SQL literal strings', function() {
check(select().from('person').where(sql('some_func($, $)', ['a', 'b'])),
"SELECT * FROM person WHERE some_func('a', 'b')");
});
it('should escape single quotes when sql() SQL literal strings are used', function() {
check(select().from('person').where(sql('some_func($)', ["Muad'Dib"])),
"SELECT * FROM person WHERE some_func('Muad''Dib')");
});
it('should support user-supplied conversions', function() {
const orig_bool = sql.conversions.Boolean;
sql.conversions.Boolean = function(bool: boolean) { return bool ? '1' : '0'; };
const str = del('user').where('active', false).toString();
sql.conversions.Boolean = orig_bool;
assert.equal(str, 'DELETE FROM "user" WHERE active = 0');
});
});
});
it('should expand abbreviations in FROM and JOINs', function() {
check(select().from('usr').join('psn', { 'usr.psn_fk': 'psn.pk' }),
'SELECT * FROM "user" usr INNER JOIN person psn ON usr.psn_fk = psn.pk');
});
it('should expand left_tbl on all joins', function() {
const left_tbls: any[] = [];
const orig = sql.joinCriteria();
sql.joinCriteria(function(left_tbl) {
left_tbls.push(left_tbl);
// return orig.apply(this, arguments);
return orig.apply();
});
select().from('test').join('psn').join('usr').toString();
sql.joinCriteria(orig);
assert(left_tbls.indexOf('person') > -1 && left_tbls.indexOf('psn') === -1, `left_tbl is not expanded: [ ${left_tbls.join(',')} ]`);
});
it('should support aliases', function() {
check(select().from('user usr2').join('address addr2'),
'SELECT * FROM "user" usr2 INNER JOIN address addr2 ON usr2.addr_fk = addr2.pk');
});
it('should auto-generate join criteria using supplied joinCriteria() function', function() {
check(select().from('usr').join('psn'),
'SELECT * FROM "user" usr INNER JOIN person psn ON usr.psn_fk = psn.pk');
});
it('should auto-generate join criteria to multiple tables', function() {
check(select().from('usr').join('psn').join('addr'),
'SELECT * FROM "user" usr ' +
'INNER JOIN person psn ON usr.psn_fk = psn.pk ' +
'INNER JOIN address addr ON psn.addr_fk = addr.pk');
});
it('should auto-generate join criteria from a single table to multiple tables', function() {
check(select().from('usr').join('psn', 'addr'),
'SELECT * FROM "user" usr ' +
'INNER JOIN person psn ON usr.psn_fk = psn.pk ' +
'INNER JOIN address addr ON usr.addr_fk = addr.pk');
});
it('should handle unions', function() {
check(select().from('usr').where({ name: 'Roy' })
.union(select().from('usr').where({ name: 'Moss' }))
.union(select().from('usr').where({ name: 'The elders of the internet' })),
"SELECT * FROM \"user\" usr WHERE name = 'Roy'" +
" UNION SELECT * FROM \"user\" usr WHERE name = 'Moss'" +
" UNION SELECT * FROM \"user\" usr WHERE name = 'The elders of the internet'");
});
it('should handle chained unions', function() {
check(select().from('usr').where({ name: 'Roy' })
.union().select().from('usr').where({ name: 'blurns' }),
"SELECT * FROM \"user\" usr WHERE name = 'Roy'" +
" UNION SELECT * FROM \"user\" usr WHERE name = 'blurns'");
});
it('should handle chained unions with params', function() {
checkParams(select().from('usr').where({ name: 'Roy' })
.union().select().from('usr').where({ name: 'Moss' }),
"SELECT * FROM \"user\" usr WHERE name = $1" +
" UNION SELECT * FROM \"user\" usr WHERE name = $2", ['Roy', 'Moss']);
});
it('should handle unions with params', function() {
checkParams(select().from('usr').where({ name: 'Roy' })
.union(select().from('usr').where({ name: 'Moss' }))
.union(select().from('usr').where({ name: 'The elders of the internet' })),
'SELECT * FROM "user" usr WHERE name = $1' +
' UNION SELECT * FROM "user" usr WHERE name = $2' +
' UNION SELECT * FROM "user" usr WHERE name = $3',
['Roy', 'Moss', 'The elders of the internet']);
});
describe('UPDATE statements', function() {
it('should handle .set() with (key, value)', function() {
check(update('user').set('name', 'Fred'),
"UPDATE \"user\" SET name = 'Fred'");
});
it('should handle .values() with an object literal', function() {
check(update('user').values({ name: 'Fred' }),
"UPDATE \"user\" SET name = 'Fred'");
});
it('should handle multiple .set()s with object literals', function() {
check(update('user').set({ name: 'Fred' }).set({ last_name: 'Flintstone' }),
"UPDATE \"user\" SET name = 'Fred', last_name = 'Flintstone'");
});
it('should handle multiple .values() with (key, value)', function() {
check(update('user').values('name', 'Fred').values('last_name', 'Flintstone'),
"UPDATE \"user\" SET name = 'Fred', last_name = 'Flintstone'");
});
it('should handle values argument', function() {
check(update('user', { name: 'Fred' }),
"UPDATE \"user\" SET name = 'Fred'");
});
});
describe('INSERT statements', function() {
it('should take an object of column/value pairs', function() {
check(insert('user', { id: 33, name: 'Fred' }),
"INSERT INTO \"user\" (id, name) VALUES (33, 'Fred')");
});
it('should insert a null for undefined', function() {
check(insert('user', { id: 33, name: undefined }),
"INSERT INTO \"user\" (id, name) VALUES (33, null)");
});
it('should insert a null for null', function() {
check(insert('user', { id: 33, name: null }),
"INSERT INTO \"user\" (id, name) VALUES (33, null)");
});
it('should take an array of columns & values', function() {
check(insert('user', ['id', 'name']).values([33, 'Fred']),
"INSERT INTO \"user\" (id, name) VALUES (33, 'Fred')");
});
it('should take multiple parameters of columns & values', function() {
check(insert('user', 'id', 'name').values(33, 'Fred'),
"INSERT INTO \"user\" (id, name) VALUES (33, 'Fred')");
});
it('should take an array of objects', function() {
check(insert('user', [{ id: 33, name: 'Fred' }, { id: 34, name: 'Wilma' }]),
"INSERT INTO \"user\" (id, name) VALUES (33, 'Fred'), (34, 'Wilma')");
});
describe('.values()', function() {
it('should take an array of arrays', function() {
check(insert('user', ['id', 'name']).values([[33, 'Fred'], [34, 'Wilma']]),
"INSERT INTO \"user\" (id, name) VALUES (33, 'Fred'), (34, 'Wilma')");
});
});
describe('.into()', function() {
it('should take an object of column/value pairs', function() {
check(insert().into('user', { id: 33, name: 'Fred' }),
"INSERT INTO \"user\" (id, name) VALUES (33, 'Fred')");
});
it('should take an array of columns & values', function() {
check(insert().into('user', ['id', 'name']).values([33, 'Fred']),
"INSERT INTO \"user\" (id, name) VALUES (33, 'Fred')");
});
it('should take multiple parameters of columns & values', function() {
check(insert().into('user', 'id', 'name').values(33, 'Fred'),
"INSERT INTO \"user\" (id, name) VALUES (33, 'Fred')");
});
});
});
describe('SELECT clause', function() {
it('should handle an array', function() {
check(select(['one', 'order']).from('user'),
'SELECT one, "order" FROM "user"');
});
it('should handle multiple args', function() {
check(select('one', 'order').from('user'),
'SELECT one, "order" FROM "user"');
});
it('should default to *', function() {
check(select().from('user'),
'SELECT * FROM "user"');
});
it('should handle a comma-delimited str', function() {
check(select('one, order').from('user'),
'SELECT one, "order" FROM "user"');
});
it('should handle being called multiple times', function() {
check(select('one, order').select(['two', 'desc']).select('three', 'four').from('user'),
'SELECT one, "order", two, "desc", three, four FROM "user"');
});
it('should support DISTINCT', function() {
check(select('one, order').distinct('two, desc').from('user'),
'SELECT DISTINCT one, "order", two, "desc" FROM "user"');
});
it('should support FOR UPDATE', function() {
check(select().from('user').forUpdate(),
'SELECT * FROM "user" FOR UPDATE');
});
it('should support FOR UPDATE w/ col list', function() {
check(select().from('user').forUpdate().of('user'),
'SELECT * FROM "user" FOR UPDATE OF "user"');
});
it('should support FOR UPDATE OF ... NO WAIT', function() {
check(select().from('user').forUpdate().of('user').noWait(),
'SELECT * FROM "user" FOR UPDATE OF "user" NO WAIT');
});
});
describe('.from()', function() {
it('should handle an array', function() {
check(select().from(['one', 'two', 'usr']),
'SELECT * FROM one, two, "user" usr');
});
it('should handle multiple args', function() {
check(select().from('one', 'two', 'usr'),
'SELECT * FROM one, two, "user" usr');
});
it('should handle a comma-delimited string', function() {
check(select().from('one, two, usr'),
'SELECT * FROM one, two, "user" usr');
});
it('should handle being called multiple times', function() {
check(select().from('one', 'usr').from(['two', 'psn']).from('three, addr'),
'SELECT * FROM one, "user" usr, two, person psn, three, address addr');
});
it('should handle subquery object', function() {
check(select().from(select().from('user').as('subq')),
'SELECT * FROM (SELECT * FROM "user") subq');
});
it('should handle subquery string', function() {
check(select().from('(SELECT * FROM "user") subq'),
'SELECT * FROM (SELECT * FROM "user") subq');
});
});
describe('select().into() should insert into a new table', function() {
it('.into()', function() {
check(select().into('new_user').from('user'),
'SELECT * INTO new_user FROM "user"');
});
it('.intoTable()', function() {
check(select().intoTable('new_user').from('user'),
'SELECT * INTO new_user FROM "user"');
});
it('intoTemp()', function() {
check(select().intoTemp('new_user').from('user'),
'SELECT * INTO TEMP new_user FROM "user"');
});
it('intoTempTable()', function() {
check(select().intoTempTable('new_user').from('user'),
'SELECT * INTO TEMP new_user FROM "user"');
});
});
describe('insert().into() should insert into a preexisting table', function() {
it('insert().into().select()', function() {
check(insert().into('new_user', 'id', 'addr_id')
.select('id', 'addr_id').from('user'),
'INSERT INTO new_user (id, addr_id) SELECT id, addr_id FROM "user"');
});
it('insert().into().select() with no columns', function() {
check(insert().into('new_user')
.select('id', 'addr_id').from('user'),
'INSERT INTO new_user SELECT id, addr_id FROM "user"');
});
it('insert().select()', function() {
check(insert('new_user', 'id', 'addr_id')
.select('id', 'addr_id').from('user'),
'INSERT INTO new_user (id, addr_id) SELECT id, addr_id FROM "user"');
});
it('insert().select() with params', function() {
check(insert('new_user', 'id', 'addr_id')
.select('id', 'addr_id').from('user').where({ active: true }).toParams().text,
'INSERT INTO new_user (id, addr_id) SELECT id, addr_id FROM "user" WHERE active = $1');
});
});
describe('GROUP BY clause', function() {
it('should support single group by', function() {
check(select().from('user').groupBy('last_name'),
'SELECT * FROM "user" GROUP BY last_name');
});
it('should support multiple groupBy() args w/ reserved words quoted', function() {
check(select().from('user').groupBy('last_name', 'order'),
'SELECT * FROM "user" GROUP BY last_name, "order"');
});
it('should support .groupBy().groupBy()', function() {
check(select().from('user').groupBy('last_name').groupBy('order'),
'SELECT * FROM "user" GROUP BY last_name, "order"');
});
it('should support an array', function() {
check(select().from('user').groupBy(['last_name', 'order']),
'SELECT * FROM "user" GROUP BY last_name, "order"');
});
});
describe('.order() / .orderBy()', function() {
it('should support .orderBy(arg1, arg2)', function() {
check(select().from('user').orderBy('last_name', 'order'),
'SELECT * FROM "user" ORDER BY last_name, "order"');
});
it('should support an array', function() {
check(select().from('user').orderBy(['last_name', 'order']),
'SELECT * FROM "user" ORDER BY last_name, "order"');
});
it('should support being called multiple times', function() {
check(select().from('user').orderBy('last_name').orderBy('order'),
'SELECT * FROM "user" ORDER BY last_name, "order"');
});
});
describe('joins', function() {
it('.join() should accept a comma-delimited string', function() {
check(select().from('usr').join('psn, addr'),
'SELECT * FROM "user" usr ' +
'INNER JOIN person psn ON usr.psn_fk = psn.pk ' +
'INNER JOIN address addr ON usr.addr_fk = addr.pk');
});
it('.leftJoin() should generate a left join', function() {
check(select().from('usr').leftJoin('addr'),
'SELECT * FROM "user" usr ' +
'LEFT JOIN address addr ON usr.addr_fk = addr.pk');
});
it('.leftOuterJoin() should generate a left join', function() {
check(select().from('usr').leftOuterJoin('addr'),
'SELECT * FROM "user" usr ' +
'LEFT JOIN address addr ON usr.addr_fk = addr.pk');
});
it('.rightJoin() should generate a right join', function() {
check(select().from('usr').rightJoin('addr'),
'SELECT * FROM "user" usr ' +
'RIGHT JOIN address addr ON usr.addr_fk = addr.pk');
});
it('.rightOuterJoin() should generate a right join', function() {
check(select().from('usr').rightOuterJoin('addr'),
'SELECT * FROM "user" usr ' +
'RIGHT JOIN address addr ON usr.addr_fk = addr.pk');
});
it('.fullJoin() should generate a full join', function() {
check(select().from('usr').fullJoin('addr'),
'SELECT * FROM "user" usr ' +
'FULL JOIN address addr ON usr.addr_fk = addr.pk');
});
it('.fullOuterJoin() should generate a full join', function() {
check(select().from('usr').fullOuterJoin('addr'),
'SELECT * FROM "user" usr ' +
'FULL JOIN address addr ON usr.addr_fk = addr.pk');
});
it('.crossJoin() should generate a cross join', function() {
check(select().from('usr').crossJoin('addr'),
'SELECT * FROM "user" usr ' +
'CROSS JOIN address addr');
});
it('join() should accept an expression for the on argument', function() {
check(select().from('usr').join('addr', eq('usr.addr_id', sql('addr.id'))),
'SELECT * FROM "user" usr INNER JOIN address addr ON usr.addr_id = addr.id');
});
it('join() should accept an array for the on argument (for JOIN USING)', function() {
check(select().from('usr').join('addr', ['addr_id', 'country_id']),
'SELECT * FROM "user" usr INNER JOIN address addr USING (addr_id, country_id)');
});
});
describe('.on()', function() {
it('should accept an object literal', function() {
check(select().from('usr').join('addr').on({ 'usr.addr_id': 'addr.id' }),
'SELECT * FROM "user" usr ' +
'INNER JOIN address addr ON usr.addr_id = addr.id');
});
it('should accept a (key, value) pair', function() {
check(select().from('usr').join('addr').on('usr.addr_id', 'addr.id'),
'SELECT * FROM "user" usr ' +
'INNER JOIN address addr ON usr.addr_id = addr.id');
});
it('can be called multiple times', function() {
check(select().from('usr', 'psn').join('addr').on({ 'usr.addr_id': 'addr.id' })
.on('psn.addr_id', 'addr.id'),
'SELECT * FROM "user" usr, person psn ' +
'INNER JOIN address addr ON usr.addr_id = addr.id AND psn.addr_id = addr.id');
});
it('can be called multiple times w/ an object literal', function() {
check(select().from('usr', 'psn').join('addr').on({ 'usr.addr_id': 'addr.id' })
.on({ 'psn.addr_id': 'addr.id' }),
'SELECT * FROM "user" usr, person psn ' +
'INNER JOIN address addr ON usr.addr_id = addr.id AND psn.addr_id = addr.id');
});
it('should accept an expression', function() {
check(select().from('usr').join('addr').on(eq('usr.addr_id', sql('addr.id'))),
'SELECT * FROM "user" usr INNER JOIN address addr ON usr.addr_id = addr.id');
});
it('should not proceed if .using() has already been used', function() {
assert.throws(function() {
select().from('t1').join('t2').using('id').on({ 't1.t2_id': 't2.id' });
});
});
});
describe('.using()', function() {
it('should accept a comma-delimited string', function() {
check(select().from('usr').join('addr').using('addr_id, contrived_id'),
'SELECT * FROM "user" usr ' +
'INNER JOIN address addr USING (addr_id, contrived_id)');
});
it('should accept multiple arguments', function() {
check(select().from('usr').join('addr').using('addr_id', 'contrived_id'),
'SELECT * FROM "user" usr ' +
'INNER JOIN address addr USING (addr_id, contrived_id)');
});
it('should accept an array literal', function() {
check(select().from('usr').join('addr').using(['addr_id', 'contrived_id']),
'SELECT * FROM "user" usr ' +
'INNER JOIN address addr USING (addr_id, contrived_id)');
});
it('should not proceed if .on() has already been used', function() {
assert.throws(function() {
select().from('t1').join('t2').on({ 't1.t2_id': 't2.id' }).using('id');
});
});
});
describe('natural joins', function() {
it('.naturalJoin() should accept a comma-delimited string', function() {
check(select().from('usr').naturalJoin('psn, addr'),
'SELECT * FROM "user" usr ' +
'NATURAL INNER JOIN person psn ' +
'NATURAL INNER JOIN address addr');
});
it('.naturalLeftJoin() should generate a natural left join', function() {
check(select().from('usr').naturalLeftJoin('addr'),
'SELECT * FROM "user" usr ' +
'NATURAL LEFT JOIN address addr');
});
it('.naturalLeftOuterJoin() should generate a natural left join', function() {
check(select().from('usr').naturalLeftOuterJoin('addr'),
'SELECT * FROM "user" usr ' +
'NATURAL LEFT JOIN address addr');
});
it('.naturalRightJoin() should generate a natural right join', function() {
check(select().from('usr').naturalRightJoin('addr'),
'SELECT * FROM "user" usr ' +
'NATURAL RIGHT JOIN address addr');
});
it('.naturalRightOuterJoin() should generate a natural right join', function() {
check(select().from('usr').naturalRightOuterJoin('addr'),
'SELECT * FROM "user" usr ' +
'NATURAL RIGHT JOIN address addr');
});
it('.naturalFullJoin() should generate a natural full join', function() {
check(select().from('usr').naturalFullJoin('addr'),
'SELECT * FROM "user" usr ' +
'NATURAL FULL JOIN address addr');
});
it('.naturalFullOuterJoin() should generate a natural full join', function() {
check(select().from('usr').naturalFullOuterJoin('addr'),
'SELECT * FROM "user" usr ' +
'NATURAL FULL JOIN address addr');
});
});
describe('WHERE clauses', function() {
it('should AND multiple where() criteria by default', function() {
check(select().from('user').where({
first_name: 'Fred',
last_name: 'Flintstone'
}),
"SELECT * FROM \"user\" WHERE first_name = 'Fred' AND last_name = 'Flintstone'");
});
it('should AND multiple where()s by default', function() {
check(select().from('user').where({ first_name: 'Fred' })
.where({ last_name: 'Flintstone' }),
"SELECT * FROM \"user\" WHERE first_name = 'Fred' AND last_name = 'Flintstone'");
});
it('should handle explicit .and() with (key, value) args', function() {
check(select().from('user').where('first_name', 'Fred')
.and('last_name', 'Flintstone'),
"SELECT * FROM \"user\" WHERE first_name = 'Fred' AND last_name = 'Flintstone'");
});
it('should handle nested and(or())', function() {
check(select().from('user').where(and({ last_name: 'Flintstone' }, or({ first_name: 'Fred' }, { first_name: 'Wilma' }))),
"SELECT * FROM \"user\" WHERE last_name = 'Flintstone' AND (first_name = 'Fred' OR first_name = 'Wilma')");
});
it('should handle or([...])', function() {
check(select().from('table').where({ this: 'test' }).and(or(['test1', 'test2'].map(function(val) { return eq('that', val); }))),
"SELECT * FROM \"table\" WHERE this = 'test' AND (that = 'test1' OR that = 'test2')");
});
it('and() should be implicit', function() {
check(select().from('user').where({ last_name: 'Flintstone' }, or({ first_name: 'Fred' }, { first_name: 'Wilma' })),
"SELECT * FROM \"user\" WHERE last_name = 'Flintstone' AND (first_name = 'Fred' OR first_name = 'Wilma')");
});
it('should handle like()', function() {
check(select().from('user').where(like('last_name', 'Flint%')),
"SELECT * FROM \"user\" WHERE last_name LIKE 'Flint%'");
});
it('should accept a 3rd escape param to like()', function() {
check(select().from('user').where(like('percent', '100\\%', '\\')),
"SELECT * FROM \"user\" WHERE percent LIKE '100\\%' ESCAPE '\\'");
});
it('should handle not()', function() {
check(select().from('user').where(not({ first_name: 'Fred' })),
"SELECT * FROM \"user\" WHERE NOT first_name = 'Fred'");
});
it('should handle in()', function() {
check(select().from('user').where($in('first_name', ['Fred', 'Wilma'])),
"SELECT * FROM \"user\" WHERE first_name IN ('Fred', 'Wilma')");
});
it('should handle .in() with multiple args', function() {
check(select().from('user').where($in('name', 'Jimmy', 'Owen')),
"SELECT * FROM \"user\" WHERE name IN ('Jimmy', 'Owen')");
});
it('should handle .in() with a subquery', function() {
check(select().from('user').where($in('addr_id', select('id').from('address'))),
'SELECT * FROM "user" WHERE addr_id IN (SELECT id FROM address)');
});
it('should handle exists() with a subquery', function() {
check(select().from('user').where(exists(select().from('address').where({ 'user.addr_id': sql('address.id') }))),
'SELECT * FROM "user" WHERE EXISTS (SELECT * FROM address WHERE "user".addr_id = address.id)');
});
it('should handle exists() with a subquery, parameterized', function() {
checkParams(select().from('user').where('active', true).where(exists(select().from('address').where({ 'user.addr_id': 37 }))),
'SELECT * FROM "user" WHERE active = $1 AND EXISTS (SELECT * FROM address WHERE "user".addr_id = $2)',
[true, 37]);
});
it('should handle isNull()', function() {
check(select().from('user').where(isNull('first_name')),
'SELECT * FROM "user" WHERE first_name IS NULL');
});
it('should handle isNotNull()', function() {
check(select().from('user').where(isNotNull('first_name')),
'SELECT * FROM "user" WHERE first_name IS NOT NULL');
});
it('should automatically generate IS NULL', function() {
check(select().from('user').where({ first_name: null }),
'SELECT * FROM "user" WHERE first_name IS NULL');
});
it('should handle explicit equal()', function() {
check(select().from('user').where(equal('first_name', 'Fred')),
"SELECT * FROM \"user\" WHERE first_name = 'Fred'");
});
it('should automatically generate IS NOT NULL', function() {
check(select().from('user').where(notEq('first_name', null)),
"SELECT * FROM \"user\" WHERE first_name IS NOT NULL");
});
it('should handle lt()', function() {
check(select().from('user').where(lt('order', 5)),
'SELECT * FROM "user" WHERE "order" < 5');
});
it('should handle lte()', function() {
check(select().from('user').where(lte('order', 5)),
'SELECT * FROM "user" WHERE "order" <= 5');
});
it('should handle gt()', function() {
check(select().from('user').where(gt('order', 5)),
'SELECT * FROM "user" WHERE "order" > 5');
});
it('should handle gte()', function() {
check(select().from('user').where(gte('order', 5)),
'SELECT * FROM "user" WHERE "order" >= 5');
});
it('should handle between()', function() {
check(select().from('user').where(between('name', 'Frank', 'Fred')),
"SELECT * FROM \"user\" WHERE name BETWEEN 'Frank' AND 'Fred'");
});
it('should do nothing for null, undefined, {}', function() {
check(select().from('user').where(),
"SELECT * FROM \"user\"");
check(select().from('user').where(null),
"SELECT * FROM \"user\"");
check(select().from('user').where({}),
"SELECT * FROM \"user\"");
check(select().from('user').where(undefined),
"SELECT * FROM \"user\"");
});
it('should not ignore the 2nd arg if the first is {}', function() {
check(select().from('user').where({}, { name: 'Fred' }),
"SELECT * FROM \"user\" WHERE name = 'Fred'");
});
describe('customizations', function() {
it('should support customized criteria', function() {
check(select().from('table').where(arraysToOrs({ this: 'test', that: ['test1', 'test2'] })),
"SELECT * FROM \"table\" WHERE this = 'test' AND (that = 'test1' OR that = 'test2')");
});
it('should support changing the default array handling', function() {
const proto = sql.select.prototype;
const orig = proto.where;
proto.where = function(criteria: sql.OnCriteria) {
// return orig.call(this, arraysToOrs(criteria));
return orig.call();
};
check(select().from('table').where({ this: 'test', that: ['test1', 'test2'] }),
"SELECT * FROM \"table\" WHERE this = 'test' AND (that = 'test1' OR that = 'test2')");
proto.where = orig;
});
function arraysToOrs(criteria: sql.WhereObject) {
const where = and();
for (const col in criteria) {
const val = criteria[col];
let expr: sql.WhereExpression;
expr = Array.isArray(val) ? or(val.map(function(val) { return eq(col, val); }))
: expr = eq(col, val);
where.expressions.push(expr);
}
return where;
}
it('should support converting expressions to string', function() {
check(and({ this: 'test' }, $in('that', ['v1', 'v2'])),
"(this = 'test' AND that IN ('v1', 'v2'))");
});
it('should support raw sql blocks in expressions', function() {
check(and({ this: 'test' }, sql('field is null')),
"(this = 'test' AND field is null)");
});
it('should support converting ?-parameterized sql blocks to string', function() {
check(sql('field = ?', 123).toString({ placeholder: '?' }),
"field = 123");
});
it('should support converting default-parameterized sql blocks to string', function() {
check(sql('field = $1', 123),
"field = 123");
});
});
});
describe('should quote column names with capitals in them', function() {
it('in SELECT', function() {
check(select('Name').from('user'),
'SELECT "Name" FROM "user"');
});
it('in SELECT, after tbl prefix, before AS suffix', function() {
check(select('user.Name AS UserName').from('user'),
'SELECT "user"."Name" AS UserName FROM "user"');
});
it('in SELECT, after tbl prefix, with the optional "AS" omitted', function() {
check(select('user.Name UserName').from('user'),
'SELECT "user"."Name" UserName FROM "user"');
});
it('in SELECT, with the optional "AS" omitted', function() {
check(select('Name UserName').from('user'),
'SELECT "Name" UserName FROM "user"');
});
});
describe('should quote table names that are reserved words or have capitals', function() {
it('in joins', function() {
check(select('Name UserName').from('person').join('user', { 'person.usr_id': 'user.id' }),
'SELECT "Name" UserName FROM person INNER JOIN "user" ON person.usr_id = "user".id');
});
it('unless they are wrapped in sql() literals (to query reserved system tables)', function() {
check(select(sql('user.id')).from(sql('user')),
'SELECT user.id FROM user');
});
});
describe('should quote reserved words in column names', function() {
it('in ORDER BY', function() {
check(select().from('usr').orderBy('order'),
'SELECT * FROM "user" usr ORDER BY "order"');
});
it('in SELECT', function() {
check(select('desc').from('usr'),
'SELECT "desc" FROM "user" usr');
});
it('in JOIN ONs', function() {
check(select().from('usr').join('psn', { 'usr.order': 'psn.order' }),
'SELECT * FROM "user" usr INNER JOIN person psn ON usr."order" = psn."order"');
});
it('in JOIN USINGs', function() {
check(select().from('usr').join('psn').using('order'),
'SELECT * FROM "user" usr INNER JOIN person psn USING ("order")');
});
it('in INSERT', function() {
check(insert('user').values({ order: 1 }),
'INSERT INTO "user" ("order") VALUES (1)');
});
it('in alternative insert() API', function() {
check(insert('user', 'order').values(1),
'INSERT INTO "user" ("order") VALUES (1)');
});
it('with a db and table prefix and a suffix', function() {
check(select('db.person.desc AS psn_desc').from('person'),
'SELECT db.person."desc" AS psn_desc FROM person');
});
it('should quote sqlite reserved words', function() {
check(select('action').from('user'),
'SELECT "action" FROM "user"');
});
it('should not quote reserved words in SELECT expressions', function() {
check(select("CASE WHEN name = 'Fred' THEN 1 ELSE 0 AS security_level").from('user'),
"SELECT CASE WHEN name = 'Fred' THEN 1 ELSE 0 AS security_level FROM \"user\"");
});
});
describe('subqueries in <, >, etc', function() {
it('should support a subquery in >', function() {
const count_addrs_for_usr = select('count(*)').from('address').where({ 'user.addr_id': sql('address.id') });
check(select().from('user').where(gt(count_addrs_for_usr, 5)),
'SELECT * FROM "user" WHERE (SELECT count(*) FROM address WHERE "user".addr_id = address.id) > 5');
});
it('should support a subquery in <=', function() {
const count_addrs_for_usr = select('count(*)').from('address').where({ 'user.addr_id': sql('address.id') });
check(select().from('user').where(lte(count_addrs_for_usr, 5)),
'SELECT * FROM "user" WHERE (SELECT count(*) FROM address WHERE "user".addr_id = address.id) <= 5');
});
it('should support = ANY (subquery) quantifier', function() {
check(select().from('user').where(eqAny('user.id', select('user_id').from('address'))),
'SELECT * FROM "user" WHERE "user".id = ANY (SELECT user_id FROM address)');
});
it('should support <> ANY (subquery) quantifier', function() {
check(select().from('user').where(notEqAny('user.id', select('user_id').from('address'))),
'SELECT * FROM "user" WHERE "user".id <> ANY (SELECT user_id FROM address)');
});
});
describe('deep Statement.clone()', function() {
it('should deep clone WHERE expressions', function() {
const sel = select().from('user').where({ first_name: 'Fred' });
sel.clone().where({ last_name: 'Flintstone' });
check(sel, "SELECT * FROM \"user\" WHERE first_name = 'Fred'");
});
it('should deep clone .order()', function() {
const sel = select().from('user').order('name');
sel.clone().order('last_name');
check(sel, 'SELECT * FROM "user" ORDER BY name');
});
it('should deep clone .join()', function() {
const sel = select().from('user').join('addr');
sel.clone().join('psn');
check(sel, 'SELECT * FROM "user" INNER JOIN address addr ON "user".addr_fk = addr.pk');
});
it('should clone values', function() {
const ins = insert('user', { first_name: 'Fred' });
ins.clone().values({ last_name: 'Flintstone' });
check(ins, "INSERT INTO \"user\" (first_name) VALUES ('Fred')");
});
it('should clone $in subqueries', function() {
const sel = select().from('user').where($in('first_name', select('first_name').from('user')));
sel.clone().where('last_name', 'Flintstone');
check(sel, "SELECT * FROM \"user\" WHERE first_name IN (SELECT first_name FROM \"user\")");
});
});
describe('the AS keyword', function() {
it('should not generate invalid SQL', function() {
check(select().from('user AS usr').join('addr'),
'SELECT * FROM "user" AS usr INNER JOIN address addr ON usr.addr_fk = addr.pk');
});
});
describe('delete()', function() {
it('should generate a DELETE statement', function() {
check(del('user'),
'DELETE FROM "user"');
});
it('should support .from()', function() {
check(del().from('user'),
'DELETE FROM "user"');
});
it('should generate a DELETE statement with a WHERE clause', function() {
check(del('user').where('first_name', 'Fred'),
"DELETE FROM \"user\" WHERE first_name = 'Fred'");
});
});
describe('_extension()', function() {
it('should shield base', function() {
const ext = sql._extension();
ext.prop = 1;
assert.equal(sql.prop, undefined);
ext.select.prop = 1;
// assert.equal(sql.select.returning, undefined);
assert(ext.select.prototype.clauses !== sql.select.prototype.clauses);
});
});
});
function describe(...param: any[]) { }
function it(...param: any[]) { }
function check(...param: any[]) { }
function checkParams(...param: any[]) { }