UNPKG

sharp-db

Version:

Classes for running SQL and building select queries for MySQL in Node

902 lines (899 loc) 28.6 kB
jest.mock('mysql2'); const mysqlMock = require('mysql2'); const Select = require('../Select/Select.js'); const normalizeWhitespace = s => s.replace(/\s+/g, ' ').trim(); describe('Select', function () { describe('class', function () { it('should be instantiable', function () { const query = new Select(); expect(query).toBeInstanceOf(Select); }); it('should allow init', function () { const query = Select.init(); expect(query).toBeInstanceOf(Select); }); }); describe('where() with arguments', function () { it('should handle expressions', function () { const query = new Select(); query.where('mycol = LOWER(mycol2)'); expect(query._wheres[0]).toBe('mycol = LOWER(mycol2)'); }); it('should handle equals', function () { const query = new Select(); query.where('mycol', 'myval'); expect(query._wheres[0]).toBe("`mycol` = 'myval'"); }); it('should handle automatic IN', function () { const query = new Select(); query.where('mycol', [1, 2]); expect(query._wheres[0]).toBe('`mycol` IN(1,2)'); }); it('should handle explicit IN', function () { const query = new Select(); query.where('mycol', 'IN', [1, 2]); expect(query._wheres[0]).toBe('`mycol` IN(1,2)'); }); it('should handle explicit IN (not an array)', function () { const query = new Select(); query.where('mycol', 'IN', 123); expect(query._wheres[0]).toBe('`mycol` IN(123)'); }); it('should handle automatic NOT IN', function () { const query = new Select(); query.where('mycol', '!=', [1, 2]); expect(query._wheres[0]).toBe('`mycol` NOT IN(1,2)'); }); it('should handle explicit NOT IN', function () { const query = new Select(); query.where('mycol', 'NOT IN', [1, 2]); expect(query._wheres[0]).toBe('`mycol` NOT IN(1,2)'); }); it('should handle BETWEEN', function () { const query = new Select(); query.where('mycol', 'BETWEEN', [1, 2]); expect(query._wheres[0]).toBe('`mycol` BETWEEN 1 AND 2'); }); it('should handle operators', function () { const query = new Select(); query.where('mycol', '>', 3); expect(query._wheres[0]).toBe('`mycol` > 3'); }); it('should handle NULL', function () { const query = new Select(); query.where('mycol', null); expect(query._wheres[0]).toBe('`mycol` IS NULL'); }); it('should handle NOT NULL', function () { const query = new Select(); query.where('mycol', '!', null); expect(query._wheres[0]).toBe('`mycol` IS NOT NULL'); }); it('should handle LIKE with single item', function () { const query = new Select(); query.where('mycol', 'LIKE', 'foo'); expect(query._wheres[0]).toBe("`mycol` LIKE 'foo'"); }); it('should handle LIKE with array', function () { const query = new Select(); query.where('mycol', 'LIKE', ['foo', 'bar']); expect(query._wheres[0]).toBe( "(`mycol` LIKE 'foo' OR `mycol` LIKE 'bar')" ); }); it('should handle LIKE ? with single item', function () { const query = new Select(); query.where('mycol', 'LIKE ?', 'foo'); expect(query._wheres[0]).toBe("`mycol` LIKE 'foo'"); }); it('should handle LIKE ? with array', function () { const query = new Select(); query.where('mycol', 'LIKE ?', ['foo', 'bar']); expect(query._wheres[0]).toBe( "(`mycol` LIKE 'foo' OR `mycol` LIKE 'bar')" ); }); it('should handle LIKE %? with single item', function () { const query = new Select(); query.where('mycol', 'LIKE %?', 'foo'); expect(query._wheres[0]).toBe("`mycol` LIKE '%foo'"); }); it('should handle LIKE %? with array', function () { const query = new Select(); query.where('mycol', 'LIKE %?', ['foo', 'bar']); expect(query._wheres[0]).toBe( "(`mycol` LIKE '%foo' OR `mycol` LIKE '%bar')" ); }); it('should handle LIKE ?% with single item', function () { const query = new Select(); query.where('mycol', 'LIKE ?%', 'foo'); expect(query._wheres[0]).toBe("`mycol` LIKE 'foo%'"); }); it('should handle LIKE ?% with array', function () { const query = new Select(); query.where('mycol', 'LIKE ?%', ['foo', 'bar']); expect(query._wheres[0]).toBe( "(`mycol` LIKE 'foo%' OR `mycol` LIKE 'bar%')" ); }); it('should handle LIKE %?% with single item', function () { const query = new Select(); query.where('mycol', 'LIKE %?%', 'foo'); expect(query._wheres[0]).toBe("`mycol` LIKE '%foo%'"); }); it('should handle LIKE %?% with array', function () { const query = new Select(); query.where('mycol', 'LIKE %?%', ['foo', 'bar']); expect(query._wheres[0]).toBe( "(`mycol` LIKE '%foo%' OR `mycol` LIKE '%bar%')" ); }); it('should handle NOT LIKE %?% with single item', function () { const query = new Select(); query.where('mycol', 'not like %?%', 'foo'); expect(query._wheres[0]).toBe("`mycol` NOT LIKE '%foo%'"); }); it('should handle NOT LIKE %?% with array', function () { const query = new Select(); query.where('mycol', 'not like %?%', ['foo', 'bar']); expect(query._wheres[0]).toBe( "(`mycol` NOT LIKE '%foo%' OR `mycol` NOT LIKE '%bar%')" ); }); it('should handle multiple question marks (2 args)', function () { const query = new Select(); query.table('users'); query.where('SUBSTR(prefs, ?, ?) = role', [1, 4]); expect(query.normalized()).toBe( "SELECT * FROM users WHERE SUBSTR(prefs, '1', '4') = role" ); }); }); describe('where() with Arrays', function () { it('should handle numeric arrays', function () { const query = new Select(); query.where(['mycol = LOWER(mycol2)']); expect(query._wheres[0]).toBe('mycol = LOWER(mycol2)'); }); }); describe('where() with Objects', function () { it('should handle automatic equals', function () { const query = new Select(); query.where({ mycol: 'myval' }); expect(query._wheres[0]).toBe("`mycol` = 'myval'"); }); it('should handle automatic IN', function () { const query = new Select(); query.where({ mycol: [1, 2] }); expect(query._wheres[0]).toBe('`mycol` IN(1,2)'); }); it('should handle operators', function () { const query = new Select(); query.where({ 'mycol >': 3 }); expect(query._wheres[0]).toBe('mycol > 3'); }); }); describe('having()', function () { it('should handle strings', function () { const query = new Select(); query.having('SUM(size) < 1024'); expect(query._havings[0]).toBe('SUM(size) < 1024'); }); it('should handle 2 args with implicit equals', function () { const query = new Select(); query.having('COUNT(*)', 0); expect(query._havings[0]).toBe('COUNT(*) = 0'); }); it('should handle 2 args with operator', function () { const query = new Select(); query.having('SUM(size) <', 1024); expect(query._havings[0]).toBe('SUM(size) < 1024'); }); it('should handle 3 args', function () { const query = new Select(); query.having('COUNT(*)', '>', 1); expect(query._havings[0]).toBe('COUNT(*) > 1'); }); it('should handle an object', function () { const query = new Select(); query.having({ 'COUNT(*)': 1 }); expect(query._havings[0]).toBe('COUNT(*) = 1'); }); }); describe('orHaving()', function () { it('should handle strings', function () { const query = new Select(); query.orHaving([['SUM(size) > 1024'], ['SUM(size) < 4096']]); expect(query._havings[0]).toBe('(SUM(size) > 1024 OR SUM(size) < 4096)'); }); it('should handle 2 args with implicit equals', function () { const query = new Select(); query.orHaving([ ['COUNT(*)', 0], ['SUM(size)', 0], ]); expect(query._havings[0]).toBe('(COUNT(*) = 0 OR SUM(size) = 0)'); }); }); describe('orWhere()', function () { it('should handle expressions', function () { const query = new Select(); query.orWhere([ ['a', '>', 1], ['b', 2], ]); expect(query._wheres[0]).toBe('(`a` > 1 OR `b` = 2)'); }); }); describe('sortField()', function () { it('should handle simple columns', function () { const query = new Select(); query.sortField('post.created_at'); expect(query._orderBys[0]).toBe('post.created_at ASC'); }); it('should handle minus signs', function () { const query = new Select(); query.sortField('-created_at'); expect(query._orderBys[0]).toBe('created_at DESC'); }); it('should handle mapping', function () { const query = new Select(); query.sortField('-created_at', { created_at: 'post.created_timestamp', }); expect(query._orderBys[0]).toBe('post.created_timestamp DESC'); }); }); describe('whereBetween()', function () { it('should handle arrays', function () { const query = new Select(); query.whereBetween('attempts', [1, 3]); expect(query._wheres[0]).toBe('`attempts` BETWEEN 1 AND 3'); }); it('should handle arrays (left end open)', function () { const query = new Select(); query.whereBetween('attempts', [null, 3]); expect(query._wheres[0]).toBe('`attempts` <= 3'); }); it('should handle arrays (right end open)', function () { const query = new Select(); query.whereBetween('attempts', [8]); expect(query._wheres[0]).toBe('`attempts` >= 8'); }); it('should throw error when both are nullish', function () { const bothNull = () => { const query = new Select(); query.whereBetween('attempts', [null, null]); }; expect(bothNull).toThrow(); }); it('should throw error when array is empty', function () { const emptyArray = () => { const query = new Select(); query.whereBetween('attempts', []); }; expect(emptyArray).toThrow(); }); }); describe('foundRows()', () => { it('should handle a simple query', () => { const query = Select.parse('SELECT * FROM a'); const actual = normalizeWhitespace(query.getFoundRowsSql()); expect(actual).toBe('SELECT COUNT(*) AS foundRows FROM a'); }); it('should handle a distinct', () => { const query = Select.parse('SELECT * FROM a'); const actual = query.getFoundRowsSql('DISTINCT department_id', true); expect(actual).toBe( 'SELECT COUNT(DISTINCT department_id) AS foundRows FROM a' ); }); it('should produce a new Select object', () => { const query = Select.parse('SELECT * FROM a'); const actual = query.getFoundRowsQuery().normalized(); expect(actual).toBe('SELECT COUNT(*) AS foundRows FROM a'); }); it('should handle queries with HAVING', () => { const query = Select.parse( 'SELECT category, COUNT(*) FROM posts GROUP BY category HAVING COUNT(*) > 1' ); const actual = normalizeWhitespace(query.getFoundRowsSql()); expect(actual).toBe( 'SELECT COUNT(*) AS foundRows FROM ( SELECT category, COUNT(*) FROM posts GROUP BY category HAVING COUNT(*) > 1 ) AS subq' ); expect(query.getFoundRowsSql(null, true)).toBe( 'SELECT COUNT(*) AS foundRows FROM (SELECT category, COUNT(*) FROM posts GROUP BY category HAVING COUNT(*) > 1) AS subq' ); }); it('should fetch count()', async () => { mysqlMock.pushResponse({ results: [{ foundRows: 3 }], fields: [{ name: 'foundRows' }], }); const query = new Select(); const { results } = await query.foundRows(); expect(results).toEqual(3); }); }); describe('LIMIT and OFFSET', () => { it('should add both', () => { const query = Select.parse('SELECT * FROM a'); query.limit(2); query.offset(4); expect(query.normalized()).toBe('SELECT * FROM a LIMIT 2 OFFSET 4'); expect(normalizeWhitespace(query.toString())).toBe( 'SELECT * FROM a LIMIT 2 OFFSET 4' ); }); it('should allow :named placeholders', () => { const query = Select.parse('SELECT * FROM a'); query.limit(':limit'); query.offset(':offset'); expect(query.normalized()).toBe( 'SELECT * FROM a LIMIT :limit OFFSET :offset' ); query.bind({ limit: 2, offset: 4 }); expect(query.toBoundSql()).toBe('SELECT * FROM a LIMIT 2 OFFSET 4'); }); }); describe('reset()', () => { it('should reset all', () => { const query = Select.parse('SELECT * FROM a'); query.reset(); const emptyQuery = new Select(); expect(query).toEqual(emptyQuery); }); it('should reset single fields', () => { const query = Select.parse('SELECT * FROM a'); query.reset('column'); const emptyQuery = new Select(); emptyQuery.from('a'); expect(query).toEqual(emptyQuery); }); it('should reset multiple fields', () => { const query = Select.parse('SELECT * FROM a'); query.reset(['column', 'table']); const emptyQuery = new Select(); expect(query).toEqual(emptyQuery); }); it('should reset nullable props', () => { const query = Select.parse('SELECT * FROM a LIMIT 10 OFFSET 0'); query.reset(['limit', 'offset']); const expected = 'SELECT * FROM a'; expect(query.normalized()).toEqual(expected); }); it('should reset OFFSET when resetting page', () => { const query = Select.parse('SELECT * FROM a'); query.limit(10); query.page(2); query.reset(['limit', 'page']); const expected = 'SELECT * FROM a'; expect(query.normalized()).toEqual(expected); }); }); describe('page()', () => { it('should allow 1', () => { const query = new Select(); query.page(1); expect(query._page).toBe(1); }); it('should allow 2', () => { const query = Select.parse('SELECT * FROM a'); query.page(3); query.limit(10); expect(query.normalized()).toBe('SELECT * FROM a LIMIT 10 OFFSET 20'); expect(normalizeWhitespace(query.toString())).toBe( 'SELECT * FROM a LIMIT 10 OFFSET 20' ); }); it('should handle string numbers', () => { const query = Select.parse('SELECT * FROM a'); query.page('3'); query.limit(10); expect(query.normalized()).toBe('SELECT * FROM a LIMIT 10 OFFSET 20'); }); it('should ignore 0', () => { const query = Select.parse('SELECT * FROM a'); query.page(0); expect(query.normalized()).toBe('SELECT * FROM a'); }); it('should ignore negative numbers', () => { const query = Select.parse('SELECT * FROM a'); query.page(-4); expect(query.normalized()).toBe('SELECT * FROM a'); }); it('should ignore non-numbers', () => { const query = Select.parse('SELECT * FROM a'); query.page('foo'); expect(query.normalized()).toBe('SELECT * FROM a'); }); it('should ignore strings starting with 0', () => { const query = Select.parse('SELECT * FROM a'); query.page('0123'); expect(query.normalized()).toBe('SELECT * FROM a'); }); }); describe('limit()', () => { it('should ignore non numbers', () => { const query = Select.parse('SELECT * FROM a'); query.limit('foo'); expect(query.normalized()).toBe('SELECT * FROM a'); }); it('should ignore floats', () => { const query = Select.parse('SELECT * FROM a'); query.limit(3.75); expect(query.normalized()).toBe('SELECT * FROM a'); }); it('should ignore negative numbers', () => { const query = Select.parse('SELECT * FROM a'); query.limit(-1); expect(query.normalized()).toBe('SELECT * FROM a'); }); it('should allow stringified numbers', () => { const query = Select.parse('SELECT * FROM a'); query.limit('123'); expect(query.normalized()).toBe('SELECT * FROM a LIMIT 123'); }); }); describe('offset()', () => { it('should ignore non numbers', () => { const query = Select.parse('SELECT * FROM a'); query.offset('foo'); expect(query.normalized()).toBe('SELECT * FROM a'); }); it('should ignore negative numbers', () => { const query = Select.parse('SELECT * FROM a'); query.offset(-2); expect(query.normalized()).toBe('SELECT * FROM a'); }); it('should ignore floats', () => { const query = Select.parse('SELECT * FROM a'); query.offset(2.5); expect(query.normalized()).toBe('SELECT * FROM a'); }); it('should allow stringified numbers', () => { const query = Select.parse('SELECT * FROM a'); query.offset('123'); expect(query.normalized()).toBe('SELECT * FROM a OFFSET 123'); }); }); describe('join()', () => { it('should allow generic join', () => { const query = new Select(); query.table('users u'); query.join('avatars a ON a.user_id = u.id'); expect(query.normalized()).toBe( 'SELECT * FROM users u INNER JOIN avatars a ON a.user_id = u.id' ); }); it('should allow unjoining', () => { const query = new Select(); query.table('users u'); query.join('avatars a ON a.user_id = u.id'); query.unjoin('avatars'); expect(query.normalized()).toBe('SELECT * FROM users u'); }); it('should allow unjoining array', () => { const query = new Select(); query.table('users u'); query.join('avatars a ON a.user_id = u.id'); query.join('permissions p ON p.user_id = u.id'); query.unjoin(['avatars', 'permissions']); expect(query.normalized()).toBe('SELECT * FROM users u'); }); }); describe('options()', () => { it('should add SQL_CALC_FOUND_ROWS', () => { const query = new Select(); query.table('users'); query.columns(['a', 'b']); query.limit(10); query.option('SQL_CALC_FOUND_ROWS'); expect(query.normalized()).toBe( 'SELECT SQL_CALC_FOUND_ROWS a, b FROM users LIMIT 10' ); }); }); describe('columns()', () => { it('should allow 1', () => { const query = new Select(); query.table('users'); query.columns(['a', 'b']); expect(query.normalized()).toBe('SELECT a, b FROM users'); }); }); describe('escape()', () => { it('should handle strings', () => { const query = new Select(); expect(query.escape('me')).toBe("'me'"); }); it('should handle numbers', () => { const query = new Select(); expect(query.escape(1)).toBe('1'); }); }); describe('escapeQuoteless()', () => { it('should handle strings', () => { const query = new Select(); expect(query.escapeQuoteless('me')).toBe('me'); }); it('should handle numbers', () => { const query = new Select(); expect(query.escapeQuoteless(1)).toBe('1'); }); }); describe('toBoundSql()', () => { it('should allow name-value pairs', () => { const query = Select.parse('SELECT * FROM a WHERE id = :id'); query.bind('id', 2); expect(query.toBoundSql()).toBe('SELECT * FROM a WHERE id = 2'); }); it('should allow name-value pairs twice', () => { const query = Select.parse( 'SELECT * FROM a WHERE id = :id AND type = :type' ); query.bind('id', 2); query.bind('type', 3); expect(query.toBoundSql()).toBe( 'SELECT * FROM a WHERE id = 2 AND type = 3' ); }); it('should allow objects', () => { const query = Select.parse('SELECT * FROM a WHERE id = :id'); query.bind({ id: 4 }); expect(query.toBoundSql()).toBe('SELECT * FROM a WHERE id = 4'); }); it('should allow unbinding strings', () => { const query = Select.parse('SELECT * FROM a WHERE id = :id'); query.bind({ id: 4 }); query.unbind('id'); expect(query.toBoundSql()).toBe('SELECT * FROM a WHERE id = :id'); }); it('should allow unbinding all', () => { const query = Select.parse('SELECT * FROM a WHERE id = :id'); query.bind({ id: 4 }); query.unbind(); expect(query.toBoundSql()).toBe('SELECT * FROM a WHERE id = :id'); }); }); describe('_extractBindingName()', () => { it('should find binding', () => { const sql = 'SELECT * FROM a WHERE id IN(:id)'; expect(Select._extractBindingName(sql)).toBe('id'); }); it('should throw Error when none are found', () => { const find = () => { const sql = 'SELECT * FROM a'; Select._extractBindingName(sql); }; expect(find).toThrow(); }); }); describe('fetch()', () => { it('should support simple queries', async () => { const response = { error: null, results: [ { id: 1, name: 'John' }, { id: 2, name: 'Jane' }, ], fields: [{ name: 'id' }, { name: 'name' }], }; mysqlMock.pushResponse(response); const query = new Select(); query.table('users'); query.columns(['id', 'name']); const { queries, results, fields } = await query.fetch(); expect(normalizeWhitespace(queries[0])).toBe( 'SELECT id, name FROM users' ); expect(results).toEqual(response.results); expect(fields).toEqual(response.fields); }); it('should fetch withSiblingData()', async () => { mysqlMock.pushResponse({ results: [ { id: 11, published_by: 1, published_at: '2020-02-11' }, { id: 12, published_by: 2, published_at: '2020-02-12' }, ], fields: [{ name: 'id' }], }); mysqlMock.pushResponse({ results: [ { id: 1, name: 'John' }, { id: 2, name: 'Jane' }, ], fields: [{ name: 'id' }], }); const expectedResult = [ { id: 11, published_by: 1, published_at: '2020-02-11', publisher: { id: 1, name: 'John' }, }, { id: 12, published_by: 2, published_at: '2020-02-12', publisher: { id: 2, name: 'Jane' }, }, ]; const query = new Select(); query.columns(['id', 'user_id', 'published_at']); query.table('posts'); query.withSiblingData( 'publisher', Select.parse( 'SELECT id, name FROM users WHERE user_id IN(:published_by)' ) ); const { queries, results } = await query.fetch(); expect(results).toEqual(expectedResult); expect(queries).toHaveLength(2); }); it('should fetch withChildData()', async () => { mysqlMock.pushResponse({ results: [ { id: 11, headline: 'Elvis is alive' }, { id: 12, headline: 'He proclaimed foobar' }, { id: 13, headline: 'Conspiracy 101' }, ], }); mysqlMock.pushResponse({ results: [ { id: 1, post_id: 11, path: '/plan.doc' }, { id: 2, post_id: 12, path: '/report.pdf' }, { id: 3, post_id: 12, path: '/presentation.ppt' }, ], fields: [{ name: 'post_id' }], }); const expectedResult = [ { id: 11, headline: 'Elvis is alive', files: [{ id: 1, post_id: 11, path: '/plan.doc' }], }, { id: 12, headline: 'He proclaimed foobar', files: [ { id: 2, post_id: 12, path: '/report.pdf' }, { id: 3, post_id: 12, path: '/presentation.ppt' }, ], }, { id: 13, headline: 'Conspiracy 101', files: [], }, ]; const query = new Select(); query.columns(['id', 'headline']); query.table('posts'); query.withChildData( 'files', Select.parse( 'SELECT post_id, id, path FROM files WHERE post_id IN(:id)' ) ); const { queries, results } = await query.fetch(); expect(results).toEqual(expectedResult); expect(queries).toHaveLength(2); }); it('should fetch sibling data recursively', async () => { mysqlMock.pushResponse({ results: [ { id: 11, published_by: 1, headline: 'Elvis is alive' }, { id: 12, published_by: 2, headline: 'He proclaimed foobar' }, { id: 13, published_by: 3, headline: 'Stock market up' }, { id: 14, published_by: 4, headline: 'Pigs can fly' }, ], fields: [ { name: 'id' }, { name: 'published_by' }, { name: 'headline' }, ], }); mysqlMock.pushResponse({ results: [ { id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 4, name: 'Nina' }, ], fields: [{ name: 'id' }, { name: 'name' }], }); mysqlMock.pushResponse({ results: [ { user_id: 1, url: '/avatars/John.jpg' }, { user_id: 2, url: '/avatars/Jane.jpg' }, ], fields: [{ name: 'user_id' }, { name: 'url' }], }); const expectedResult = [ { id: 11, headline: 'Elvis is alive', published_by: 1, publisher: { id: 1, name: 'John', avatar: { user_id: 1, url: '/avatars/John.jpg', }, }, }, { id: 12, headline: 'He proclaimed foobar', published_by: 2, publisher: { id: 2, name: 'Jane', avatar: { user_id: 2, url: '/avatars/Jane.jpg', }, }, }, { id: 13, headline: 'Stock market up', published_by: 3, publisher: undefined, }, { id: 14, headline: 'Pigs can fly', published_by: 4, publisher: { id: 4, name: 'Nina', avatar: undefined, }, }, ]; const avatarsQuery = Select.parse( 'SELECT user_id, url FROM avatars WHERE user_id IN(:id)' ); const usersQuery = Select.parse( 'SELECT id, name FROM users WHERE id IN(:published_by)' ); const postsQuery = Select.parse( 'SELECT id, published_by, headline FROM posts' ); postsQuery.withSiblingData('publisher', usersQuery); usersQuery.withSiblingData('avatar', avatarsQuery); const { queries, results } = await postsQuery.fetch(); expect(results).toEqual(expectedResult); expect(queries).toHaveLength(3); }); }); describe('fetchFirst()', () => { it('should fetch single row', async () => { mysqlMock.pushResponse({ results: [{ id: 1, name: 'John' }], }); const query = Select.parse('SELECT * FROM users'); const { results } = await query.fetchFirst(); expect(results).toEqual({ id: 1, name: 'John' }); }); }); describe('fetchHash()', () => { it('should fetch object', async () => { mysqlMock.pushResponse({ results: [ { id: 1, name: 'John' }, { id: 2, name: 'Jane' }, ], fields: [{ name: 'id' }, { name: 'name' }], }); const query = Select.parse('SELECT id, name FROM users'); const { results } = await query.fetchHash(); expect(results).toEqual({ 1: 'John', 2: 'Jane' }); }); }); describe('fetchList()', () => { it('should fetch array', async () => { mysqlMock.pushResponse({ results: [ { id: 1, name: 'John' }, { id: 2, name: 'Jane' }, ], fields: [{ name: 'id' }, { name: 'name' }], }); const query = Select.parse('SELECT id, name FROM users'); const { results } = await query.fetchList(); expect(results).toEqual([1, 2]); }); }); describe('fetchValue()', () => { it('should fetch array', async () => { mysqlMock.pushResponse({ results: [{ name: 'John' }], fields: [{ name: 'name' }], }); const query = Select.parse('SELECT name FROM users WHERE id = 1'); const { results } = await query.fetchValue(); expect(results).toBe('John'); }); }); describe('fetchIndexed()', () => { it('should fetch object', async () => { mysqlMock.pushResponse({ results: [ { id: 1, name: 'John' }, { id: 2, name: 'Jane' }, ], }); const query = Select.parse('SELECT * FROM users'); const { results } = await query.fetchIndexed('id'); expect(results).toEqual({ 1: { id: 1, name: 'John' }, 2: { id: 2, name: 'Jane' }, }); }); }); describe('fetchGrouped()', () => { it('should fetch object', async () => { mysqlMock.pushResponse({ results: [ { id: 1, name: 'John', department_id: 1 }, { id: 2, name: 'Jane', department_id: 1 }, { id: 3, name: 'Jean', department_id: 2 }, ], }); const query = Select.parse('SELECT * FROM users'); const { results } = await query.fetchGrouped('department_id'); expect(results).toEqual({ 1: [ { id: 1, name: 'John', department_id: 1 }, { id: 2, name: 'Jane', department_id: 1 }, ], 2: [{ id: 3, name: 'Jean', department_id: 2 }], }); }); }); describe('toString() and normalized()', () => { it('should build every type()', async () => { const query = new Select(); query.option('SQL_CALC_FOUND_ROWS'); query.column('id'); query.columns(['name', 'email']); query.from('users'); query.tables(['avatars', 'addresses']); query.join('avatars ON avatars.user_id = users.id'); query.join('addresses ON addresses.user_id = users.id'); query.where('users.id', 1); query.limit(1); const expected = normalizeWhitespace(` SELECT SQL_CALC_FOUND_ROWS id, name, email FROM users, avatars, addresses INNER JOIN avatars ON avatars.user_id = users.id INNER JOIN addresses ON addresses.user_id = users.id WHERE users.id = 1 LIMIT 1 `); expect(normalizeWhitespace(query.toString())).toEqual(expected); expect(query.normalized()).toEqual(expected); }); it('should default columns to *', async () => { const query = new Select(); query.from('users'); const expected = 'SELECT * FROM users'; expect(normalizeWhitespace(query.toString())).toEqual(expected); expect(query.normalized()).toEqual(expected); }); it('should handle ORDER BY', async () => { const query = new Select(); query.from('users'); query.orderBy('fname'); const expected = 'SELECT * FROM users ORDER BY fname'; expect(normalizeWhitespace(query.toString())).toEqual(expected); expect(query.normalized()).toEqual(expected); }); }); });