criterion
Version:
criterion allows you to work with (build, combine, reuse, ...) SQL-where-conditions ('x = 5 AND y IS NOT NULL'...) as data (goodbye string-concatenation) and compile them to SQL: it has a succinct mongodb-like query-language, a simple and elegant function
818 lines (640 loc) • 24.9 kB
text/coffeescript
criterion = C = require '../src/criterion'
escape = (x) -> '"' + x + '"'
module.exports =
'successfully making criteria':
'x = 7': (test) ->
SQL = 'x = ?'
ESCAPED = '"x" = ?'
PARAMS = [7]
lang1 = criterion {x: 7}
lang2 = criterion {x: {$eq: 7}}
raw = criterion SQL, PARAMS...
dsl = C.eq(C.escape('x'), 7)
dslReversed = C.eq(7, C.escape('x'))
test.equal lang1.sql(), SQL
test.equal lang1.sql(escape), ESCAPED
test.deepEqual lang1.params(), PARAMS
test.equal lang2.sql(), SQL
test.equal lang2.sql(escape), ESCAPED
test.deepEqual lang2.params(), PARAMS
test.equal raw.sql(), SQL
test.equal raw.sql(escape), SQL
test.deepEqual raw.params(), PARAMS
test.equal dsl.sql(), SQL
test.equal dsl.sql(escape), ESCAPED
test.deepEqual dsl.params(), PARAMS
test.equal dslReversed.sql(), '? = x'
test.done()
'!=': (test) ->
SQL = 'x != ?'
ESCAPED = '"x" != ?'
PARAMS = ['a']
lang = criterion {x: {$ne: 'a'}}
raw = criterion SQL, PARAMS...
dsl = C.ne(C.escape('x'), 'a')
test.equal lang.sql(), SQL
test.equal lang.sql(escape), ESCAPED
test.deepEqual lang.params(), PARAMS
test.equal raw.sql(), SQL
test.equal raw.sql(escape), SQL
test.deepEqual raw.params(), PARAMS
test.equal dsl.sql(), SQL
test.equal dsl.sql(escape), ESCAPED
test.deepEqual dsl.params(), PARAMS
test.done()
'< AND <=': (test) ->
SQL = '(x < ?) AND (y <= ?)'
ESCAPED = '("x" < ?) AND ("y" <= ?)'
PARAMS = [3, 4]
lang = criterion {x: {$lt: 3}, y: {$lte: 4}}
raw = criterion SQL, PARAMS...
dsl = C.and(
C.lt(C.escape('x'), 3)
C.lte(C.escape('y'), 4)
)
combined = criterion(x: {$lt: 3})
.and(y: {$lte: 4})
test.equal lang.sql(), SQL
test.equal lang.sql(escape), ESCAPED
test.deepEqual lang.params(), PARAMS
test.equal raw.sql(), SQL
test.equal raw.sql(escape), SQL
test.deepEqual raw.params(), PARAMS
test.equal dsl.sql(), SQL
test.equal dsl.sql(escape), ESCAPED
test.deepEqual dsl.params(), PARAMS
test.equal combined.sql(), SQL
test.equal combined.sql(escape), ESCAPED
test.deepEqual combined.params(), PARAMS
test.done()
'> AND >=': (test) ->
SQL = '(x > ?) AND (y >= ?)'
ESCAPED = '("x" > ?) AND ("y" >= ?)'
PARAMS = [5, 6]
lang = criterion {x: {$gt: 5}, y: {$gte: 6}}
raw = criterion SQL, PARAMS...
dsl = C.and(
C.gt(C.escape('x'), 5)
C.gte(C.escape('y'), 6)
)
test.equal lang.sql(), SQL
test.equal lang.sql(escape), ESCAPED
test.deepEqual lang.params(), PARAMS
test.equal raw.sql(), SQL
test.equal raw.sql(escape), SQL
test.deepEqual raw.params(), PARAMS
test.equal dsl.sql(), SQL
test.equal dsl.sql(escape), ESCAPED
test.deepEqual dsl.params(), PARAMS
test.done()
'x IS NULL': (test) ->
SQL = 'x IS NULL'
ESCAPED = '"x" IS NULL'
PARAMS = []
lang = criterion {x: {$null: true}}
raw = criterion SQL
dsl = C.null(C.escape('x'))
test.equal lang.sql(), SQL
test.equal lang.sql(escape), ESCAPED
test.deepEqual lang.params(), PARAMS
test.equal raw.sql(), SQL
test.equal raw.sql(escape), SQL
test.deepEqual raw.params(), PARAMS
test.equal dsl.sql(), SQL
test.equal dsl.sql(escape), ESCAPED
test.deepEqual dsl.params(), PARAMS
test.done()
'x IS NOT NULL': (test) ->
SQL = 'x IS NOT NULL'
ESCAPED = '"x" IS NOT NULL'
PARAMS = []
lang = criterion {x: {$null: false}}
raw = criterion SQL
dsl = C.null(C.escape('x'), false)
test.equal lang.sql(), SQL
test.equal lang.sql(escape), ESCAPED
test.deepEqual lang.params(), PARAMS
test.equal raw.sql(), SQL
test.equal raw.sql(escape), SQL
test.deepEqual raw.params(), PARAMS
test.equal dsl.sql(), SQL
test.equal dsl.sql(escape), ESCAPED
test.deepEqual dsl.params(), PARAMS
test.done()
'x IN (?, ?, ?)': (test) ->
SQL = 'x IN (?, ?, ?)'
ESCAPED = '"x" IN (?, ?, ?)'
PARAMS = [1, 2, 3]
lang = criterion {x: [1, 2, 3]}
langLong = criterion {x: {$in: [1, 2, 3]}}
raw1 = criterion SQL, 1, 2, 3
raw2 = criterion 'x IN (?)', [1, 2, 3]
dsl = C.in(C.escape('x'), [1, 2, 3])
test.equal lang.sql(), SQL
test.equal lang.sql(escape), ESCAPED
test.deepEqual lang.params(), PARAMS
test.equal langLong.sql(), SQL
test.equal langLong.sql(escape), ESCAPED
test.deepEqual langLong.params(), PARAMS
test.equal raw1.sql(), SQL
test.equal raw1.sql(escape), SQL
test.deepEqual raw1.params(), PARAMS
test.equal raw2.sql(), SQL
test.equal raw2.sql(escape), SQL
test.deepEqual raw2.params(), PARAMS
test.equal dsl.sql(), SQL
test.equal dsl.sql(escape), ESCAPED
test.deepEqual dsl.params(), PARAMS
test.done()
'NOT IN': (test) ->
SQL = 'x NOT IN (?, ?, ?)'
ESCAPED = '"x" NOT IN (?, ?, ?)'
PARAMS = [1, 2, 3]
lang = criterion {x: {$nin: [1, 2, 3]}}
raw = criterion SQL, PARAMS...
dsl = C.nin(C.escape('x'), [1, 2, 3])
test.equal lang.sql(), SQL
test.equal lang.sql(escape), ESCAPED
test.deepEqual lang.params(), PARAMS
test.equal raw.sql(), SQL
test.equal raw.sql(escape), SQL
test.deepEqual raw.params(), PARAMS
test.equal dsl.sql(), SQL
test.equal dsl.sql(escape), ESCAPED
test.deepEqual dsl.params(), PARAMS
test.done()
'AND': (test) ->
SQL = '(x = ?) AND (y = ?) AND (z = ?) AND a = ?'
ESCAPED = '("x" = ?) AND ("y" = ?) AND ("z" = ?) AND a = ?'
PARAMS = [7, 'foo', 2.5, 6]
lang1 = criterion {x: 7, y: 'foo'}, {z: 2.5}, [criterion('a = ?', 6)]
lang2 = criterion [{x: 7, y: 'foo'}, {z: 2.5}, criterion('a = ?', 6)]
lang3 = criterion {$and: [{x: 7, y: 'foo'}, {z: 2.5}, criterion('a = ?', 6)]}
lang4 = criterion {x: 7, y: 'foo', $and: [{z: 2.5}, criterion('a = ?', 6)]}
raw = criterion SQL, PARAMS...
dsl1 = C.and(
C.eq(C.escape('x'), 7)
C.eq(C.escape('y'), 'foo')
C.eq(C.escape('z'), 2.5)
C('a = ?', 6)
)
dsl2 = C.and(
C.eq(C.escape('x'), 7)
C.eq(C.escape('y'), 'foo')
C.and(
C.eq(C.escape('z'), 2.5)
C('a = ?', 6)
)
)
dsl3 = C(
C.eq(C.escape('x'), 7)
C.eq(C.escape('y'), 'foo')
C(
C.eq(C.escape('z'), 2.5)
C('a = ?', 6)
)
)
test.equal lang1.sql(), SQL
test.equal lang1.sql(escape), ESCAPED
test.deepEqual lang1.params(), PARAMS
test.equal lang2.sql(), SQL
test.equal lang2.sql(escape), ESCAPED
test.deepEqual lang2.params(), PARAMS
test.equal lang3.sql(), SQL
test.equal lang3.sql(escape), ESCAPED
test.deepEqual lang3.params(), PARAMS
test.equal lang4.sql(), SQL
test.equal lang4.sql(escape), ESCAPED
test.deepEqual lang4.params(), PARAMS
test.equal raw.sql(), SQL
test.equal raw.sql(escape), SQL
test.deepEqual raw.params(), PARAMS
test.equal dsl1.sql(), SQL
test.equal dsl1.sql(escape), ESCAPED
test.deepEqual dsl1.params(), PARAMS
test.equal dsl2.sql(), SQL
test.equal dsl2.sql(escape), ESCAPED
test.deepEqual dsl2.params(), PARAMS
test.equal dsl3.sql(), SQL
test.equal dsl3.sql(escape), ESCAPED
test.deepEqual dsl3.params(), PARAMS
test.done()
'OR': (test) ->
SQL = '(x = ?) OR (y = ?) OR (z = ?) OR a = ?'
ESCAPED = '("x" = ?) OR ("y" = ?) OR ("z" = ?) OR a = ?'
PARAMS = [7, 'foo', 2.5, 6]
lang1 = criterion {$or: [{x: 7}, {y: 'foo'}, {z: 2.5}, criterion('a = ?', 6)]}
lang2 = criterion {$or: {x: 7, y: 'foo', $or: [{z: 2.5}, criterion('a = ?', 6)]}}
raw = criterion SQL, PARAMS...
dsl1 = C.or(
C.eq(C.escape('x'), 7)
C.eq(C.escape('y'), 'foo')
C.eq(C.escape('z'), 2.5)
C('a = ?', 6)
)
dsl2 = C.or(
C.eq(C.escape('x'), 7)
C.eq(C.escape('y'), 'foo')
C.or(
C.eq(C.escape('z'), 2.5)
C('a = ?', 6)
)
)
test.equal lang1.sql(), SQL
test.equal lang1.sql(escape), ESCAPED
test.deepEqual lang1.params(), PARAMS
test.equal lang2.sql(), SQL
test.equal lang2.sql(escape), ESCAPED
test.deepEqual lang2.params(), PARAMS
test.equal raw.sql(), SQL
test.equal raw.sql(escape), SQL
test.deepEqual raw.params(), PARAMS
test.equal dsl1.sql(), SQL
test.equal dsl1.sql(escape), ESCAPED
test.deepEqual dsl1.params(), PARAMS
test.equal dsl2.sql(), SQL
test.equal dsl2.sql(escape), ESCAPED
test.deepEqual dsl2.params(), PARAMS
test.done()
'NOT': (test) ->
SQL = 'NOT ((x > ?) AND (y >= ?))'
ESCAPED = 'NOT (("x" > ?) AND ("y" >= ?))'
PARAMS = [3, 4]
lang = criterion {$not: {x: {$gt: 3}, y: {$gte: 4}}}
raw = criterion SQL, PARAMS...
dsl = C.not(
C.and(
C.gt(C.escape('x'), 3)
C.gte(C.escape('y'), 4)
)
)
test.equal lang.sql(), SQL
test.equal lang.sql(escape), ESCAPED
test.deepEqual lang.params(), PARAMS
test.equal raw.sql(), SQL
test.equal raw.sql(escape), SQL
test.deepEqual raw.params(), PARAMS
test.equal dsl.sql(), SQL
test.equal dsl.sql(escape), ESCAPED
test.deepEqual dsl.params(), PARAMS
test.done()
'OR inside AND (is wrapped in parentheses)': (test) ->
SQL = '(username = ?) AND (password = ?) AND ((active = ?) OR (active IS NULL))'
ESCAPED = '("username" = ?) AND ("password" = ?) AND (("active" = ?) OR ("active" IS NULL))'
PARAMS = ['user', 'hash', 1]
lang = criterion
username: 'user'
password: 'hash'
$or: [{active: 1}, {active: {$null: true}}]
raw = criterion SQL, PARAMS...
dsl = C.and(
C.eq(C.escape('username'), 'user')
C.eq(C.escape('password'), 'hash')
C.or(
C.eq(C.escape('active'), 1)
C.null(C.escape('active'))
)
)
test.equal lang.sql(), SQL
test.equal lang.sql(escape), ESCAPED
test.deepEqual lang.params(), PARAMS
test.equal raw.sql(), SQL
test.equal raw.sql(escape), SQL
test.deepEqual raw.params(), PARAMS
test.equal dsl.sql(), SQL
test.equal dsl.sql(escape), ESCAPED
test.deepEqual dsl.params(), PARAMS
test.done()
'AND, OR and NOT can be deeply nested': (test) ->
SQL = '(alpha = ?) OR ((charlie != ?) AND (NOT (((delta = ?) OR (delta IS NULL)) AND (echo = ?)))) OR ((echo < ?) AND ((golf = ?) OR (NOT (lima != ?)))) OR (foxtrot = ?) OR (NOT (alpha = ? OR (NOT (echo < ?)) OR (alpha < ?) OR (bravo = ?) OR ((alpha = ?) AND (bravo = ?)))) OR (bravo = ?)'
ESCAPED = '("alpha" = ?) OR (("charlie" != ?) AND (NOT ((("delta" = ?) OR ("delta" IS NULL)) AND ("echo" = ?)))) OR (("echo" < ?) AND (("golf" = ?) OR (NOT ("lima" != ?)))) OR ("foxtrot" = ?) OR (NOT (alpha = ? OR (NOT ("echo" < ?)) OR ("alpha" < ?) OR ("bravo" = ?) OR (("alpha" = ?) AND ("bravo" = ?)))) OR ("bravo" = ?)'
PARAMS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
lang = criterion {
$or: {
alpha: 1
$and: {
charlie: {$ne: 2}
$not: {
$and: {
$or: [
{delta: 3},
{delta: {$null: true}}
]
echo: 4
}
}
}
$or: [
[
{echo: {$lt: 5}}
{$or: {
golf: 6
$not: {lima: {$ne: 7}}
}}
]
{foxtrot: 8}
]
$not: {
$or: [
criterion('alpha = ?', 9)
{$not: {echo: {$lt: 10}}}
{alpha: {$lt: 11}}
{bravo: 12}
[
{alpha: 13}
{bravo: 14}
]
]
}
bravo: 15
}
}
raw = criterion SQL, PARAMS...
dsl = C.or(
C.eq(C.escape('alpha'), 1)
C.and(
C.ne(C.escape('charlie'), 2)
C.not(
C.and(
C.or(
C.eq(C.escape('delta'), 3)
C.null(C.escape('delta'))
)
C.eq(C.escape('echo'), 4)
)
)
)
C.or(
C.and(
C.lt(C.escape('echo'), 5)
C.or(
C.eq(C.escape('golf'), 6)
C.not(
C.ne(C.escape('lima'), 7)
)
)
)
C.eq(C.escape('foxtrot'), 8)
)
C.not(
C.or(
C('alpha = ?', 9)
C.not(
C.lt(C.escape('echo'), 10)
)
C.lt(C.escape('alpha'), 11)
C.eq(C.escape('bravo'), 12)
C(
C.eq(C.escape('alpha'), 13)
C.eq(C.escape('bravo'), 14)
)
)
)
C.eq(C.escape('bravo'), 15)
)
test.equal lang.sql(), SQL
test.equal lang.sql(escape), ESCAPED
test.deepEqual lang.params(), PARAMS
test.equal raw.sql(), SQL
test.equal raw.sql(escape), SQL
test.deepEqual raw.params(), PARAMS
test.equal dsl.sql(), SQL
test.equal dsl.sql(escape), ESCAPED
test.deepEqual dsl.params(), PARAMS
test.done()
'IN and $nin': (test) ->
subquery =
sql: (escape) ->
"SELECT #{escape 'id'} FROM \"user\" WHERE #{escape 'is_active'}"
params: -> []
subqueryWithParams =
sql: (escape) ->
"SELECT #{escape 'id'} FROM \"user\" WHERE #{escape 'is_active'} = ?"
params: ->
[true]
inWithoutParams = criterion {x: {$in: subquery}}
test.equal inWithoutParams.sql(), 'x IN (SELECT id FROM "user" WHERE is_active)'
test.equal inWithoutParams.sql(escape), '"x" IN (SELECT "id" FROM "user" WHERE "is_active")'
test.deepEqual inWithoutParams.params(), []
inWithParams = criterion {x: {$in: subqueryWithParams}}
test.equal inWithParams.sql(), 'x IN (SELECT id FROM "user" WHERE is_active = ?)'
test.equal inWithParams.sql(escape), '"x" IN (SELECT "id" FROM "user" WHERE "is_active" = ?)'
test.deepEqual inWithParams.params(), [true]
ninWithoutParams = criterion {x: {$nin: subquery}}
test.equal ninWithoutParams.sql(), 'x NOT IN (SELECT id FROM "user" WHERE is_active)'
test.equal ninWithoutParams.sql(escape), '"x" NOT IN (SELECT "id" FROM "user" WHERE "is_active")'
test.deepEqual ninWithoutParams.params(), []
ninWithParams = criterion {x: {$nin: subqueryWithParams}}
test.equal ninWithParams.sql(), 'x NOT IN (SELECT id FROM "user" WHERE is_active = ?)'
test.equal ninWithParams.sql(escape), '"x" NOT IN (SELECT "id" FROM "user" WHERE "is_active" = ?)'
test.deepEqual ninWithParams.params(), [true]
test.done()
'$exists': (test) ->
subquery =
sql: (escape) ->
"SELECT * FROM \"user\" WHERE #{escape 'is_active'}"
params: -> []
subqueryWithParams =
sql: (escape) ->
"SELECT * FROM \"user\" WHERE #{escape 'is_active'} = ?"
params: ->
[true]
existsWithoutParams = criterion {id: 7, $exists: subquery}
test.equal existsWithoutParams.sql(), '(id = ?) AND (EXISTS (SELECT * FROM "user" WHERE is_active))'
test.equal existsWithoutParams.sql(escape), '("id" = ?) AND (EXISTS (SELECT * FROM "user" WHERE "is_active"))'
test.deepEqual existsWithoutParams.params(), [7]
existsWithParams = criterion {id: 7, $exists: subqueryWithParams}
test.equal existsWithParams.sql(), '(id = ?) AND (EXISTS (SELECT * FROM "user" WHERE is_active = ?))'
test.equal existsWithParams.sql(escape), '("id" = ?) AND (EXISTS (SELECT * FROM "user" WHERE "is_active" = ?))'
test.deepEqual existsWithParams.params(), [7, true]
test.done()
'$any, $neAny, $ltAny, $lteAny, $gtAny, $gteAny, $all, $neAll, $ltAll, $lteAll, $gtAll, $gteAll': (test) ->
subquery =
sql: (escape) ->
"SELECT * FROM #{escape "user"}"
params: -> []
subqueryWithParams =
sql: (escape) ->
"SELECT * FROM #{escape "user"} WHERE #{escape "id"} = ?"
params: ->
[7]
anyWithoutParams = criterion {x: {$any: subquery}}
test.equal anyWithoutParams.sql(), 'x = ANY (SELECT * FROM user)'
test.equal anyWithoutParams.sql(escape), '"x" = ANY (SELECT * FROM "user")'
test.deepEqual anyWithoutParams.params(), []
anyWithParams = criterion {x: {$any: subqueryWithParams}, y: 6}
test.equal anyWithParams.sql(), '(x = ANY (SELECT * FROM user WHERE id = ?)) AND (y = ?)'
test.equal anyWithParams.sql(escape), '("x" = ANY (SELECT * FROM "user" WHERE "id" = ?)) AND ("y" = ?)'
test.deepEqual anyWithParams.params(), [7, 6]
# since all other subqueries follow the same code path
# we omit testing with params and escaping for them
test.equal criterion({x: {$neAny: subquery}}).sql(), 'x != ANY (SELECT * FROM user)'
test.equal criterion({x: {$ltAny: subquery}}).sql(), 'x < ANY (SELECT * FROM user)'
test.equal criterion({x: {$lteAny: subquery}}).sql(), 'x <= ANY (SELECT * FROM user)'
test.equal criterion({x: {$gtAny: subquery}}).sql(), 'x > ANY (SELECT * FROM user)'
test.equal criterion({x: {$gteAny: subquery}}).sql(), 'x >= ANY (SELECT * FROM user)'
test.equal criterion({x: {$all: subquery}}).sql(), 'x = ALL (SELECT * FROM user)'
test.equal criterion({x: {$neAll: subquery}}).sql(), 'x != ALL (SELECT * FROM user)'
test.equal criterion({x: {$ltAll: subquery}}).sql(), 'x < ALL (SELECT * FROM user)'
test.equal criterion({x: {$lteAll: subquery}}).sql(), 'x <= ALL (SELECT * FROM user)'
test.equal criterion({x: {$gtAll: subquery}}).sql(), 'x > ALL (SELECT * FROM user)'
test.equal criterion({x: {$gteAll: subquery}}).sql(), 'x >= ALL (SELECT * FROM user)'
test.done()
'row-wise comparison': (test) ->
subquery =
sql: (escape) ->
"SELECT #{escape 'created_at'} FROM #{escape 'message'} WHERE #{escape 'id'} = ?"
params: ->
[1]
c = criterion {is_active: true, created_at: {$lte: subquery}}
test.equal c.sql(), '(is_active = ?) AND (created_at <= (SELECT created_at FROM message WHERE id = ?))'
test.equal c.sql(escape), '("is_active" = ?) AND ("created_at" <= (SELECT "created_at" FROM "message" WHERE "id" = ?))'
test.deepEqual c.params(), [true, 1]
test.done()
'with one param and one array': (test) ->
c = criterion 'x = ? AND y IN (?)', 7, [8,9,10]
test.equal c.sql(), 'x = ? AND y IN (?, ?, ?)'
test.deepEqual c.params(), [7, 8, 9, 10]
test.done()
'with two params and array': (test) ->
c = criterion 'x = ? AND y = ? AND z IN (?)', 7, 8, [9,10,11]
test.equal c.sql(), 'x = ? AND y = ? AND z IN (?, ?, ?)'
test.deepEqual c.params(), [7, 8, 9, 10, 11]
test.done()
'with two params and two arrays': (test) ->
c = criterion 'x = ? AND y = ? AND z IN (?) AND (a && ARRAY[?])', 7, 8, [9,10,11], [12,13,14]
test.equal c.sql(), 'x = ? AND y = ? AND z IN (?, ?, ?) AND (a && ARRAY[?, ?, ?])'
test.deepEqual c.params(), [7, 8, 9, 10, 11, 12, 13, 14]
test.done()
'equality with criterion argument': (test) ->
c = criterion {x: criterion('crypt(?, gen_salt(?, ?))', 'password', 'bf', 4)}
test.equal c.sql(), 'x = crypt(?, gen_salt(?, ?))'
test.equal c.sql(escape), '"x" = crypt(?, gen_salt(?, ?))'
test.deepEqual c.params(), ['password', 'bf', 4]
test.done()
'$ne with criterion argument': (test) ->
c = criterion {x: {$ne: criterion('crypt(?, gen_salt(?, ?))', 'password', 'bf', 4)}}
test.equal c.sql(), 'x != crypt(?, gen_salt(?, ?))'
test.equal c.sql(escape), '"x" != crypt(?, gen_salt(?, ?))'
test.deepEqual c.params(), ['password', 'bf', 4]
test.done()
'$lt with criterion argument': (test) ->
c = criterion {x: {$lt: criterion('NOW()')}}
test.equal c.sql(), 'x < NOW()'
test.equal c.sql(escape), '"x" < NOW()'
test.deepEqual c.params(), []
test.done()
'successfully manipulating criteria':
'and': (test) ->
fst = criterion {x: 7, y: 'foo'}
snd = criterion 'z = ?', true
fstAndSnd = fst.and snd
test.equal fstAndSnd.sql(), '(x = ?) AND (y = ?) AND z = ?'
test.deepEqual fstAndSnd.params(), [7, 'foo', true]
test.done()
'or': (test) ->
fst = criterion {x: 7, y: 'foo'}
snd = criterion 'z = ?', true
sndOrFst = snd.or fst
test.equal sndOrFst.sql(), 'z = ? OR ((x = ?) AND (y = ?))'
test.deepEqual sndOrFst.params(), [true, 7, 'foo']
test.done()
'not': (test) ->
c = criterion {x: 7, y: 'foo'}
test.equal c.not().sql(), 'NOT ((x = ?) AND (y = ?))'
test.deepEqual c.not().params(), [7, 'foo']
test.done()
'double negation is removed': (test) ->
c = criterion {x: 7, y: 'foo'}
test.equal c.not().not().sql(), '(x = ?) AND (y = ?)'
test.deepEqual c.not().not().params(), [7, 'foo']
test.equal c.not().not().not().sql(), 'NOT ((x = ?) AND (y = ?))'
test.deepEqual c.not().not().not().params(), [7, 'foo']
test.done()
'errors when making criteria':
'not string or object': (test) ->
try
criterion 6
catch e
test.equal e.message, 'string or object expected as first argument but number given'
test.done()
'empty object': (test) ->
try
criterion {}
catch e
test.equal e.message, 'empty condition-object'
try
criterion [{}]
catch e
test.equal e.message, 'empty condition-object'
test.done()
'empty array param': (test) ->
try
criterion 'b < ? AND a IN(?) AND c < ?', 6, [], 7
catch e
test.equal e.message, 'params[1] is an empty array'
test.done()
'null value with modifier': (test) ->
try
criterion {x: {$lt: null}}
catch e
test.equal e.message, 'value undefined or null for key x and modifier key $lt'
test.done()
'null value without modifier': (test) ->
try
criterion {x: null}
catch e
test.equal e.message, 'value undefined or null for key x'
test.done()
'in with empty array': (test) ->
try
criterion {x: []}
catch e
test.equal e.message, '`in` with empty array as right operand'
test.done()
'$nin with empty array': (test) ->
try
criterion {x: {$nin: []}}
catch e
test.equal e.message, '`nin` with empty array as right operand'
test.done()
'$any with array': (test) ->
try
criterion {x: {$any: [1]}}
catch e
test.equal e.message, "`any` doesn't support array as right operand. only `in` and `nin` do!"
test.done()
'$any with number': (test) ->
try
criterion {x: {$any: 6}}
catch e
test.equal e.message, "`any` requires right operand that implements sql-fragment interface"
test.done()
'unknown modifier': (test) ->
try
criterion {x: {$not: 6}}
catch e
test.equal e.message, 'unknown modifier key $not'
try
criterion {x: {$foo: 6}}
catch e
test.equal e.message, 'unknown modifier key $foo'
test.done()
'$exists without sql-fragment': (test) ->
try
criterion {$exists: 6}
catch e
test.equal e.message, "`exists` operand must implement sql-fragment interface"
test.done()
'$in without array or sql-fragment': (test) ->
try
criterion({x: {$in: 6}})
catch e
test.equal e.message, "`in` requires right operand that is an array or implements sql-fragment interface"
test.done()
'$nin without array or sql-fragment': (test) ->
try
criterion({x: {$nin: 6}})
catch e
test.equal e.message, "`nin` requires right operand that is an array or implements sql-fragment interface"
test.done()