Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

some changes #19

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions lib/js-array.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ exports.jsOperatorMap = {
};
exports.operators = {
sort: function(){
// TODO: support single argument: {'a.b.c': false, 'd': true} to denote sort(a.b.c,-d)?
var terms = [];
for(var i = 0; i < arguments.length; i++){
var sortAttribute = arguments[i];
Expand All @@ -46,8 +47,8 @@ exports.operators = {
});
return this;
},
match: filter(function(value, regex){
return new RegExp(regex).test(value);
match: filter(function(value, str, prm){
return new RegExp(str, prm).test(value);
}),
"in": filter(function(value, values){
return values.indexOf(value) > -1;
Expand Down Expand Up @@ -114,18 +115,14 @@ exports.operators = {
for (var i in object) if (object.hasOwnProperty(i)) {
selected[i] = object[i];
}
// FIXME: evaluateProperty(). Feasible for exclusion?
for(var i = 0; i < argc; i++) {
delete selected[args[i]];
}
return selected;
});
},
values: function(first){
if(arguments.length == 1){
return this.map(function(object){
return object[first];
});
}
var args = arguments;
var argc = arguments.length;
return this.map(function(object){
Expand All @@ -137,7 +134,12 @@ exports.operators = {
} else {
for(var i = 0; i < argc; i++){
var propertyName = args[i];
selected.push(object[propertyName]);
var value = evaluateProperty(object, propertyName);
selected.push(value);
}
// N.B. single argument? -> flatten array
if(args.length === 1){
return selected[0];
}
}
return selected;
Expand All @@ -147,10 +149,13 @@ exports.operators = {
var totalCount = this.length;
start = start || 0;
var sliced = this.slice(start, start + limit);
if(maxCount){
//if(maxCount){
if(true){
if (totalCount < start) start = 0;
sliced.start = start;
sliced.end = start + sliced.length - 1;
sliced.totalCount = Math.min(totalCount, typeof maxCount === "number" ? maxCount : Infinity);
//sliced.totalCount = Math.min(totalCount, typeof maxCount === "number" ? maxCount : Infinity);
sliced.totalCount = totalCount;
}
return sliced;
},
Expand Down Expand Up @@ -278,11 +283,13 @@ function filter(condition, not){
second = property;
property = undefined;
}
var args = arguments;
var args = Array.prototype.slice.call(arguments, 0);
var filtered = [];
for(var i = 0, length = this.length; i < length; i++){
var item = this[i];
if(condition(evaluateProperty(item, property), second)){
//if(condition(evaluateProperty(item, property), second)){
args[0] = evaluateProperty(item, property);
if(condition.apply(this, args)){
filtered.push(item);
}
}
Expand All @@ -306,7 +313,9 @@ exports.evaluateProperty = evaluateProperty;
function evaluateProperty(object, property){
if(property instanceof Array){
property.forEach(function(part){
object = object[decodeURIComponent(part)];
// TODO: will it be supporting $ref?
if (part)
object = object && object[decodeURIComponent(part)];
});
return object;
}else if(typeof property == "undefined"){
Expand Down
30 changes: 10 additions & 20 deletions lib/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ var operatorMap = {
"!=": "ne"
};

exports.primaryKeyName = 'id';
exports.lastSeen = ['sort', 'select', 'values', 'limit'];
exports.jsonQueryCompatible = true;

function parse(/*String|Object*/query, parameters){
if (typeof query === "undefined" || query === null)
query = '';
var term = new exports.Query();
var topTerm = term;
topTerm.cache = {}; // room for lastSeen params
if(typeof query === "object"){
if (Array.isArray(query)){
return Query().in('id', query);
}
if(query instanceof exports.Query){
return query;
}
Expand Down Expand Up @@ -96,17 +96,6 @@ function parse(/*String|Object*/query, parameters){
}
else if(propertyOrValue || delim === ','){
term.args.push(stringToValue(propertyOrValue, parameters));

// cache the last seen sort(), select(), values() and limit()
if (exports.lastSeen.indexOf(term.name) >= 0) {
topTerm.cache[term.name] = term.args;
}
// cache the last seen id equality
if (term.name === 'eq' && term.args[0] === exports.primaryKeyName) {
var id = term.args[1];
if (id && !(id instanceof RegExp)) id = id.toString();
topTerm.cache[exports.primaryKeyName] = id;
}
}
return "";
});
Expand All @@ -121,10 +110,6 @@ function parse(/*String|Object*/query, parameters){
function call(newTerm){
term.args.push(newTerm);
term = newTerm;
// cache the last seen sort(), select(), values() and limit()
if (exports.lastSeen.indexOf(term.name) >= 0) {
topTerm.cache[term.name] = term.args;
}
}
function setConjunction(operator){
if(!term.name){
Expand All @@ -137,9 +122,14 @@ function parse(/*String|Object*/query, parameters){
function removeParentProperty(obj) {
if(obj && obj.args){
delete obj.parent;
obj.args.forEach(removeParentProperty);
if (obj.args.forEach) {
obj.args.forEach(removeParentProperty);
} else {
for (var fe = 0; fe < obj.args.length; fe += 1)
removeParentProperty(obj.args[fe]);
}
}
return obj;
return obj;
};
removeParentProperty(topTerm);
return topTerm;
Expand Down
135 changes: 92 additions & 43 deletions lib/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ parser.Query = function(seed, params){
};
exports.Query = parser.Query;
//TODO:THE RIGHT WAY IS:exports.knownOperators = Object.keys(jsarray.operators || {}).concat(Object.keys(jsarray.jsOperatorMap || {}));
exports.knownOperators = ["sort", "in", "not", "any", "all", "or", "and", "select", "exclude", "values", "limit", "distinct", "recurse", "aggregate", "between", "sum", "mean", "max", "min", "count", "first", "one", "eq", "ne", "le", "ge", "lt", "gt"];
exports.knownOperators = ["sort", "match", "in", "out", "contains", "excludes", "all", "or", "and", "select", "values", "limit", "distinct", "recurse", "aggregate", "between", "sum", "mean", "max", "min", "count", "first", "one", "eq", "ne", "le", "ge", "lt", "gt"];
exports.knownScalarOperators = ["mean", "sum", "min", "max", "count", "first", "one"];
exports.arrayMethods = ["forEach", "reduce", "map", "filter", "indexOf", "some", "every"];

Expand All @@ -46,6 +46,11 @@ Query.prototype.toString = function(){
queryToString(this);
};

Query.prototype.where = function(query){
this.args = this.args.concat(parseQuery(query).args);
return this;
}

function queryToString(part) {
if (part instanceof Array) {
return '('+part.map(function(arg) {
Expand Down Expand Up @@ -78,6 +83,7 @@ function encodeString(s) {
exports.encodeValue = function(val) {
var encoded;
if (val === null) val = 'null';
if (typeof val === 'undefined') return val;
if (val !== parser.converters["default"]('' + (
val.toISOString && val.toISOString() || val.toString()
))) {
Expand All @@ -90,7 +96,7 @@ exports.encodeValue = function(val) {
val = encodeString(val.substring(1, i));
encoded = true;
}
if(type === "object"){
if(val instanceof Date){
type = "epoch";
val = val.getTime();
encoded = true;
Expand Down Expand Up @@ -142,89 +148,103 @@ exports.updateQueryMethods();
/* recursively iterate over query terms calling 'fn' for each term */
Query.prototype.walk = function(fn, options){
options = options || {};
function walk(name, terms){
(terms || []).forEach(function(term, i, arr) {
function walk(){
var self = this;
this.args = this.args.map(function(term, i, arr) {
var args, func, key, x;
term != null ? term : term = {};
func = term.name;
args = term.args;
if (!func || !args) {
return;
}
var f;
if (args[0] instanceof Query) {
walk.call(this, func, args);
f = walk;
} else {
var newTerm = fn.call(this, func, args);
if (newTerm && newTerm.name && newTerm.args)
arr[i] = newTerm;
f = fn;
}
});
return f.call(term);
}).filter(function(x){return x;});
return this;
}
walk.call(this, this.name, this.args);
};

/* append a new term */
Query.prototype.push = function(term){
this.args.push(term);
return this;
var q = walk.call(this);
return q;
};

/* disambiguate query */
Query.prototype.normalize = function(options){
options = options || {};
options.primaryKey = options.primaryKey || 'id';
options.clear = options.clear || [];
options.map = options.map || {};
var result = {
original: this,
search: this,
last: {},
sort: [],
limit: [Infinity, 0, Infinity],
skip: 0,
limit: Infinity,
sortObj: {},
sortArr: [],
limit: [Infinity, 0],
select: [],
selectObj: {},
selectArr: [],
values: false
};
var plusMinus = {
// [plus, minus]
sort: [1, -1],
select: [1, 0]
};
function normal(func, args){
function normal(){
var func = this.name;
var args = this.args;
if (!func || !args) return;
// rename props
args = this.args = args.map(function(x){return x === 'id' || x === '-id' || x === '+id' ? x.replace('id', options.primaryKey) : x});
//console.log('MAPPED', args);
// cache some parameters
if (func === 'sort' || func === 'select') {
if (func === 'sort' || func === 'select' || func === 'values') {
if (func === 'values') {
func = 'select';
result.values = true;
}
result[func] = args;
var pm = plusMinus[func];
result[func+'Arr'] = result[func].map(function(x){
if (x instanceof Array) x = x.join('.');
var o = {};
var a = /([-+]*)(.+)/.exec(x);
o[a[2]] = pm[(a[1].charAt(0) === '-')*1];
return o;
});
result[func+'Obj'] = {};
result[func].forEach(function(x){
result[func+'Arr'] = result[func].map(function(x, index){
if (x instanceof Array) x = x.join('.');
var a = /([-+]*)(.+)/.exec(x);
result[func+'Obj'][a[2]] = pm[(a[1].charAt(0) === '-')*1];
var v = pm[(a[1].charAt(0) === '-')*1] * (index+1);
result[func+'Obj'][a[2]] = v;
return {name: a[2], value: v};
});
} else if (func === 'limit') {
// validate limit() args to be numbers, with sane defaults
var limit = args;
result.skip = +limit[1] || 0;
limit = +limit[0] || 0;
var skip = +limit[1] || 0;
limit = +limit[0] || Infinity;
if (options.hardLimit && limit > options.hardLimit)
limit = options.hardLimit;
result.limit = limit;
result.limit = [limit, skip];
result.needCount = true;
} else if (func === 'values') {
// N.B. values() just signals we want array of what we select()
result.values = true;
} else if (func === 'eq') {
// cache primary key equality -- useful to distinguish between .get(id) and .query(query)
var t = typeof args[1];
//if ((args[0] instanceof Array ? args[0][args[0].length-1] : args[0]) === options.primaryKey && ['string','number'].indexOf(t) >= 0) {
if (args[0] === options.primaryKey && ['string','number'].indexOf(t) >= 0) {
result.pk = String(args[1]);
} else {
if (func === 'eq') {
// cache first primary key equality -- useful to distinguish between .get(id) and .query(query)
if (args[0] === options.primaryKey && ['string','number'].indexOf(typeof args[1]) >= 0) {
if (!result.pk)
result.pk = String(args[1]);
}
}
// collect search conditions
var arg0 = args[0];
if (arg0 instanceof Array) arg0 = arg0.join('/');
//if (!result.cond[arg0])
// result.cond[arg0] = [];
//result.cond[arg0].push(this);
// memorize the last condition
result.last[arg0] = this;
// clear all conditions on fields specified in options.clear[]
return options.clear.indexOf(this.args[0]) >= 0 ? undefined : this;
}
// cache search conditions
//if (options.known[func])
Expand All @@ -233,7 +253,36 @@ Query.prototype.normalize = function(options){
func = options.map[func];
}*/
}

// normalize
this.walk(normal);

result.filter = function(query){
var Q = String(query);
//console.log('ADD', Q, this.search);
this.search.args = this.search.args.filter(function(x){
return String(x) !== Q;
});
/*var args = query.args[0].args;
var arg0 = args[0];
if (arg0 instanceof Array) arg0 = arg0.join('/');
if (!this.cond[arg0])
this.cond[arg0] = [];
this.cond[arg0].push(query.args[0]);*/
this.search.args.push(query.args[0]);
return this;
};

result.toString = function(){
var q = this.search;
['sort', 'select', 'limit', 'values'].forEach(function(op){
var args = result[op];
if (args instanceof Array && args.length)
q = q[op].apply(q, args);
});
return q.toString();
};

return result;
};

Expand Down
Loading