Skip to content

Commit

Permalink
Merge pull request #8 from brandsExclusive/LBO-438
Browse files Browse the repository at this point in the history
[LBO-438] Vendor Search to sort matches displayed in admin portal
  • Loading branch information
reb2020 authored Mar 16, 2018
2 parents 6b792fa + a3e5c15 commit be6a762
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 27 deletions.
5 changes: 5 additions & 0 deletions lib/functions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

module.exports = {
similarity: require('./similarity')
}

6 changes: 6 additions & 0 deletions lib/functions/similarity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const escape = require("pg-escape");

module.exports = function (field, word) {
return escape('similarity(%I, %L)', field, word)
}

62 changes: 58 additions & 4 deletions lib/queryBuilder.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// See test/queryBuilder_test.js for usage examples

const {similarity} = require('./functions/index');

function queryBuilder(initial_state) {
let out = {};
out.state = initial_state || {};
Expand All @@ -8,9 +10,12 @@ function queryBuilder(initial_state) {
let sql = "";
let bound_vars = [];
if (this.state.count) {
sql = sql + " SELECT COUNT(*) "
sql = sql + " SELECT COUNT(*)"
} else {
sql = sql + " SELECT * "
if(!this.state.select){
this.state.select = ['*'];
}
sql = sql + " SELECT " + this.state.select.join(", ")
}
if (this.state.from) {
sql = sql + ` FROM ${this.state.from} `
Expand Down Expand Up @@ -53,7 +58,22 @@ function queryBuilder(initial_state) {
}

if (this.state.orderBy) {
sql = sql + ` ORDER BY ${this.state.orderBy[0]} ${this.state.orderBy[1]} `
let ordersBy = [];
for (order of this.state.orderBy) {
let column = order[0],
direction = order[1],
orderParams = order[2];

if(orderParams){
if(orderParams.function == 'similarity'){
column = similarity(column, orderParams.values.pop());
}
}

ordersBy.push(`${column} ${direction}`)
}

sql = sql + " ORDER BY " + ordersBy.join(", ");
}

if (this.state.paginate) {
Expand All @@ -76,16 +96,50 @@ function queryBuilder(initial_state) {
};
out.from = from.bind(out);

let select = function (fields) {
this.state.select = fields;
return this;
};
out.select = select.bind(out);

let addSelect = function (field) {
this.state.select = this.state.select || [];
this.state.select.push(field);
return this;
};
out.addSelect = addSelect.bind(out);

let orderBy = function (field, direction) {
delete this.state.count;
this.state.orderBy = [field, direction];
this.state.orderBy = [];
this.state.orderBy.push([field, direction, null]);
return this;
};
out.orderBy = orderBy.bind(out);

let addOrderBy = function (field, direction) {
delete this.state.count;
this.state.orderBy = this.state.orderBy || [];
this.state.orderBy.push([field, direction, null]);
return this;
};
out.addOrderBy = addOrderBy.bind(out);

let addOrderByFunction = function (functionName, field, values, direction) {
delete this.state.count;
this.state.orderBy = this.state.orderBy || [];
this.state.orderBy.push([field, direction, {
function: functionName,
values: values || []
}]);
return this;
};
out.addOrderByFunction = addOrderByFunction.bind(out);

let where = function (field, operator, value) {
this.state.wheres = this.state.wheres || [];
this.state.wheres.push([field, operator, value]);

return this;
};
out.where = where.bind(out);
Expand Down
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"dependencies": {
"eslint": "^4.0.0",
"pg": "^6.2.4",
"pg-escape": "^0.2.0",
"pg-pool": "^1.7.1",
"postgrator": "^2.10.0",
"url": "^0.11.0"
Expand Down
102 changes: 79 additions & 23 deletions test/queryBuilder_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,36 @@ describe('queryBuilder', function() {
it('should accept from', function() {
query = queryBuilder();
query.from("a_thing");
expect([" SELECT * FROM a_thing ", []]).to.eql(query.finalize());
expect([" SELECT * FROM a_thing ", []]).to.eql(query.finalize());
});

it('with multiple froms, the last should win', function() {
query = queryBuilder();
query.from("a_thing");
query.from("banana");
expect([" SELECT * FROM banana ", []]).to.eql(query.finalize());
expect([" SELECT * FROM banana ", []]).to.eql(query.finalize());
});

it('should accept count', function() {
query = queryBuilder();
query.from("banana");
query.count();
expect([" SELECT COUNT(*) FROM banana ", []]).to.eql(query.finalize());
expect([" SELECT COUNT(*) FROM banana ", []]).to.eql(query.finalize());
});

it('should accept order by', function() {
query = queryBuilder();
query.from("banana");
query.orderBy("species", "ASC");
expect([" SELECT * FROM banana ORDER BY species ASC ", []]).to.eql(query.finalize());
expect([" SELECT * FROM banana ORDER BY species ASC", []]).to.eql(query.finalize());
});

it('should clear order by when calling count', function() {
query = queryBuilder();
query.from("banana");
query.orderBy("species", "ASC");
query.count();
expect([" SELECT COUNT(*) FROM banana ", []]).to.eql(query.finalize());
expect([" SELECT COUNT(*) FROM banana ", []]).to.eql(query.finalize());
});

it('should clear count by when calling order by', function() {
Expand All @@ -46,51 +46,51 @@ describe('queryBuilder', function() {
query.count();
query.orderBy("species", "ASC");

expect([" SELECT * FROM banana ORDER BY species ASC ", []]).to.eql(query.finalize());
expect([" SELECT * FROM banana ORDER BY species ASC", []]).to.eql(query.finalize());
});

it('with multiple order bys, the last should win', function() {
query = queryBuilder();
query.from("banana");
query.orderBy("peel", "DESC");
query.orderBy("species", "ASC");
expect([" SELECT * FROM banana ORDER BY species ASC ", []]).to.eql(query.finalize());
expect([" SELECT * FROM banana ORDER BY species ASC", []]).to.eql(query.finalize());
});

it('should accept pagination', function() {
query = queryBuilder();
query.from("banana");
query.paginate(5, 5);
expect([" SELECT * FROM banana LIMIT 5 OFFSET 20 ", []]).to.eql(query.finalize());
expect([" SELECT * FROM banana LIMIT 5 OFFSET 20 ", []]).to.eql(query.finalize());
});

it('should accept string pagination', function() {
query = queryBuilder();
query.from("banana");
query.paginate("5", "5");
expect([" SELECT * FROM banana LIMIT 5 OFFSET 20 ", []]).to.eql(query.finalize());
expect([" SELECT * FROM banana LIMIT 5 OFFSET 20 ", []]).to.eql(query.finalize());
});

it('should sanitize garbage in pagination', function() {
query = queryBuilder();
query.from("banana");
query.paginate("5bbbb", "5aaaa");
expect([" SELECT * FROM banana LIMIT 5 OFFSET 20 ", []]).to.eql(query.finalize());
expect([" SELECT * FROM banana LIMIT 5 OFFSET 20 ", []]).to.eql(query.finalize());
});

it('with multiple paginates, the last should win', function() {
query = queryBuilder();
query.from("banana");
query.paginate(2, 2);
query.paginate(5, 5);
expect([" SELECT * FROM banana LIMIT 5 OFFSET 20 ", []]).to.eql(query.finalize());
expect([" SELECT * FROM banana LIMIT 5 OFFSET 20 ", []]).to.eql(query.finalize());
});

it('should accept where', function() {
query = queryBuilder();
query.from("banana");
query.where("species","=", "cavendish");
expect([" SELECT * FROM banana WHERE species = $1 ", ["cavendish"]]).to.eql(query.finalize());
expect([" SELECT * FROM banana WHERE species = $1 ", ["cavendish"]]).to.eql(query.finalize());
});

it('should compose multiple wheres', function() {
Expand All @@ -99,7 +99,7 @@ describe('queryBuilder', function() {
query.where("species","=", "cavendish");
query.where("state","=", "ripe");
expect([
" SELECT * FROM banana WHERE species = $1 AND state = $2 "
" SELECT * FROM banana WHERE species = $1 AND state = $2 "
, ["cavendish", "ripe"]]).to.eql(query.finalize());
});

Expand All @@ -109,7 +109,7 @@ describe('queryBuilder', function() {
query.where("species","in", ['cavendish','alchemist']);
query.where("feeder","in", ['ape','cat']);
expect([
" SELECT * FROM banana WHERE species in ($1,$2) AND feeder in ($3,$4) "
" SELECT * FROM banana WHERE species in ($1,$2) AND feeder in ($3,$4) "
,["cavendish","alchemist","ape","cat"]]).to.eql(query.finalize());
});

Expand All @@ -119,7 +119,7 @@ describe('queryBuilder', function() {
query.where("species","in", ['cavendish','alchemist']);
query.where("feeder","=", 'ape');
expect([
" SELECT * FROM banana WHERE species in ($1,$2) AND feeder = $3 "
" SELECT * FROM banana WHERE species in ($1,$2) AND feeder = $3 "
,["cavendish","alchemist","ape"]]).to.eql(query.finalize());
});

Expand All @@ -128,7 +128,7 @@ describe('queryBuilder', function() {
query.from("sports");
query.where("name","like", "football");
expect([
" SELECT * FROM sports WHERE name like $1 "
" SELECT * FROM sports WHERE name like $1 "
,["%football%"]]).to.eql(query.finalize());
});

Expand All @@ -137,7 +137,7 @@ describe('queryBuilder', function() {
query.from("sports");
query.where("name","ilike", "Football");
expect([
" SELECT * FROM sports WHERE name ilike $1 "
" SELECT * FROM sports WHERE name ilike $1 "
,["%Football%"]]).to.eql(query.finalize());
});

Expand All @@ -148,7 +148,7 @@ describe('queryBuilder', function() {
query.where("feeder","=", 'ape');
query.where("name","like", "Football");
expect([
" SELECT * FROM banana WHERE species in ($1,$2) AND feeder = $3 AND name like $4 "
" SELECT * FROM banana WHERE species in ($1,$2) AND feeder = $3 AND name like $4 "
,["cavendish","alchemist","ape","%Football%"]]).to.eql(query.finalize());
});

Expand All @@ -159,7 +159,7 @@ describe('queryBuilder', function() {
query.where("feeder","=", 'ape');
query.where("name","ilike", "Football");
expect([
" SELECT * FROM banana WHERE species in ($1,$2) AND feeder = $3 AND name ilike $4 "
" SELECT * FROM banana WHERE species in ($1,$2) AND feeder = $3 AND name ilike $4 "
,["cavendish","alchemist","ape","%Football%"]]).to.eql(query.finalize());
});

Expand All @@ -168,8 +168,8 @@ describe('queryBuilder', function() {
query.from("banana");
second_query = query.clone();
second_query.count();
expect([" SELECT * FROM banana " , []]).to.eql(query.finalize());
expect([" SELECT COUNT(*) FROM banana " , []]).to.eql(second_query.finalize());
expect([" SELECT * FROM banana " , []]).to.eql(query.finalize());
expect([" SELECT COUNT(*) FROM banana " , []]).to.eql(second_query.finalize());
});

it('should compose all query types and clone cleanly', function() {
Expand All @@ -181,8 +181,64 @@ describe('queryBuilder', function() {
second_query = query.clone();
second_query.count();
query.paginate(5, 5);
expect([" SELECT * FROM banana WHERE species = $1 AND state = $2 ORDER BY species ASC LIMIT 5 OFFSET 20 " , ["cavendish", "ripe"]]).to.eql(query.finalize());
expect([" SELECT COUNT(*) FROM banana WHERE species = $1 AND state = $2 ",["cavendish", "ripe"]]).to.eql(second_query.finalize());
expect([" SELECT * FROM banana WHERE species = $1 AND state = $2 ORDER BY species ASC LIMIT 5 OFFSET 20 " , ["cavendish", "ripe"]]).to.eql(query.finalize());
expect([" SELECT COUNT(*) FROM banana WHERE species = $1 AND state = $2 ",["cavendish", "ripe"]]).to.eql(second_query.finalize());
});

it('should compose custom select fields', function() {
query = queryBuilder();
query.from("banana");
query.select(["first_name"]);
query.addSelect("last_name");
query.where("species","in", ['cavendish','alchemist']);
query.where("feeder","=", 'ape');
expect([
" SELECT first_name, last_name FROM banana WHERE species in ($1,$2) AND feeder = $3 "
,["cavendish","alchemist","ape"]]).to.eql(query.finalize());
});

it('should compose custom select fields and several orders', function() {
query = queryBuilder();
query.from("banana");
query.select(["first_name"]);
query.addSelect("last_name");
query.where("species","in", ['cavendish','alchemist']);
query.where("feeder","=", 'ape');
query.orderBy("species", "ASC");
query.addOrderBy("first_name", "DESC");
expect([
" SELECT first_name, last_name FROM banana WHERE species in ($1,$2) AND feeder = $3 ORDER BY species ASC, first_name DESC"
,["cavendish","alchemist","ape"]]).to.eql(query.finalize());
});

it('should compose custom select fields and several orders with order by function', function() {
query = queryBuilder();
query.from("banana");
query.select(["first_name"]);
query.addSelect("last_name");
query.where("species","in", ['cavendish','alchemist']);
query.where("feeder","=", 'ape');
query.orderBy("species", "ASC");
query.addOrderBy("first_name", "DESC");
query.addOrderByFunction('similarity', 'species', ['cavendish'], 'DESC')
expect([
" SELECT first_name, last_name FROM banana WHERE species in ($1,$2) AND feeder = $3 ORDER BY species ASC, first_name DESC, similarity(species, 'cavendish') DESC"
,["cavendish","alchemist","ape"]]).to.eql(query.finalize());
});

it('test injection', function() {
query = queryBuilder();
query.from("banana");
query.select(["first_name"]);
query.addSelect("last_name");
query.where("species","in", ['cavendish','alchemist']);
query.where("feeder","=", 'ape');
query.orderBy("species", "ASC");
query.addOrderBy("first_name", "DESC");
query.addOrderByFunction('similarity', 'species', ['\'; (select * from banana);'], 'DESC')
expect([
" SELECT first_name, last_name FROM banana WHERE species in ($1,$2) AND feeder = $3 ORDER BY species ASC, first_name DESC, similarity(species, '''; (select * from banana);') DESC"
,["cavendish","alchemist","ape"]]).to.eql(query.finalize());
});

});
Expand Down

0 comments on commit be6a762

Please sign in to comment.