UNPKG

igo

Version:

Igo is a Node.js Web Framework based on Express

514 lines (446 loc) 20.7 kB
var assert = require('assert'); var Sql = require('../../src/db/Sql'); const { dialect } = require('../../src/db/drivers/mysql'); describe('db.Sql', function() { // Fresh query for each test to avoid state leakage const freshQuery = (overrides = {}) => ({ table: 'books', where: [], whereNot: [], joins: [], order: [], ...overrides }); // describe('selectSQL', function() { it('should return correct SQL', function() { var selectSQL = new Sql(freshQuery(), dialect).selectSQL(); assert.strictEqual('SELECT `books`.* FROM `books`', selectSQL.sql); assert.strictEqual(0, selectSQL.params.length); }); it('should allow order by', function() { var selectSQL = new Sql(freshQuery({ order: ['`title`'] }), dialect).selectSQL(); assert.strictEqual('SELECT `books`.* FROM `books` ORDER BY `title`', selectSQL.sql); assert.strictEqual(0, selectSQL.params.length); }); it('should allow limit', function() { var selectSQL = new Sql(freshQuery({ limit: 3 }), dialect).selectSQL(); assert.strictEqual('SELECT `books`.* FROM `books` LIMIT ?, ?', selectSQL.sql); assert.strictEqual(2, selectSQL.params.length); assert.strictEqual(0, selectSQL.params[0]); assert.strictEqual(3, selectSQL.params[1]); }); it('should allow distinct', function() { var selectSQL = new Sql(freshQuery({ distinct: ['type'] }), dialect).selectSQL(); assert.strictEqual('SELECT DISTINCT `type` FROM `books`', selectSQL.sql); assert.strictEqual(0, selectSQL.params.length); }); }); // describe('countSQL', function() { it('should return correct SQL', function() { var selectSQL = new Sql(freshQuery(), dialect).countSQL(); assert.strictEqual('SELECT COUNT(0) as `count` FROM `books`', selectSQL.sql); }); it('should return correct SQL with where', function() { var query = freshQuery({ where: [{ status: 'active' }] }); var countSQL = new Sql(query, dialect).countSQL(); assert.strictEqual('SELECT COUNT(0) as `count` FROM `books` WHERE `books`.`status` = ?', countSQL.sql); assert.deepStrictEqual(['active'], countSQL.params); }); }); // describe('whereSQL', function() { // Array branch: [sql, scalarParam] it('should allow string as query param', function() { var params = []; var query = freshQuery({ where: [[ 'field like ?', '%soon%' ]] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE field like ? ', sql); assert.strictEqual('%soon%', params[0]); }); it('should allow integer as query param', function() { var params = []; var query = freshQuery({ where: [[ 'id=?', 12 ]] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE id=? ', sql); assert.strictEqual(12, params[0]); }); // Array branch: [sql, arrayParam] it('should allow array as query param', function() { var params = []; var query = freshQuery({ where: [[ 'field like ?', ['%soon%'] ]] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE field like ? ', sql); assert.strictEqual('%soon%', params[0]); }); // Array branch: $? placeholder substitution it('should substitute $? placeholders with dialect params', function() { var params = []; var query = freshQuery({ where: [[ '`books`.`id` = $? AND `books`.`status` = $?', [42, 'active'] ]] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE `books`.`id` = ? AND `books`.`status` = ? ', sql); assert.strictEqual(42, params[0]); assert.strictEqual('active', params[1]); }); // String branch it('should allow raw string as where clause', function() { var params = []; var query = freshQuery({ where: ['`books`.`id` > 10'] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE `books`.`id` > 10 ', sql); assert.strictEqual(0, params.length); }); // Object branch: scalar equality it('should allow object as criterion', function() { var params = []; var query = freshQuery({ where: [{ id: 123 }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE `books`.`id` = ? ', sql); assert.strictEqual(123, params[0]); }); // Object branch: null → IS NULL it('should generate IS NULL for null value', function() { var params = []; var query = freshQuery({ where: [{ deleted_at: null }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE `books`.`deleted_at` IS NULL ', sql); assert.strictEqual(0, params.length); }); // Object branch: undefined → IS NULL it('should generate IS NULL for undefined value', function() { var params = []; var query = freshQuery({ where: [{ deleted_at: undefined }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE `books`.`deleted_at` IS NULL ', sql); assert.strictEqual(0, params.length); }); // Object branch: empty array → FALSE it('should generate FALSE for empty array value', function() { var params = []; var query = freshQuery({ where: [{ id: [] }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE FALSE ', sql); assert.strictEqual(0, params.length); }); // Object branch: non-empty array → IN it('should generate IN for array value', function() { var params = []; var query = freshQuery({ where: [{ id: [1, 2, 3] }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE `books`.`id` IN (?) ', sql); assert.deepStrictEqual([1, 2, 3], params[0]); }); // Object branch: dot-qualified key it('should handle dot-qualified column names', function() { var params = []; var query = freshQuery({ where: [{ 'library.title': 'foo' }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE `library`.`title` = ? ', sql); assert.strictEqual('foo', params[0]); }); // Multiple conditions (implicit AND) it('should join multiple conditions with AND', function() { var params = []; var query = freshQuery({ where: [{ status: 'active' }, { type: 'novel' }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE `books`.`status` = ? AND `books`.`type` = ? ', sql); assert.strictEqual('active', params[0]); assert.strictEqual('novel', params[1]); }); // Multiple keys in same object (implicit AND) it('should AND multiple keys in same object', function() { var params = []; var query = freshQuery({ where: [{ status: 'active', type: 'novel' }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE `books`.`status` = ? AND `books`.`type` = ? ', sql); assert.strictEqual('active', params[0]); assert.strictEqual('novel', params[1]); }); // Empty where → empty string it('should return empty string for empty where', function() { var params = []; var query = freshQuery(); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('', sql); assert.strictEqual(0, params.length); }); }); // describe('whereNotSQL', function() { // IS NOT NULL it('should generate IS NOT NULL for null value', function() { var params = []; var query = freshQuery({ where: [], whereNot: [{ deleted_at: null }] }); var sql = new Sql(query, dialect).whereNotSQL(params); assert.strictEqual('WHERE `books`.`deleted_at` IS NOT NULL ', sql); assert.strictEqual(0, params.length); }); // != for scalar it('should generate != for scalar value', function() { var params = []; var query = freshQuery({ where: [], whereNot: [{ status: 'deleted' }] }); var sql = new Sql(query, dialect).whereNotSQL(params); assert.strictEqual('WHERE `books`.`status` != ? ', sql); assert.strictEqual('deleted', params[0]); }); // NOT IN for array it('should generate NOT IN for array value', function() { var params = []; var query = freshQuery({ where: [], whereNot: [{ id: [1, 2, 3] }] }); var sql = new Sql(query, dialect).whereNotSQL(params); assert.strictEqual('WHERE `books`.`id` NOT IN (?) ', sql); assert.deepStrictEqual([1, 2, 3], params[0]); }); // TRUE for empty array it('should generate TRUE for empty array value', function() { var params = []; var query = freshQuery({ where: [], whereNot: [{ id: [] }] }); var sql = new Sql(query, dialect).whereNotSQL(params); assert.strictEqual('WHERE TRUE ', sql); assert.strictEqual(0, params.length); }); // AND prefix when where is already populated it('should use AND prefix when where clauses exist', function() { var params = []; var query = freshQuery({ where: [{ status: 'active' }], whereNot: [{ type: 'draft' }] }); // First generate the where SQL var whereSql = new Sql(query, dialect).whereSQL(params); // Then generate whereNot - needs a fresh Sql instance var params2 = []; var whereNotSql = new Sql(query, dialect).whereNotSQL(params2); assert.strictEqual('AND `books`.`type` != ? ', whereNotSql); assert.strictEqual('draft', params2[0]); }); // WHERE prefix when no where clauses it('should use WHERE prefix when no where clauses exist', function() { var params = []; var query = freshQuery({ where: [], whereNot: [{ type: 'draft' }] }); var sql = new Sql(query, dialect).whereNotSQL(params); assert.strictEqual('WHERE `books`.`type` != ? ', sql); }); // Operators in whereNot it('should invert $gte to < in whereNot', function() { var params = []; var query = freshQuery({ where: [], whereNot: [{ price: { $gte: 100 } }] }); var sql = new Sql(query, dialect).whereNotSQL(params); assert.strictEqual('WHERE `books`.`price` < ? ', sql); assert.deepStrictEqual([100], params); }); it('should use NOT LIKE in whereNot for $like', function() { var params = []; var query = freshQuery({ where: [], whereNot: [{ title: { $like: 'Draft%' } }] }); var sql = new Sql(query, dialect).whereNotSQL(params); assert.strictEqual('WHERE `books`.`title` NOT LIKE ? ', sql); assert.deepStrictEqual(['Draft%'], params); }); it('should invert $lte to > in whereNot', function() { var params = []; var query = freshQuery({ where: [], whereNot: [{ price: { $lte: 100 } }] }); var sql = new Sql(query, dialect).whereNotSQL(params); assert.strictEqual('WHERE `books`.`price` > ? ', sql); assert.deepStrictEqual([100], params); }); it('should invert $gt to <= in whereNot', function() { var params = []; var query = freshQuery({ where: [], whereNot: [{ price: { $gt: 5 } }] }); var sql = new Sql(query, dialect).whereNotSQL(params); assert.strictEqual('WHERE `books`.`price` <= ? ', sql); assert.deepStrictEqual([5], params); }); it('should invert $lt to >= in whereNot', function() { var params = []; var query = freshQuery({ where: [], whereNot: [{ price: { $lt: 50 } }] }); var sql = new Sql(query, dialect).whereNotSQL(params); assert.strictEqual('WHERE `books`.`price` >= ? ', sql); assert.deepStrictEqual([50], params); }); it('should use NOT BETWEEN in whereNot for $between', function() { var params = []; var query = freshQuery({ where: [], whereNot: [{ price: { $between: [10, 50] } }] }); var sql = new Sql(query, dialect).whereNotSQL(params); assert.strictEqual('WHERE `books`.`price` NOT BETWEEN ? AND ? ', sql); assert.deepStrictEqual([10, 50], params); }); }); // describe('updateSQL', function() { it('should generate UPDATE with WHERE', function() { var query = freshQuery({ table: 'books', verb: 'update', values: { title: 'New Title', status: 'published' }, where: [{ id: 42 }], }); var result = new Sql(query, dialect).updateSQL(); assert.strictEqual('UPDATE `books` SET `title` = ?, `status` = ? WHERE `books`.`id` = ? ', result.sql); assert.deepStrictEqual(['New Title', 'published', 42], result.params); }); it('should generate UPDATE without WHERE', function() { var query = freshQuery({ table: 'books', verb: 'update', values: { status: 'archived' }, }); var result = new Sql(query, dialect).updateSQL(); assert.strictEqual('UPDATE `books` SET `status` = ?', result.sql.trim()); assert.deepStrictEqual(['archived'], result.params); }); }); // describe('deleteSQL', function() { it('should generate DELETE with WHERE', function() { var query = freshQuery({ table: 'books', verb: 'delete', where: [{ id: 42 }], }); var result = new Sql(query, dialect).deleteSQL(); assert.strictEqual('DELETE FROM `books` WHERE `books`.`id` = ? ', result.sql); assert.deepStrictEqual([42], result.params); }); it('should generate DELETE without WHERE', function() { var query = freshQuery({ table: 'books', verb: 'delete', }); var result = new Sql(query, dialect).deleteSQL(); assert.strictEqual('DELETE FROM `books`', result.sql.trim()); assert.deepStrictEqual([], result.params); }); }); // describe('insertSQL', function() { it('should generate INSERT', function() { var query = freshQuery({ table: 'books', verb: 'insert', values: { title: 'My Book', status: 'draft' }, }); var result = new Sql(query, dialect).insertSQL(); assert.strictEqual('INSERT INTO `books`(`title`,`status`) VALUES(?,?) ', result.sql); assert.deepStrictEqual(['My Book', 'draft'], result.params); }); }); // describe('whereSQL operators', function() { it('should support $like operator', function() { var params = []; var query = freshQuery({ where: [{ title: { $like: 'Node%' } }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE `books`.`title` LIKE ? ', sql); assert.deepStrictEqual(['Node%'], params); }); it('should support $gte operator', function() { var params = []; var query = freshQuery({ where: [{ price: { $gte: 10 } }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE `books`.`price` >= ? ', sql); assert.deepStrictEqual([10], params); }); it('should support $lte operator', function() { var params = []; var query = freshQuery({ where: [{ price: { $lte: 100 } }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE `books`.`price` <= ? ', sql); assert.deepStrictEqual([100], params); }); it('should support $gt operator', function() { var params = []; var query = freshQuery({ where: [{ price: { $gt: 5 } }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE `books`.`price` > ? ', sql); assert.deepStrictEqual([5], params); }); it('should support $lt operator', function() { var params = []; var query = freshQuery({ where: [{ price: { $lt: 50 } }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE `books`.`price` < ? ', sql); assert.deepStrictEqual([50], params); }); it('should support multiple operators on same column', function() { var params = []; var query = freshQuery({ where: [{ price: { $gte: 100, $lte: 500 } }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE (`books`.`price` >= ? AND `books`.`price` <= ?) ', sql); assert.deepStrictEqual([100, 500], params); }); it('should support $between operator', function() { var params = []; var query = freshQuery({ where: [{ created_at: { $between: ['2024-01-01', '2024-12-31'] } }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE `books`.`created_at` BETWEEN ? AND ? ', sql); assert.deepStrictEqual(['2024-01-01', '2024-12-31'], params); }); it('should support $or operator', function() { var params = []; var query = freshQuery({ where: [{ $or: [{ status: 'active' }, { status: 'pending' }] }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE (`books`.`status` = ? OR `books`.`status` = ?) ', sql); assert.deepStrictEqual(['active', 'pending'], params); }); it('should support $and operator', function() { var params = []; var query = freshQuery({ where: [{ $and: [{ status: 'active' }, { price: { $gte: 10 } }] }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE (`books`.`status` = ? AND `books`.`price` >= ?) ', sql); assert.deepStrictEqual(['active', 10], params); }); it('should support $or with different columns', function() { var params = []; var query = freshQuery({ where: [{ $or: [{ title: { $like: 'Node%' } }, { status: 'featured' }] }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE (`books`.`title` LIKE ? OR `books`.`status` = ?) ', sql); assert.deepStrictEqual(['Node%', 'featured'], params); }); it('should support mixed operators and plain values', function() { var params = []; var query = freshQuery({ where: [{ status: 'active', price: { $gte: 10 }, title: { $like: '%JS%' } }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE `books`.`status` = ? AND `books`.`price` >= ? AND `books`.`title` LIKE ? ', sql); assert.deepStrictEqual(['active', 10, '%JS%'], params); }); it('should support $and with siblings', function() { var params = []; var query = freshQuery({ where: [{ $and: [{ price: { $gte: 10 } }], status: 'active' }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE (`books`.`price` >= ? AND `books`.`status` = ?) ', sql); assert.deepStrictEqual([10, 'active'], params); }); it('should support $or combined with other conditions', function() { var params = []; var query = freshQuery({ where: [{ $or: [{ status: 'active' }, { status: 'pending' }], type: 'novel' }] }); var sql = new Sql(query, dialect).whereSQL(params); assert.strictEqual('WHERE (`books`.`status` = ? OR `books`.`status` = ?) AND `books`.`type` = ? ', sql); assert.deepStrictEqual(['active', 'pending', 'novel'], params); }); it('should support operators in UPDATE WHERE clause', function() { var query = freshQuery({ verb: 'update', values: { status: 'archived' }, where: [{ created_at: { $lt: '2023-01-01' } }], }); var result = new Sql(query, dialect).updateSQL(); assert.strictEqual('UPDATE `books` SET `status` = ? WHERE `books`.`created_at` < ? ', result.sql); assert.deepStrictEqual(['archived', '2023-01-01'], result.params); }); it('should support operators in DELETE WHERE clause', function() { var query = freshQuery({ verb: 'delete', where: [{ price: { $lt: 5 }, status: 'draft' }], }); var result = new Sql(query, dialect).deleteSQL(); assert.strictEqual('DELETE FROM `books` WHERE `books`.`price` < ? AND `books`.`status` = ? ', result.sql); assert.deepStrictEqual([5, 'draft'], result.params); }); it('should support operators in COUNT WHERE clause', function() { var query = freshQuery({ where: [{ price: { $between: [10, 50] } }], }); var result = new Sql(query, dialect).countSQL(); assert.strictEqual('SELECT COUNT(0) as `count` FROM `books` WHERE `books`.`price` BETWEEN ? AND ?', result.sql); assert.deepStrictEqual([10, 50], result.params); }); }); });