Merge pull request #24618 from adn05/master

Add definitions for sql-bricks package
This commit is contained in:
Nathan Shively-Sanders
2018-03-29 15:29:01 -07:00
committed by GitHub
4 changed files with 1388 additions and 0 deletions

409
types/sql-bricks/index.d.ts vendored Normal file
View File

@@ -0,0 +1,409 @@
// Type definitions for sql-bricks 2.0
// Project: http://csnw.github.io/sql-bricks
// Definitions by: Narcisse Assogba <https://github.com/adn05>
// Paleo <https://github.com/paleo>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
declare namespace SqlBricks {
/**
* Statement is an abstract base class for all statements (SELECT, INSERT, UPDATE, DELETE)
* and should never be instantiated directly. It is exposed because it can be used with the
* instanceof operator to easily determine whether something is a SQL Bricks statement: my_var instanceof Statement.
*/
interface Statement {
/**
* Clones a statement so that subsequent modifications do not affect the original statement.
*/
clone(): this
/**
* Returns the non-parameterized SQL for the statement. This is called implicitly by Javascript when using a Statement anywhere that a string is expected (string concatenation, Array.join(), etc).
* While toString() is easy to use, it is not recommended in most cases because:
* It doesn't provide robust protection against SQL injection attacks (it just does basic escaping)
* It doesn't provide as much support for complex data types (objects, arrays, etc, are "stringified" before being passed to your database driver, which then has to interpret them correctly)
* It does not provide the same level of detail in error messages (see this issue)
* For the above reasons, it is usually better to use toParams().
*/
toString(): string
/**
* Returns an object with two properties: a parameterized text string and a values array. The values are populated with anything on the right-hand side
* of a WHERE criteria,as well as any values passed into an insert() or update() (they can be passed explicitly with val() or opted out of with sql())
* @param options A placeholder option of '?%d' can be passed to generate placeholders compatible with node-sqlite3 (%d is replaced with the parameter #):
* @example
* update('person', {'first_name': 'Fred'}).where({'last_name': 'Flintstone'}).toParams({placeholder: '?%d'});
* // {"text": "UPDATE person SET first_name = ?1 WHERE last_name = ?2", "values": ["Fred", "Flintstone"]}
*/
toParams(options?: { placeholder: string }): SqlBricksParam
}
interface SqlBricksParam {
text: string
values: any[]
}
type TableName = string | SelectStatement
interface OnCriteria {
[column: string]: string
}
interface WhereObject {
[column: string]: any
}
interface WhereGroup {
op?: string
expressions: WhereExpression[]
}
interface WhereBinary {
op: string
col: string | SelectStatement
val: any
quantifier: string
}
/**
* When a non-expression object is passed somewhere a whereExpression is expected,
* each key/value pair will be ANDed together:
*/
type WhereExpression = WhereGroup | WhereBinary | WhereObject | string
/**
* A SELECT statement
*/
interface SelectStatement extends Statement {
/**
* Appends additional columns to an existing query.
* @param columns can be passed as multiple arguments, a comma-delimited string or an array.
*/
select(...columns: Array<string | SelectStatement>): SelectStatement
/**
* Appends additional columns to an existing query.
* @param columns can be passed as multiple arguments, a comma-delimited string or an array.
*/
select(columns: string[] | SelectStatement[]): SelectStatement
as(alias: string): SelectStatement
distinct(...columns: Array<string | SelectStatement>): SelectStatement
distinct(columns: string[] | SelectStatement[]): SelectStatement
/**
* Makes the query a SELECT ... INTO query (which creates a new table with the results of the query).
* @alias intoTable
* @param tbl new table to create
*/
into(tbl: TableName): SelectStatement
/**
* Makes the query a SELECT ... INTO query (which creates a new table with the results of the query).
* @alias into
* @param tbl new table to create
*/
intoTable(tbl: TableName): SelectStatement
intoTemp(tbl: TableName): SelectStatement
intoTempTable(tbl: TableName): SelectStatement
/**
* Table names can be passed in as multiple string arguments, a comma-delimited string or an array.
* @param tbls table names
*/
from(...tbls: TableName[]): SelectStatement
/**
* Table names can be passed in as multiple string arguments, a comma-delimited string or an array.
* @param tbls array of table names
*/
from(tbls: TableName[]): SelectStatement
/**
* Adds the specified join to the query.
* @alias innerJoin
* @param tbl can include an alias after a space or after the 'AS' keyword ('my_table my_alias').
* @param onCriteria is optional if a joinCriteria function has been supplied.
*/
join(tbl: string, criteria?: OnCriteria | string[] | WhereExpression): SelectStatement
join(tbl: string, onCol1: string, onCol2: string): SelectStatement
join(firstTbl: string, ...otherTbls: string[]): SelectStatement
leftJoin(tbl: string, criteria?: OnCriteria | string[] | WhereExpression): SelectStatement
leftJoin(tbl: string, onCol1: string, onCol2: string): SelectStatement
leftJoin(firstTbl: string, ...otherTbls: string[]): SelectStatement
rightJoin(tbl: string, criteria?: OnCriteria | string[] | WhereExpression): SelectStatement
rightJoin(tbl: string, onCol1: string, onCol2: string): SelectStatement
rightJoin(firstTbl: string, ...otherTbls: string[]): SelectStatement
fullJoin(tbl: string, criteria?: OnCriteria | string[] | WhereExpression): SelectStatement
fullJoin(tbl: string, onCol1: string, onCol2: string): SelectStatement
fullJoin(firstTbl: string, ...otherTbls: string[]): SelectStatement
crossJoin(tbl: string, criteria?: OnCriteria | string[] | WhereExpression): SelectStatement
crossJoin(tbl: string, onCol1: string, onCol2: string): SelectStatement
crossJoin(firstTbl: string, ...otherTbls: string[]): SelectStatement
innerJoin(tbl: string, criteria?: OnCriteria | string[] | WhereExpression): SelectStatement
innerJoin(tbl: string, onCol1: string, onCol2: string): SelectStatement
innerJoin(firstTbl: string, ...otherTbls: string[]): SelectStatement
leftOuterJoin(tbl: string, criteria?: OnCriteria | string[] | WhereExpression): SelectStatement
leftOuterJoin(tbl: string, onCol1: string, onCol2: string): SelectStatement
leftOuterJoin(firstTbl: string, ...otherTbls: string[]): SelectStatement
rightOuterJoin(tbl: string, criteria?: OnCriteria | string[] | WhereExpression): SelectStatement
rightOuterJoin(tbl: string, onCol1: string, onCol2: string): SelectStatement
rightOuterJoin(firstTbl: string, ...otherTbls: string[]): SelectStatement
fullOuterJoin(tbl: string, criteria?: OnCriteria | string[] | WhereExpression): SelectStatement
fullOuterJoin(tbl: string, onCol1: string, onCol2: string): SelectStatement
fullOuterJoin(firstTbl: string, ...otherTbls: string[]): SelectStatement
on(onCriteria: OnCriteria | WhereExpression): SelectStatement
on(onCol1: string, onCol2: string): SelectStatement
/**
* Joins using USING instead of ON.
* @param columnList columnList can be passed in as one or more string arguments, a comma-delimited string, or an array.
* @example
* select('*').from('person').join('address').using('address_id', 'country_id');
* // SELECT * FROM person INNER JOIN address USING (address_id, country_id)
*/
using(...columnList: string[]): SelectStatement
using(columnList: string[]): SelectStatement
/**
* Adds the specified natural join to the query.
* @param tbl can include an alias after a space or after the 'AS' keyword ('my_table my_alias').
*/
naturalJoin(tbl: string): SelectStatement
naturalLeftJoin(tbl: string): SelectStatement
naturalRightJoin(tbl: string): SelectStatement
naturalFullJoin(tbl: string): SelectStatement
naturalInnerJoin(tbl: string): SelectStatement
naturalLeftOuterJoin(tbl: string): SelectStatement
naturalRightOuterJoin(tbl: string): SelectStatement
naturalFullOuterJoin(tbl: string): SelectStatement
where(column?: string | null, value?: any): SelectStatement
where(...whereExpr: WhereExpression[]): SelectStatement
and(...options: any[]): SelectStatement
/**
* Sets or extends the GROUP BY columns.
* @param columns can take multiple arguments, a single comma-delimited string or an array.
*/
groupBy(...columns: string[]): SelectStatement
groupBy(columns: string[]): SelectStatement
having(column: string, value: string): SelectStatement
having(whereExpr: WhereExpression): SelectStatement
/**
* Sets or extends the list of columns in the ORDER BY clause.
* @param columns can be passed as multiple arguments, a single comma-delimited string or an array.
*/
orderBy(...columns: string[]): SelectStatement
orderBy(columns: string[]): SelectStatement
order(...columns: string[]): SelectStatement
order(columns: string[]): SelectStatement
forUpdate(...tbls: string[]): SelectStatement
of(tlb: string): SelectStatement
noWait(): SelectStatement
union(...stmt: Statement[]): SelectStatement
intersect(...stmt: Statement[]): SelectStatement
minus(...stmt: Statement[]): SelectStatement
except(...stmt: Statement[]): SelectStatement
}
/**
* An INSERT statement
*/
interface InsertStatement extends Statement {
into(tbl: TableName, ...columns: any[]): InsertStatement
intoTable(tbl: TableName, ...columns: any[]): InsertStatement
select(...columns: Array<string | SelectStatement>): SelectStatement
select(columns: string[] | SelectStatement[]): SelectStatement
values(...values: any[]): InsertStatement
}
/**
* An UPDATE statement
*/
interface UpdateStatement extends Statement {
values(...values: any[]): UpdateStatement
set(...values: any[]): UpdateStatement
where(column?: string | null, value?: any): SelectStatement
where(...whereExpr: WhereExpression[]): SelectStatement
and(column?: string | null, value?: any): SelectStatement
and(...whereExpr: WhereExpression[]): SelectStatement
}
/**
* A DELETE statement
*/
interface DeleteStatement extends Statement {
from(...tbls: string[]): DeleteStatement
using(...columnList: string[]): SelectStatement
using(columnList: string[]): SelectStatement
where(column?: string | null, value?: any): SelectStatement
where(...whereExpr: WhereExpression[]): SelectStatement
and(column?: string | null, value?: any): SelectStatement
and(...whereExpr: WhereExpression[]): SelectStatement
}
}
interface SqlBricksFn {
(...params: any[]): any
/**
* Wraps a value (user-supplied string, number, boolean, etc) so that it can be passed into SQL Bricks
* anywhere that a column is expected (the left-hand side of WHERE criteria and many other SQL Bricks APIs)
* @param value value to be wraped
*/
val(value: any): any
/**
* Returns a new INSERT statement. It can be used with or without the new operator.
* @alias insertInto
* @param tbl table name
* @param values a values object or a columns list. Passing a set of columns (as multiple arguments, a comma-delimited string or an array)
* will put the statement into split keys/values mode, where a matching array of values is expected in values()
* @example
* insert('person', {'first_name': 'Fred', 'last_name': 'Flintstone'});
* // INSERT INTO person (first_name, last_name) VALUES ('Fred', 'Flintstone')
*/
insert(tbl?: string, ...values: any[]): SqlBricks.InsertStatement
/**
* Returns a new INSERT statement. It can be used with or without the new operator.
* @alias insert
* @param tbl table name
* @param values a values object or a columns list. Passing a set of columns (as multiple arguments, a comma-delimited string or an array)
* will put the statement into split keys/values mode, where a matching array of values is expected in values()
* @example
* insert('person', {'first_name': 'Fred', 'last_name': 'Flintstone'});
* // INSERT INTO person (first_name, last_name) VALUES ('Fred', 'Flintstone')
*/
insertInto(tbl?: string, ...values: any[]): SqlBricks.InsertStatement
/**
* Returns a new select statement, seeded with a set of columns. It can be used with or without the new keyword.
* @param columns it can be passed in here (or appended later via sel.select() or sel.distinct()) via multiple arguments
* or a comma-delimited string or an array. If no columns are specified, toString() will default to SELECT *.
*/
select(...columns: Array<string | SqlBricks.SelectStatement>): SqlBricks.SelectStatement
select(columns: string[] | SqlBricks.SelectStatement[]): SqlBricks.SelectStatement
/**
* Returns a new UPDATE statement. It can be used with or without the new operator.
* @param tbl table name
* @param values
*/
update(tbl: string, ...values: any[]): SqlBricks.UpdateStatement
/**
* Returns a new DELETE statement. It can be used with or without the new operator.
* @alias deleteFrom
* @param tbl table name
*/
delete(tbl?: string): SqlBricks.DeleteStatement
/**
* Returns a new DELETE statement. It can be used with or without the new operator.
* @alias delete
* @param tbl table name
*/
deleteFrom(tbl?: string): SqlBricks.DeleteStatement
/**
* Registers a set of frequently-used table aliases with SQL Bricks.
* These table aliases can then be used by themselves in from(), join(), etc
* and SQL Bricks will automatically expand them to include the table name as well as the alias.
* @param expansions
* @example
* sql.aliasExpansions({'psn': 'person', 'addr': 'address', 'zip': 'zipcode', 'usr': 'user'});
* select().from('psn').join('addr', {'psn.addr_id': 'addr.id'});
* // SELECT * FROM person psn INNER JOIN address addr ON psn.addr_id = addr.id
*/
aliasExpansions(expansions: { [tbl: string]: string }): void
/**
* Sets a user-supplied function to automatically generate the .on() criteria for joins whenever it is not supplied explicitly.
* @param func
*/
joinCriteria(func?: (...args: any[]) => SqlBricks.OnCriteria): any
_extension(): any
prop: number
conversions: any
//////////////////////////////////////////
////// Where Expression functions //////
//////////////////////////////////////////
/**
* Joins the passed expressions with AND
* @param whereExprs
*/
and(...whereExprs: SqlBricks.WhereExpression[]): SqlBricks.WhereGroup
/**
* Joins the passed expressions with OR:
* @param whereExprs
*/
or(...whereExprs: SqlBricks.WhereExpression[]): SqlBricks.WhereGroup
/**
* Negates the expression by wrapping it in NOT (...)
* (if it is at the top level, the parentheses are unnecessary and will be omitted)
* @param whereExpr
*/
not(whereExpr: SqlBricks.WhereExpression): SqlBricks.WhereGroup
/**
* Generates a BETWEEN
* @param column
* @param value1
* @param value2
*/
between(column: string, value1: any, value2: any): SqlBricks.WhereExpression
isNull(column: string): SqlBricks.WhereExpression
isNotNull(column: string): SqlBricks.WhereExpression
like(column: string, value: any, escapeStr?: string): SqlBricks.WhereExpression
exists(stmt: any): SqlBricks.WhereExpression
in(column: string, stmt: SqlBricks.SelectStatement): SqlBricks.WhereExpression
in(column: string, ...values: any[]): SqlBricks.WhereExpression
/**
* Generates the appropriate relational operator (=, <>, <, <=, > or >=).
* @param column column name or query result
* @param value column value
*/
eq(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
equal(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
notEq(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
lt(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
lte(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
gt(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
gte(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
eqAll(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
notEqAll(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
ltAll(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
lteAll(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
gtAll(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
gteAll(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
eqAny(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
notEqAny(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
ltAny(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
lteAny(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
gtAny(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
gteAny(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
eqSome(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
notEqSome(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
ltSome(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
lteSome(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
gtSome(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
gteSome(column: string | SqlBricks.SelectStatement, value?: any): SqlBricks.WhereBinary
}
declare const SqlBricks: SqlBricksFn
export = SqlBricks

View File

@@ -0,0 +1,949 @@
// 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[]) { }

View File

@@ -0,0 +1,23 @@
{
"compilerOptions": {
"module": "commonjs",
"lib": [
"es6"
],
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"baseUrl": "../",
"typeRoots": [
"../"
],
"types": [],
"noEmit": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.d.ts",
"sql-bricks-tests.ts"
]
}

View File

@@ -0,0 +1,7 @@
{
"extends": "dtslint/dt.json",
"rules": {
"only-arrow-functions": false,
"semicolon":false
}
}