Skip to content

Commit

Permalink
Implemented transformers.
Browse files Browse the repository at this point in the history
  • Loading branch information
laran committed Mar 27, 2016
1 parent 1420898 commit 279f363
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 27 deletions.
4 changes: 2 additions & 2 deletions src/gql.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ var parser = require('../dist/parser').parser,
gql, KnexWrapper = require('./knexWrapper');

gql = {
parse: function (filters) {
parse: function (filters, transformers) {
if (_.isString(filters)) {
filters = filters ? parser.parse(filters) : {};
}

return new KnexWrapper(filters);
return new KnexWrapper(filters, transformers);
}
};

Expand Down
59 changes: 35 additions & 24 deletions src/knexWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ var _ = require('lodash'), knexWrapper,
buildSimpleComparisonCondition,
orAnalogues, dollarConditionMap,
applyCondition, applyConditions,
buildRelations, objectifyRelations;
buildRelations, objectifyRelations,
transform;

dollarConditionMap = {
$gt: '>',
Expand All @@ -15,19 +16,19 @@ dollarConditionMap = {
$like: 'like'
};

buildLogicalDollarCondition = function (conditions, key, value, negated, parentKey) {
buildLogicalDollarCondition = function (conditions, key, value, negated, parentKey, transformers) {
// it's a logical grouping such as $and, $or or $not
if (key === '$or') {
// or queries always come in as an array.
// they need to be treated specially to prevent them from being confused with an in query
var _conditions = [];
if (_.isArray(value)) {
_.forEach(value, function (_value) {
_conditions.push(buildConditions(_value));
_conditions.push(buildConditions(_value, false, null, transformers));
});
} else if (_.isPlainObject(value)) {
// it was an or condition with a single value
_conditions.push(buildConditions(value));
_conditions.push(buildConditions(value, false, null, transformers));
} else {
throw new Error('$or conditions only accept arrays or an object (which represents an array of length 1) as a value');
}
Expand All @@ -36,72 +37,82 @@ buildLogicalDollarCondition = function (conditions, key, value, negated, parentK
}
conditions.push({or: _conditions});
} else if (key === '$not') {
conditions.push(buildConditions(value, true, parentKey));
conditions.push(buildConditions(value, true, parentKey, transformers));
} else {
// it's an comparison operator such as { $lt : 5 }
conditions.push(buildCondition(key, value, negated, parentKey));
conditions.push(buildCondition(key, value, negated, parentKey, transformers));
}
};

buildDollarComparisonCondition = function (condition, key, value, negated, parentKey) {
transform = function (value, key, transformers) {
if(transformers && transformers.hasOwnProperty(key)) {
return transformers[key].apply(null, [value]);
}
return value;
};

buildDollarComparisonCondition = function (condition, key, value, negated, parentKey, transformers) {
if (dollarConditionMap.hasOwnProperty(key)) {
condition[negated ? 'whereNot' : 'where'] = [parentKey, dollarConditionMap[key], value];
condition[negated ? 'whereNot' : 'where'] = [parentKey, dollarConditionMap[key], transform(value, parentKey, transformers)];
} else if (key.match(/^\$having\./i)) {
if (negated) {
throw new Error('$having cannot be negated. It\'s invalid SQL.');
}
var valkey = Object.keys(value)[0];
var valkey, alias;
valkey = Object.keys(value)[0];
if (!dollarConditionMap.hasOwnProperty(valkey)) {
throw new Error('Unsupported aggregate comparison operator: \'' + valkey + '\'');
}
condition.having = [key.substr(8), dollarConditionMap[valkey], value[valkey]];
alias = key.substr(8);
condition.having = [alias, dollarConditionMap[valkey], transform(value[valkey], alias)];
} else {
throw new Error('' + key + ' is not a valid comparison operator');
}
return condition;
};

buildSimpleComparisonCondition = function (condition, key, value, negated) {
buildSimpleComparisonCondition = function (condition, key, value, negated, transformers) {
if (_.isNull(value)) {
condition[negated ? 'whereNotNull' : 'whereNull'] = [key];
} else if (_.isArray(value)) {
condition[negated ? 'whereNotIn' : 'whereIn'] = [key, value];
condition[negated ? 'whereNotIn' : 'whereIn'] = [key, transform(value, key, transformers)];
} else if (!_.isPlainObject(value)) {
condition[negated ? 'whereNot' : 'where'] = [key, value];
condition[negated ? 'whereNot' : 'where'] = [key, transform(value, key, transformers)];
} else {
condition = buildConditions(value, negated, key);
condition = buildConditions(value, negated, key, transformers);
}
return condition;
};

buildCondition = function (key, value, negated, parentKey) {
buildCondition = function (key, value, negated, parentKey, transformers) {
if (key) {
var condition = {};
if (key.charAt(0) === '$') {
condition = buildDollarComparisonCondition(condition, key, value, negated, parentKey);
condition = buildDollarComparisonCondition(condition, key, value, negated, parentKey, transformers);
} else {
condition = buildSimpleComparisonCondition(condition, key, value, negated);
condition = buildSimpleComparisonCondition(condition, key, value, negated, transformers);
}
return condition;
}
};

buildConditions = function (filter, negated, parentKey) {
buildConditions = function (filter, negated, parentKey, transformers) {
var conditions = [];
if (_.isArray(filter)) { // it's a clause
_.each(filter, function (f) {
conditions.push(buildConditions(f, negated, parentKey));
conditions.push(buildConditions(f, negated, parentKey, transformers));
});
} else if (_.isPlainObject(filter)) {
_.forIn(filter, function (value, key) {
_.each(Object.keys(filter), function (key) {
var value = filter[key];
if (key.charAt(0) === '$') {
if (_.isArray(value) && !key.match(/\$or/i) && !key.match(/\$not/i)) {
throw new Error('Arrays are not valid values for comparison conditions that aren\'t IN conditions');
}
buildLogicalDollarCondition(conditions, key, value, negated, parentKey);
buildLogicalDollarCondition(conditions, key, value, negated, parentKey, transformers);
} else {
// it's an attribute matcher such as { name : 'sample' }
conditions.push(buildCondition(key, value, negated));
conditions.push(buildCondition(key, value, negated, null, transformers));
}
});
}
Expand Down Expand Up @@ -206,9 +217,9 @@ objectifyRelations = function (relations) {
return _.uniq(o);
};

knexWrapper = function (filters) {
knexWrapper = function (filters, transformers) {
this.filters = filters;
this.conditions = buildConditions(filters);
this.conditions = buildConditions(filters, false, null, transformers);
this.relations = function () { return objectifyRelations(buildRelations(this.filters)); };
};

Expand Down
33 changes: 32 additions & 1 deletion test/gql_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ describe('GQL', function () {
});
});

describe('relations', function () {
describe('Relations', function () {
it('should extract simple relations', function () {
gql.parse('title:Hello').relations().should.eql([]);
});
Expand All @@ -601,4 +601,35 @@ describe('GQL', function () {
.relations().should.eql(['comments', 'tags']);
});
});

describe('Transformers', function () {
it('should convert dates and return constants', function() {
var query = gql.parse(
'name:sample,(!name:sample+created_at:<=\'2016-03-01\'),(created_at:>\'2016-03-01\'),featured:true',
{
created_at: function (o) {
// will convert created_at into a Date object
return new Date(o);
},
featured: function () {
// will always return false
return false;
}
}
);

var marchFirst = new Date('2016-03-01');
query.conditions.should.eql([
{"where": [ "name", "sample" ] },
{"or": [
{"whereNot": ["name", "sample"]},
{"where": ["created_at", "<=", marchFirst]}
]
},
{"or": {"where": ["created_at", ">", marchFirst]}},
{"or": {"where": ["featured", false]}
}
]);
});
});
});

0 comments on commit 279f363

Please sign in to comment.