Skip to content

Commit

Permalink
Documentation and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
simon-leech committed Sep 26, 2024
1 parent b7cf677 commit 8b2e763
Show file tree
Hide file tree
Showing 2 changed files with 244 additions and 33 deletions.
90 changes: 60 additions & 30 deletions mod/utils/sqlFilter.js
Original file line number Diff line number Diff line change
@@ -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)}`,

Expand Down Expand Up @@ -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;
Expand All @@ -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*$/
Expand All @@ -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 ')})`;
}
187 changes: 184 additions & 3 deletions tests/mod/utils/sqlFilter.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down

0 comments on commit 8b2e763

Please sign in to comment.