├── spec ├── .eslintrc ├── support │ └── jasmine.json └── knexnest.spec.js ├── .coveralls.yml ├── .editorconfig ├── .gitignore ├── .eslintrc ├── .travis.yml ├── package.json ├── README.md ├── test └── postgres.js └── knexnest.js /spec/.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | env: 3 | jasmine: true 4 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: 1BIDeGcD59tDSEN4OpJSw88pwTYOyzZcm 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | end_of_line = lf 9 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | 3 | !/spec 4 | !/spec/** 5 | !/test 6 | !/test/** 7 | !/.coveralls.yml 8 | !/.editorconfig 9 | !/.eslintrc 10 | !/.travis.yml 11 | !/knexnest.js 12 | !/package-lock.json 13 | !/package.json 14 | !/README.md 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | env: 3 | es6: true 4 | node: true 5 | extends: 'eslint:recommended' 6 | rules: 7 | indent: 8 | - error 9 | - tab 10 | linebreak-style: 11 | - error 12 | - unix 13 | quotes: 14 | - error 15 | - single 16 | semi: 17 | - error 18 | - always 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | branches: 3 | only: 4 | - master 5 | - qc 6 | node_js: 7 | - "0.12.5" 8 | - "4" 9 | - "6" 10 | - "8" 11 | - "node" 12 | services: 13 | - postgresql 14 | before_script: 15 | - psql -c 'CREATE DATABASE knexnest_test;' -U postgres 16 | script: 17 | - npm test 18 | after_script: 19 | - DATABASE_URL=postgres://postgres@localhost:5432/knexnest_test npm run coverage:travis 20 | - npm run coveralls 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "knexnest", 3 | "version": "1.0.0", 4 | "description": "Given a Knex select object will provide a hyrdated object using promises.", 5 | "keywords": [ 6 | "Hydration", 7 | "Tabular Data", 8 | "Arrays", 9 | "Table", 10 | "Object", 11 | "Nested", 12 | "Knex", 13 | "Knex.js", 14 | "Postgres", 15 | "SQLite" 16 | ], 17 | "license": "MIT", 18 | "main": "knexnest.js", 19 | "scripts": { 20 | "coverage": "nyc jasmine", 21 | "coverage:travis": "nyc npm run test:travis", 22 | "coveralls": "nyc report --reporter=text-lcov | coveralls", 23 | "test": "jasmine", 24 | "test:travis": "jasmine && node test/postgres.js", 25 | "lint": "eslint knexnest.js spec/**/*.js" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git@github.com:CoursePark/KnexNest.git" 30 | }, 31 | "engine": { 32 | "node": ">=0.12" 33 | }, 34 | "dependencies": { 35 | "knex": "^0.14.6", 36 | "nesthydrationjs": "^1.0.5" 37 | }, 38 | "devDependencies": { 39 | "coveralls": "^3.0.1", 40 | "eslint": "^4.19.1", 41 | "jasmine": "^3.1.0", 42 | "nyc": "^11.7.3", 43 | "pg": "^7.4.3" 44 | }, 45 | "author": "Bluedrop Peformance Learning" 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | KnexNest 2 | ======== 3 | [![Build Status](https://travis-ci.org/CoursePark/KnexNest.svg?branch=master)](https://travis-ci.org/CoursePark/KnexNest) 4 | [![Coverage Status](https://coveralls.io/repos/CoursePark/KnexNest/badge.svg?branch=master&service=github)](https://coveralls.io/github/CoursePark/KnexNest?branch=master) 5 | 6 | Takes a Knex.js select query object and hydarates a list of nested objects using the NestHydration npm module. 7 | 8 | This can be simply accomplished without this library by doing 9 | 10 | ```javascript 11 | return knex.select(...) 12 | .then(NestHydrationJS.nest) 13 | .then(function (data) { 14 | ... 15 | }) 16 | ; 17 | ``` 18 | 19 | However Postgres limits column names to 63 characters and this becomes a problem when trying to nest objects several deep using NestHydration. A column name such as `_activeUser_purchases__product_originalManufacturer_logoSmall_url` would be too long and would cause nasty behavior. This module handles this problem by mapping long names to shorter ones and then returning them with a structPropToColumnMap object passed to NestHydration. 20 | 21 | Example Usage 22 | ------------- 23 | 24 | ```javascript 25 | var Knex = require('knex'); 26 | var knexnest = require('knexnest'); 27 | 28 | var knex = Knex({ 29 | client: 'postgres', 30 | connection: process.env.DATABASE_URL 31 | }); 32 | 33 | var sql = knex 34 | .select( 35 | 'c.id AS _id', 36 | 'c.title AS _title', 37 | 't.id AS _teacher_id', 38 | 't.name AS _teacher_name', 39 | 'l.id AS _lesson__id', 40 | 'l.title AS _lesson__title' 41 | ) 42 | .from('course AS c') 43 | .innerJoin('teacher AS t', 't.id', 'c.teacher_id') 44 | .innerJoin('course_lesson AS cl', 'cl.course_id', 'c.id') 45 | .innerJoin('lesson AS l', 'l.id', 'cl.lesson_id') 46 | ; 47 | knexnest(sql).then(function (data) { 48 | result = data; 49 | }); 50 | /* result should be like: 51 | [ 52 | {id: '1', title: 'Tabular to Objects', teacher: {id: '1', name: 'David'}, lesson: [ 53 | {id: '1', title: 'Defintions'}, 54 | {id: '2', title: 'Table Data'}, 55 | {id: '3', title: 'Objects'} 56 | ]}, 57 | {id: '2', title: 'Column Names Define Structure', teacher: {id: '2', name: 'Chris'}, lesson: [ 58 | {id: '4', title: 'Column Names'}, 59 | {id: '2', title: 'Table Data'}, 60 | {id: '3', title: 'Objects'} 61 | ]}, 62 | {id: '3', title: 'Object On Bottom', teacher: {id: '1', name: 'David'}, lesson: [ 63 | {id: '5', title: 'Non Array Input'}, 64 | ]} 65 | ] 66 | */ 67 | ``` 68 | 69 | Related Projects 70 | ---------------- 71 | 72 | - [NestHydrationJS](https://github.com/CoursePark/NestHydrationJS) : The base project 73 | -------------------------------------------------------------------------------- /test/postgres.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Knex = require('knex'); 4 | var knexnest = require('../knexnest'); 5 | 6 | // to use postgres, the pg npm module must be installed 7 | var knex = Knex({ 8 | client: 'postgres', 9 | connection: process.env.DATABASE_URL 10 | }); 11 | 12 | var testData = '' 13 | + 'CREATE TEMPORARY TABLE temp_user (' 14 | + ' id INTEGER PRIMARY KEY,' 15 | + ' name VARCHAR(100) NOT NULL' 16 | + ');' 17 | + 'CREATE TEMPORARY TABLE temp_product (' 18 | + ' id INTEGER PRIMARY KEY,' 19 | + ' title VARCHAR(100) NOT NULL' 20 | + ');' 21 | + 'CREATE TEMPORARY TABLE temp_user_product (' 22 | + ' id INTEGER PRIMARY KEY,' 23 | + ' user_id INTEGER NOT NULL,' 24 | + ' product_id INTEGER NOT NULL' 25 | + ');' 26 | + 'INSERT INTO temp_user (id, name) VALUES' 27 | + ' (1, \'Olga Chavez\'),' 28 | + ' (2, \'Carol Pierce\')' 29 | + ';' 30 | + 'INSERT INTO temp_product (id, title) VALUES' 31 | + ' (1, \'Orange Fresh Wrap\'),' 32 | + ' (2, \'Water Wetner\'),' 33 | + ' (3, \'Mug Mitten\')' 34 | + ';' 35 | + 'INSERT INTO temp_user_product (id, user_id, product_id) VALUES' 36 | + ' (1, 1, 1),' 37 | + ' (2, 1, 2),' 38 | + ' (3, 1, 3),' 39 | + ' (4, 2, 2),' 40 | + ' (5, 2, 3)' 41 | + ';' 42 | ; 43 | 44 | knex.raw(testData) 45 | // NOTHING COMPLICATED, SHOULD WORK ACROSS EVERYTHING 46 | .then(function () { 47 | var sql = knex 48 | .select( 49 | 'u.id AS _id', 50 | 'u.name AS _name', 51 | 'p.id AS _has__id', 52 | 'p.title AS _has__title' 53 | ) 54 | .from('temp_user AS u') 55 | .innerJoin('temp_user_product AS up', 'u.id', 'up.user_id') 56 | .innerJoin('temp_product AS p', 'p.id', 'up.product_id') 57 | ; 58 | return knexnest(sql); 59 | }) 60 | .then(function (data) { 61 | var expected = [ 62 | { 63 | id: 1, 64 | name: 'Olga Chavez', 65 | has: [ 66 | {id: 1, title: 'Orange Fresh Wrap'}, 67 | {id: 2, title: 'Water Wetner'}, 68 | {id: 3, title: 'Mug Mitten'} 69 | ] 70 | }, 71 | { 72 | id: 2, 73 | name: 'Carol Pierce', 74 | has: [ 75 | {id: 2, title: 'Water Wetner'}, 76 | {id: 3, title: 'Mug Mitten'} 77 | ] 78 | } 79 | ]; 80 | var strExpected = JSON.stringify(expected, null, ' '); 81 | var strData = JSON.stringify(data, null, ' '); 82 | if (strData !== strExpected) { 83 | throw Error('Expected\n' + strExpected + '\n to equal result of\n' + strData); 84 | } 85 | }) 86 | // LONG NAMES THAT WOULD GIVE POSTGRES PROBLEMS IF NOT CORRECTED BY KNEXNEST 87 | .then(function () { 88 | var sql = knex 89 | .select( 90 | 'u.id AS _id', 91 | 'u.name AS _aReallyReallyReallyReallyReallyReallyReallyReallyReallyLongProperty', 92 | 'p.id AS _has__id', 93 | 'p.title AS _has__aReallyReallyReallyReallyReallyReallyReallyReallyLongProperty', 94 | knex.raw('p.title AS "_has__aReallyReallyReallyReallyReallyReallyReallyLongQuotedProperty"'), 95 | knex.raw('p.title AS "_has__aReallyReallyReallyReallyReallyReallyReallyLongUnquotedProperty"') 96 | ) 97 | .from('temp_user AS u') 98 | .innerJoin('temp_user_product AS up', 'u.id', 'up.user_id') 99 | .innerJoin('temp_product AS p', 'p.id', 'up.product_id') 100 | .where('u.id', 1) 101 | .andWhere('p.id', 1) 102 | ; 103 | return knexnest(sql); 104 | }) 105 | .then(function (data) { 106 | var expected = [ 107 | { 108 | id: 1, 109 | aReallyReallyReallyReallyReallyReallyReallyReallyReallyLongProperty: 'Olga Chavez', 110 | has: [ 111 | { 112 | id: 1, 113 | aReallyReallyReallyReallyReallyReallyReallyReallyLongProperty: 'Orange Fresh Wrap', 114 | aReallyReallyReallyReallyReallyReallyReallyLongQuotedProperty: 'Orange Fresh Wrap', 115 | aReallyReallyReallyReallyReallyReallyReallyLongUnquotedProperty: 'Orange Fresh Wrap' 116 | }, 117 | ] 118 | } 119 | ]; 120 | var strExpected = JSON.stringify(expected, null, ' '); 121 | var strData = JSON.stringify(data, null, ' '); 122 | if (strData !== strExpected) { 123 | throw Error('Expected\n' + strExpected + '\n to equal result of\n' + strData); 124 | } 125 | }) 126 | .then(function () { 127 | process.stdout.write('Success\n'); 128 | }) 129 | // ERROR OUTPUT AND CLEAN UP 130 | .catch(function (error) { 131 | console.error(error); // eslint-disable-line no-console 132 | }) 133 | .then(function () { 134 | knex.destroy(); 135 | }) 136 | ; 137 | -------------------------------------------------------------------------------- /knexnest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var NestHydrationJS = require('nesthydrationjs')(); 4 | 5 | /* expects a knex object and returns a promise */ 6 | var knexnest = function (knexQuery, listOnEmpty) { 7 | // structPropToColumnMap will be sorted out properly inside nest of 8 | // NestHydration this just indicates if empty should be object or array 9 | var structPropToColumnMap = null; 10 | 11 | if (knexQuery.client.config ? knexQuery.client.config.client === 'postgres' : knexQuery.client.Raw.name === 'Raw_PG') { 12 | // Postgres limit column name lengths to 63 characters, need to work 13 | // around that. 14 | // Knex also doesn't provide get or set mechanisms for the column 15 | // information once received by select method. So the following gets 16 | // dirty by reaching in to pull out the _statement property, filtering 17 | // it for columns, processes those and puts the new values back in, 18 | // wiping out the old columns 19 | 20 | if (knexQuery._statements === undefined) { 21 | return Promise.reject('knex query object not structured as expected for KnexNest: does not have _statements property'); 22 | } 23 | 24 | var aliasList = []; 25 | var renamedMap = {}; 26 | var uniqueId = 0; 27 | 28 | var column, alias, prepend, renamed, renamedColumn; 29 | 30 | for (var i = 0; i < knexQuery._statements.length; i++) { 31 | if (knexQuery._statements[i].grouping !== 'columns') { 32 | continue; 33 | } 34 | 35 | if (knexQuery._statements[i].value === undefined) { 36 | return Promise.reject('knex query object not structured as expected for KnexNest: _statements item with column grouping does not have value property'); 37 | } 38 | 39 | // columns statement, use it 40 | for (var j = 0; j < knexQuery._statements[i].value.length; j++) { 41 | renamedColumn = null; 42 | 43 | // each column in the column statement 44 | column = knexQuery._statements[i].value[j].sql 45 | ? knexQuery._statements[i].value[j].sql 46 | : knexQuery._statements[i].value[j] 47 | ; 48 | column = column.trim(); 49 | 50 | if (column.substr(-1) === '"') { 51 | // assume the line has the format 52 | // tableNameOrAlias.columnName "alias" 53 | // or 54 | // tableNameOrAlias.columnName AS "alias" 55 | alias = column.slice(column.lastIndexOf('"', column.length - 2) + 1, -1); 56 | 57 | if (alias.length > knexnest.MAX_POSTGRES_COLUMN_NAME_LENGTH) { 58 | // shorten the alias to allowed size 59 | prepend = 'col_' + (uniqueId++) + '_'; 60 | renamed = prepend + alias.substr(-1 * knexnest.MAX_POSTGRES_COLUMN_NAME_LENGTH + prepend.length); 61 | // add to mapping used after db executed to reverse this rename 62 | renamedMap[alias] = renamed; 63 | // replace the original alias with the shortened one 64 | renamedColumn = column.substr(0, column.indexOf('"')) + '"' + renamed + '"'; 65 | } 66 | aliasList.push(alias); 67 | } else if (column.toLowerCase().indexOf(' as ') !== -1) { 68 | // assume the line has the format 69 | // tableNameOrAlias.columnName AS alias 70 | alias = column.substr(column.lastIndexOf(' ') + 1); 71 | 72 | if (alias.length > knexnest.MAX_POSTGRES_COLUMN_NAME_LENGTH) { 73 | // shorten the alias to allowed size 74 | prepend = 'col_' + (uniqueId++) + '_'; 75 | renamed = prepend + alias.substr(-1 * knexnest.MAX_POSTGRES_COLUMN_NAME_LENGTH + prepend.length); 76 | // add to mapping used after db executed to reverse this rename 77 | renamedMap[alias] = renamed; 78 | // replace the original alias with the shortened one 79 | renamedColumn = column.substr(0, column.lastIndexOf(' ') + 1) + renamed; 80 | } 81 | aliasList.push(alias); 82 | } else if (column.indexOf('.') !== -1) { 83 | // assume the line has the format 84 | // tableNameOrAlias.columnName 85 | aliasList.push(column.substr(column.indexOf('.') + 1)); 86 | } else { 87 | // assume the line has the format 88 | // columnName 89 | aliasList.push(column); 90 | } 91 | 92 | if (renamedColumn !== null) { 93 | if (knexQuery._statements[i].value[j].sql) { 94 | knexQuery._statements[i].value[j].sql = renamedColumn; 95 | } else { 96 | knexQuery._statements[i].value[j] = renamedColumn; 97 | } 98 | } 99 | } 100 | } 101 | 102 | // manually specify the propertyMapping based on the aliases determined in rename process 103 | structPropToColumnMap = NestHydrationJS.structPropToColumnMapFromColumnHints(aliasList, renamedMap); 104 | 105 | if (!(structPropToColumnMap instanceof Array) && listOnEmpty) { 106 | return Promise.reject('listOnEmpty param conflicts with query which specifies an object or null result'); 107 | } 108 | } 109 | 110 | if (structPropToColumnMap === null && listOnEmpty) { 111 | structPropToColumnMap = true; 112 | } 113 | 114 | return knexQuery 115 | .then(function (data) { 116 | return NestHydrationJS.nest(data, structPropToColumnMap); 117 | }) 118 | ; 119 | }; 120 | 121 | knexnest.MAX_POSTGRES_COLUMN_NAME_LENGTH = 63; 122 | 123 | module.exports = knexnest; 124 | -------------------------------------------------------------------------------- /spec/knexnest.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var knexnest = require('../knexnest.js'); 4 | 5 | var createMockKnexQuery = function (client, queryType, data) { 6 | var expectedClient = client === 'postgres' 7 | ? {config: {client: 'postgres'}} 8 | : {Raw: {name: client}} 9 | ; 10 | var arr = queryType === 'array' ? '_' : ''; 11 | return { 12 | client: expectedClient, 13 | _statements: [ 14 | {grouping: 'columns', value: [ 15 | 'something.id AS "' + arr + 'shortName"', 16 | 'something.someproperty AS "' + arr + 'startingShortOkNotSoShortGettingLongOkThisQualifiesAsALongReallyReallyLongName"' 17 | ]}, 18 | {grouping: 'otherstuff', value: ['should not show up in column list']}, 19 | {grouping: 'columns', value: [ 20 | 'something.someproperty AS "' + arr + 'someproperty"' 21 | ]}, 22 | {grouping: 'columns', value: [ 23 | 'something.id AS ' + arr + 'anotherShortName', 24 | 'something.someproperty AS ' + arr + 'anotherStartingShortOkNotSoShortGettingLongOkThisQualifiesAsALongReallyReallyLongName' 25 | ]}, 26 | {grouping: 'columns', value: [ 27 | '"something".otherProperty AS "' + arr + 'quotesEverywhere"', 28 | '"something".whatProperty AS ' + arr + 'quotesThereButNotHere' 29 | ]}, 30 | {grouping: 'columns', value: [ 31 | 'something.someproperty AS ' + arr + 'aliasAfterAS', 32 | 'something.someproperty As ' + arr + 'aliasAfterAs', 33 | 'something.someproperty aS ' + arr + 'aliasAfteraS', 34 | 'something.someproperty as ' + arr + 'aliasAfteras' 35 | ]} 36 | ], 37 | then: function (callback) { 38 | return new Promise(function (resolve) { 39 | process.nextTick(function () { 40 | resolve(callback(data)); 41 | }); 42 | }); 43 | }, 44 | catch: function () { 45 | } 46 | }; 47 | }; 48 | 49 | describe('KnexNest', function () { 50 | var result, error; 51 | 52 | var fullDataSetFromPostgres = [{ 53 | _shortName: '1A', 54 | col_0_hortGettingLongOkThisQualifiesAsALongReallyReallyLongName: '1B', 55 | _someproperty: '1C', 56 | _anotherShortName: '1D', 57 | col_1_hortGettingLongOkThisQualifiesAsALongReallyReallyLongName: '1E', 58 | _quotesEverywhere: '1F', 59 | _quotesThereButNotHere: '1G', 60 | _aliasAfterAS: '1H', 61 | _aliasAfterAs: '1I', 62 | _aliasAfteraS: '1J', 63 | _aliasAfteras: '1K' 64 | }]; 65 | var fullDataSetFromNonPostgres = [{ 66 | _shortName: '1A', 67 | _startingShortOkNotSoShortGettingLongOkThisQualifiesAsALongReallyReallyLongName: '1B', 68 | _someproperty: '1C', 69 | _anotherShortName: '1D', 70 | _anotherStartingShortOkNotSoShortGettingLongOkThisQualifiesAsALongReallyReallyLongName: '1E', 71 | _quotesEverywhere: '1F', 72 | _quotesThereButNotHere: '1G', 73 | _aliasAfterAS: '1H', 74 | _aliasAfterAs: '1I', 75 | _aliasAfteraS: '1J', 76 | _aliasAfteras: '1K' 77 | }]; 78 | var expectedFullDataResult = [{ 79 | shortName: '1A', 80 | startingShortOkNotSoShortGettingLongOkThisQualifiesAsALongReallyReallyLongName: '1B', 81 | someproperty: '1C', 82 | anotherShortName: '1D', 83 | anotherStartingShortOkNotSoShortGettingLongOkThisQualifiesAsALongReallyReallyLongName: '1E', 84 | quotesEverywhere: '1F', 85 | quotesThereButNotHere: '1G', 86 | aliasAfterAS: '1H', 87 | aliasAfterAs: '1I', 88 | aliasAfteraS: '1J', 89 | aliasAfteras: '1K' 90 | }]; 91 | 92 | var scenarioList = [ 93 | { 94 | describe: 'column name compliance for postgres connection and knex < 0.8.0', 95 | mockKnexQuery: createMockKnexQuery('Raw_PG', 'array', fullDataSetFromPostgres), 96 | listOnEmpty: undefined, 97 | it: 'should map the column names', 98 | expectResult: expectedFullDataResult, 99 | expectError: null 100 | }, 101 | { 102 | describe: 'column name compliance for postgres connection and knex >= 0.8.0', 103 | mockKnexQuery: createMockKnexQuery('postgres', 'array', fullDataSetFromPostgres), 104 | listOnEmpty: undefined, 105 | it: 'should map the column names', 106 | expectResult: expectedFullDataResult, 107 | expectError: null 108 | }, 109 | { 110 | describe: 'column name compliance for non-postgres connection', 111 | mockKnexQuery: createMockKnexQuery('Raw', 'array', fullDataSetFromNonPostgres), 112 | listOnEmpty: undefined, 113 | it: 'should map the column names', 114 | expectResult: expectedFullDataResult, 115 | expectError: null 116 | }, 117 | { 118 | describe: 'array query with empty result and no listOnEmpty hint for postgres connection', 119 | mockKnexQuery: createMockKnexQuery('postgres', 'array', []), 120 | listOnEmpty: undefined, 121 | it: 'should return an empty array', 122 | expectResult: [], 123 | expectError: null 124 | }, 125 | { 126 | describe: 'array query with empty result and listOnEmpty hint for postgres connection', 127 | mockKnexQuery: createMockKnexQuery('postgres', 'array', []), 128 | listOnEmpty: true, 129 | it: 'should return an empty array', 130 | expectResult: [], 131 | expectError: null 132 | }, 133 | { 134 | describe: 'array query with empty result and no listOnEmpty hint for non-postgres connection', 135 | mockKnexQuery: createMockKnexQuery('Raw', 'array', []), 136 | listOnEmpty: undefined, 137 | it: 'should return null', 138 | expectResult: null, 139 | expectError: null 140 | }, 141 | { 142 | describe: 'array query with empty result and listOnEmpty hint for non-postgres connection', 143 | mockKnexQuery: createMockKnexQuery('Raw', 'array', []), 144 | listOnEmpty: true, 145 | it: 'should return empty array', 146 | expectResult: [], 147 | expectError: null 148 | }, 149 | { 150 | describe: 'object query with empty result and no listOnEmpty hint for postgres connection', 151 | mockKnexQuery: createMockKnexQuery('postgres', 'object', []), 152 | listOnEmpty: undefined, 153 | it: 'should return an empty array', 154 | expectResult: null, 155 | expectError: null 156 | }, 157 | { 158 | describe: 'object query with empty result and listOnEmpty hint for postgres connection', 159 | mockKnexQuery: createMockKnexQuery('postgres', 'object', []), 160 | listOnEmpty: true, 161 | it: 'should throw error', 162 | expectResult: null, 163 | expectError: 'listOnEmpty param conflicts with query which specifies an object or null result' 164 | }, 165 | { 166 | describe: 'object query with empty result and no listOnEmpty hint for non-postgres connection', 167 | mockKnexQuery: createMockKnexQuery('Raw', 'object', []), 168 | listOnEmpty: undefined, 169 | it: 'should return null', 170 | expectResult: null, 171 | expectError: null 172 | }, 173 | { 174 | describe: 'object query with empty result and listOnEmpty hint for non-postgres connection', 175 | mockKnexQuery: createMockKnexQuery('Raw', 'object', []), 176 | listOnEmpty: true, 177 | it: 'should return an empty array', 178 | expectResult: [], 179 | expectError: null 180 | } 181 | ]; 182 | 183 | for (var i = 0; i < scenarioList.length; i++) { 184 | var scenario = scenarioList[i]; 185 | describe(scenario.describe, function () { 186 | beforeEach(function (done) { 187 | result = error = undefined; 188 | 189 | knexnest(scenario.mockKnexQuery, scenario.listOnEmpty) 190 | .then(function (data) { 191 | result = data; 192 | done(); 193 | }) 194 | .catch(function (err) { 195 | error = err; 196 | done(); 197 | }) 198 | ; 199 | }); 200 | 201 | it(scenario.it, function () { 202 | if (scenario.expectError) { 203 | expect(error).toEqual(scenario.expectError); 204 | } else { 205 | expect(result).toEqual(scenario.expectResult); 206 | } 207 | }); 208 | }); 209 | } 210 | }); 211 | --------------------------------------------------------------------------------