Skip to content

Commit

Permalink
Merge pull request #8 from tmukammel/iss-4
Browse files Browse the repository at this point in the history
ADD: default limit to query
  • Loading branch information
tmukammel authored Nov 3, 2022
2 parents bd45596 + 5b1eef7 commit 0c3cd0c
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 97 deletions.
9 changes: 9 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"semi": true,
"singleQuote": true,
"arrowParens": "always",
"printWidth": 160,
"tabWidth": 4,
"endOfLine": "auto",
"trailingComma": "none"
}
189 changes: 92 additions & 97 deletions sequelizeQueryParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
* Author: Twaha Mukammel ([email protected])
*/

'use strict'
'use strict';

const Promise = require('bluebird');

/**
* Pass `db` connection object which has `Sequelize.Op`
* @param {*} db
* @param {*} db
* @returns object {parse}
*/
module.exports = function (db) {
Expand All @@ -24,115 +24,110 @@ module.exports = function (db) {
ne: Op.ne,
eq: Op.eq,
not: Op.not,
like: Op.like, // LIKE '%hat'
notLike: Op.notLike, // NOT LIKE '%hat'
like: Op.like, // LIKE '%hat'
notLike: Op.notLike, // NOT LIKE '%hat'
// iLike: Op.iLike, // ILIKE '%hat' (case insensitive) (PG only)
// notILike: Op.notILike, // NOT ILIKE '%hat' (PG only)
regexp: Op.regexp, // REGEXP/~ '^[h|a|t]' (MySQL/PG only)
notRegexp: Op.notRegexp, // NOT REGEXP/!~ '^[h|a|t]' (MySQL/PG only)
regexp: Op.regexp, // REGEXP/~ '^[h|a|t]' (MySQL/PG only)
notRegexp: Op.notRegexp, // NOT REGEXP/!~ '^[h|a|t]' (MySQL/PG only)
// iRegexp: Op.iRegexp, // ~* '^[h|a|t]' (PG only)
// notIRegexp: Op.notIRegexp // !~* '^[h|a|t]' (PG only)
and: Op.and, // AND (a = 5)
or: Op.or, // (a = 5 OR a = 6)
between: Op.between, // BETWEEN 6 AND 10
notBetween: Op.notBetween, // NOT BETWEEN 11 AND 15
in: Op.in, // IN [1, 2]
notIn: Op.notIn, // NOT IN [1, 2]
and: Op.and, // AND (a = 5)
or: Op.or, // (a = 5 OR a = 6)
between: Op.between, // BETWEEN 6 AND 10
notBetween: Op.notBetween, // NOT BETWEEN 11 AND 15
in: Op.in, // IN [1, 2]
notIn: Op.notIn // NOT IN [1, 2]
// overlap: Op.overlap, // && [1, 2] (PG array overlap operator)
// contains: Op.contains, // @> [1, 2] (PG array contains operator)
// contained: Op.contained, // <@ [1, 2] (PG array contained by operator)
// col: Op.col // = "user"."organization_id", with dialect specific column identifiers, PG in this example
// any: Op.any // ANY ARRAY[2, 3]::INTEGER (PG only)
}
};

/**
* Split '.' or ',' seperated strings to array
* @param {JSON} obj
* @param {array} array
* @param {JSON} obj
* @param {array} array
*/
const splitStringAndBuildArray = (obj, array) => {
let elements = obj.split(',');

if (elements && elements.length > 0) {
elements.forEach(element => {
elements.forEach((element) => {
var fields = element.split('.');
if (fields && fields.length > 0) {
array.push(fields)
array.push(fields);
}
});
}
}
};

/**
* Parse query params
* @param {string|Array} query
* @returns {Array} sequelize formatted DB query array
*/
const parseFields = (query) => {
let array = null;

if (query !== null) {
array = [];

if (Array.isArray(query) == true) {
query.forEach(obj => {
query.forEach((obj) => {
splitStringAndBuildArray(obj, array);
});
}
else {
} else {
splitStringAndBuildArray(query, array);
}
}

return array;
}
};

/**
* Replaces operator (json object key) with Sequelize operator.
* @param {JSON} json
* @param {string} key
* @param {JSON} json
* @param {string} key
* @param {Sequelize.op} op
*/
const replaceKeyWithOperator = (json, key, op) => {
let value = json[key];
delete json[key];
json[op] = value;
}
};

/**
* Iteratively replace json keys with Sequelize formated query operators.
* @param {JSON} json next json
*/
const iterativeReplace = (json) => {
Object.keys(json).forEach(function (key) {
if (json[key] !== null && typeof json[key] === 'object') {

// console.debug("key: ", key);
let op = operators[key];
// console.debug("operation: ", op);

if (op) {
replaceKeyWithOperator(json, key, op);
// console.debug("next: ", JSON.stringify(json[op], null, 4));
iterativeReplace(json[op]);
}
else {
} else {
// console.debug("next: ", JSON.stringify(json[key], null, 4));
iterativeReplace(json[key]);
}
}
else if (key == 'model' && db[json[key]] != null) {
} else if (key == 'model' && db[json[key]] != null) {
// json['as'] = json[key].replace(/^./, char => char.toLowerCase());// /^\w/
json['model'] = db[json[key]];
}
else {
} else {
let op = operators[key];
if (op) replaceKeyWithOperator(json, key, op);
}

// console.debug("After Key:", key, " Query fields: ", JSON.stringify(json, null, 4))
});
}
};

/**
* Unescape escaped sequences in string.
Expand All @@ -143,32 +138,32 @@ module.exports = function (db) {
const queryString = query.toString();
const queryStringUnescaped = unescape(queryString);
return queryStringUnescaped;
}
};

/**
* Parse and build Sequelize format query
* @param {JSON} query
* @param {JSON} query
* @returns {JSON} sequelize formatted DB query params JSON
*/
const parseQueryFields = (query) => {
let json = JSON.parse(unescapeEscapedQuery(query));
iterativeReplace(json);
// console.debug("Resultent query fields: ", json);
return json;
}
};

/**
* Parse and build Sequelize format query
* @param {JSON} query
* @param {JSON} query
* @returns {JSON} sequelize formatted DB include params JSON
*/
const parseIncludeFields = (query) => {
let json = JSON.parse(unescapeEscapedQuery(query));
iterativeReplace(json);
// console.debug("Resultent include fields: ", json);
return json;
}
};

/**
* Parse single query parameter
* @param {string} query
Expand All @@ -179,122 +174,122 @@ module.exports = function (db) {
// console.debug("Query param: ", JSON.stringify(elements, null, 4));
if (elements && elements.length > 1) {
var param = {};
const elementsArray = elements[1].split(',')
if (elementsArray){
if (elementsArray.length > 1){
param[operators[elements[0]]] = elementsArray
}
else {
param[operators[elements[0]]] = elementsArray[0]
const elementsArray = elements[1].split(',');
if (elementsArray) {
if (elementsArray.length > 1) {
param[operators[elements[0]]] = elementsArray;
} else {
param[operators[elements[0]]] = elementsArray[0];
}
// console.debug("Query param: ", param);
return param;
}
}
// console.debug("Query param: ", elements[0]);
return elements[0];
}
};

// Max page size limit is set to 200
const pageSizeLimit = 200;

/**
* Sequelize Query Parser is a very simple package that
* Sequelize Query Parser is a very simple package that
* turns your RESTful APIs into a basic set of Graph APIs.
*
*
* Parses - filter, query, sorting, paging, group, relational object queries
*
*
* fields=field01,field02...
*
*
* limit=value&&offset=value
*
*
* sort_by=field01.asc|field02.desc
*
*
* group_by=field01,field02
*
*
* query=JSON
*
*
* include=JSON
*
*
* filedName=unaryOperator:value
*
*
* @param {JSON} req
* @returns {JSON} sequelize formatted DB query
*/
function parse(req) {
console.debug("Request query: ", req.query);
console.debug('Request query: ', req.query);

return new Promise((resolve, reject) => {
try {
var offset = 0, limit = pageSizeLimit;
var offset = 0,
limit = pageSizeLimit;
var dbQuery = {
where: {}
where: {},
offset,
limit
};
const Op = db.Sequelize.Op;


for (const key in req.query) {
switch (key) {
// Fields
case 'fields':
// split the field names (attributes) and assign to an array
let fields = req.query.fields.split(",");
let fields = req.query.fields.split(',');
// assign fields array to .attributes
if (fields && fields.length > 0) dbQuery.attributes = fields;
break;

// pagination page size
case 'limit':
dbQuery.limit = Math.min(Math.max(parseInt(req.query.limit), 1), pageSizeLimit);
limit = dbQuery.limit;
break;

// pagination page offset
case 'offset':
offset = Math.max(parseInt(req.query.offset), 0);
break;

// Sort by field order
case 'sort_by':
dbQuery.order = parseFields(req.query.sort_by);
break;

// Group by field
// TODO: Check array
case 'group_by':
dbQuery.group = parseFields(req.query.group_by);
break;

// JSON (nested) query
case 'query':
let parsed = parseQueryFields(req.query.query);
dbQuery.where = { ...dbQuery.where, ...parsed };
break;

// include and query on associated tables
case 'include':
dbQuery.include = parseIncludeFields(req.query.include);
break;

// Simple filter query
default:
dbQuery.where[key] = parseQueryParam(req.query[key]);
break;
}
}

dbQuery.offset = offset * limit;
console.debug("Final sequelize query:");

console.debug('Final sequelize query:');
console.debug(JSON.stringify(dbQuery, null, 4));

resolve(dbQuery);
} catch (error) {
console.debug('Error: ', error.message);
reject([{ msg: error.message }]);
}
catch(error) {
console.debug("Error: ", error.message)
reject([{msg: error.message}]);
}
})
});
}
return { parse }
}

return { parse };
};

0 comments on commit 0c3cd0c

Please sign in to comment.