From 8b2e763c4b02d0b8060fd772b5e122a2f8f5ffba Mon Sep 17 00:00:00 2001 From: Simon Leech <56163132+simon-leech@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:43:41 +0100 Subject: [PATCH] Documentation and tests --- mod/utils/sqlFilter.js | 90 +++++++++----- tests/mod/utils/sqlFilter.test.mjs | 187 ++++++++++++++++++++++++++++- 2 files changed, 244 insertions(+), 33 deletions(-) diff --git a/mod/utils/sqlFilter.js b/mod/utils/sqlFilter.js index 985a750f1c..1400fbd3ed 100644 --- a/mod/utils/sqlFilter.js +++ b/mod/utils/sqlFilter.js @@ -1,7 +1,10 @@ /** @module /utils/sqlFilter +@description The sqlFilter module is used to convert the filter object into a SQL query string. +@exports sqlfilter */ +// The filterTypes object contains methods for each filter type. const filterTypes = { eq: (col, val) => `"${col}" = \$${addValues(val)}`, @@ -31,15 +34,33 @@ const filterTypes = { match: (col, val) => `"${col}"::text = \$${addValues(val)}` } -let SQLparams +let SQLparams; +/** +@function addValues +@description +The addValues method is used to add values to the SQLparams array. + +@param {string} val +@returns {number} SQLparams.length +*/ function addValues(val) { SQLparams.push(val) return SQLparams.length } - +/** +@function sqlfilter +@description +The sqlfilter method is used to convert the filter object into a SQL query string. +If the filter is an array, the filter will be conditional OR. +If the filter is a string, the filter will be returned as is. + +@param {Object} filter +@param {Array} params +@returns {string} SQL query string +*/ module.exports = function sqlfilter(filter, params) { if (typeof filter === 'string') return filter; @@ -49,15 +70,24 @@ module.exports = function sqlfilter(filter, params) { // Filter in an array will be conditional OR if (filter.length) return `(${filter - - // Map filter in array with OR conjuction - .map((filter) => mapFilterEntries(filter)) - .join(' OR ')})`; + + // Map filter in array with OR conjuction + .map((filter) => mapFilterEntries(filter)) + .join(' OR ')})`; // Filter in an object will be conditional AND return mapFilterEntries(filter); } +/** +@function mapFilterEntries +@description +The mapFilterEntries method is used to map the filter entries and convert them into a SQL query string. +The method also validates the filter entries against SQL parameter validation. +@param {Object} filter +@returns {string} SQL query string +*/ + function mapFilterEntries(filter) { const SQLvalidation = /^[a-zA-Z_]\w*$/ @@ -71,28 +101,28 @@ function mapFilterEntries(filter) { } return `(${Object.entries(filter) - - // Map filter entries - .map((entry) => { - - const field = entry[0] - const value = entry[1] - - // Array entry values represent conditional OR - if (value.length) return sqlfilter(value); - - // Call filter type method for matching filter entry value - // Multiple filterTypes for the same field will be joined with AND - return Object.keys(value) - .filter(filterType => !!filterTypes[filterType]) - .map(filterType => filterTypes[filterType](field, value[filterType])) - .join(' AND ') - - }) - - // Filter out undefined / escaped filter - .filter(f=>typeof f !== 'undefined') - - // Join filter with conjunction - .join(' AND ')})`; + + // Map filter entries + .map((entry) => { + + const field = entry[0] + const value = entry[1] + + // Array entry values represent conditional OR + if (value.length) return sqlfilter(value); + + // Call filter type method for matching filter entry value + // Multiple filterTypes for the same field will be joined with AND + return Object.keys(value) + .filter(filterType => !!filterTypes[filterType]) + .map(filterType => filterTypes[filterType](field, value[filterType])) + .join(' AND ') + + }) + + // Filter out undefined / escaped filter + .filter(f => typeof f !== 'undefined') + + // Join filter with conjunction + .join(' AND ')})`; } \ No newline at end of file diff --git a/tests/mod/utils/sqlFilter.test.mjs b/tests/mod/utils/sqlFilter.test.mjs index b91e5a91c0..bfc30afacc 100644 --- a/tests/mod/utils/sqlFilter.test.mjs +++ b/tests/mod/utils/sqlFilter.test.mjs @@ -4,16 +4,197 @@ import sqlfilter from '../../../mod/utils/sqlFilter'; describe('sqlFilter', () => { it('should return correct string for eq filter', () => { + // Pass one parameter + const params = ['param1']; - const params = ['param1', 'param2']; - + // 'value1' is passed as $2 in the query const filter = { fieldname: { eq: 'value1' } }; - const expected = '("fieldname" = $3)'; + const expected = '("fieldname" = $2)'; + + assertEqual(sqlfilter(filter, params), expected) + + }); + + it('should return correct string for gt filter', () => { + + // Pass one parameter + const params = ['param1']; + + // 99 is passed as $2 in the query + const filter = { + fieldname: { + gt: 99 + } + }; + + const expected = '("fieldname" > $2)'; + + assertEqual(sqlfilter(filter, params), expected) + + }); + + it('should return correct string for gte filter', () => { + + // Pass one parameter + const params = ['param1']; + + // 99 is passed as $2 in the query + const filter = { + fieldname: { + gte: 99 + } + }; + + const expected = '("fieldname" >= $2)'; + + assertEqual(sqlfilter(filter, params), expected) + + }); + + it('should return correct string for lt filter', () => { + + // Pass one parameter + const params = ['param1']; + + // 99 is passed as $2 in the query + const filter = { + fieldname: { + lt: 99 + } + }; + + const expected = '("fieldname" < $2)'; + + assertEqual(sqlfilter(filter, params), expected) + + }); + + it('should return correct string for lte filter', () => { + + // Pass one parameter + const params = ['param1']; + + // 99 is passed as $2 in the query + const filter = { + fieldname: { + lte: 99 + } + }; + + const expected = '("fieldname" <= $2)'; + + assertEqual(sqlfilter(filter, params), expected) + + }); + + it('should return correct string for boolean filter', () => { + + // Pass one parameter + const params = ['param1']; + + // true is passed as $2 in the query + const filter = { + fieldname: { + boolean: true + } + }; + + const expected = '("fieldname" IS true)'; + + assertEqual(sqlfilter(filter, params), expected) + + }); + + it('should return correct string for null filter', () => { + + // Pass one parameter + const params = ['param1']; + + // true is passed as $2 in the query + const filter = { + fieldname: { + null: 'test' + } + }; + + const expected = '("fieldname" IS NULL)'; + + assertEqual(sqlfilter(filter, params), expected) + + }); + + it('should return correct string for ni filter', () => { + + // Pass one parameter + const params = ['param1']; + + // true is passed as $2 in the query + const filter = { + fieldname: { + ni: 'test' + } + }; + + const expected = '(NOT "fieldname" = ANY ($2))'; + + assertEqual(sqlfilter(filter, params), expected) + + }); + + it('should return correct string for in filter', () => { + + // Pass one parameter + const params = ['param1']; + + // true is passed as $2 in the query + const filter = { + fieldname: { + in: 'test' + } + }; + + const expected = '("fieldname" = ANY ($2))'; + + assertEqual(sqlfilter(filter, params), expected) + + }); + + it('should return correct string for like filter', () => { + + // Pass one parameter + const params = ['param1']; + + // test is passed as $2 in the query + const filter = { + fieldname: { + like: 'test' + } + }; + + const expected = '(("fieldname" ILIKE $2))'; + + assertEqual(sqlfilter(filter, params), expected) + + }); + + it('should return correct string for match filter', () => { + + // Pass one parameter + const params = ['param1']; + + // test is passed as $2 in the query + const filter = { + fieldname: { + match: 'test' + } + }; + + const expected = '("fieldname"::text = $2)'; assertEqual(sqlfilter(filter, params), expected)