Support value expressions in filter

This commit is contained in:
Zaid Abdulla committed 2018-12-26 00:20:47 -08:00
1 parent 0e6f08b68b
commit ea2e3707e3
7 files changed
+378 -212

No files matched your search

+24 -4
View File
@@ -7,17 +7,29 @@ const querystring = require("querystring");
const config = require("./config");
const device = require("./device");
const filterParser = require("./filter-parser");
const filterParser = require("../common/filter-parser");
const commaAscii = ",".charCodeAt(0);
const newLineAscii = "\n".charCodeAt(0);
function unpackTimestamps(filter) {
return filterParser.map(filter, exp => {
if (["=", "<>", ">=", "<=", "<", ">"].includes(exp[0]))
if (typeof exp[2] === "number") {
let alt = exp.slice();
alt[2] = new Date(alt[2]).toJSON();
return ["OR", exp, alt];
}
});
}
function filterToQuery(filter, negate = false, res = {}) {
const op = filter[0];
if ((!negate && op === "AND") || (negate && op === "OR")) {
res["$and"] = res["$and"] || [];
for (let i = 1; i < filter.length; ++i)
filterToQuery(filter[i], negate, res);
res["$and"].push(filterToQuery(filter[i], negate));
} else if ((!negate && op === "OR") || (negate && op === "AND")) {
res["$or"] = res["$or"] || [];
@@ -68,7 +80,11 @@ function count(resource, filter, limit) {
return new Promise((resolve, reject) => {
let qs = {};
if (filter) {
let q = filterToQuery(filterParser.parse(filter));
let f = filterParser.parse(filter);
f = filterParser.evaluateExpressions(f);
f = unpackTimestamps(f);
let q = filterToQuery(f);
if (resource === "devices") q = device.transposeQuery(q);
qs.query = JSON.stringify(q);
}
@@ -102,7 +118,11 @@ function query(resource, filter, limit, callback) {
if (!callback) ret = [];
let qs = {};
if (filter) {
let q = filterToQuery(filterParser.parse(filter));
let f = filterParser.parse(filter);
f = filterParser.evaluateExpressions(f);
f = unpackTimestamps(f);
let q = filterToQuery(f);
if (resource === "devices") q = device.transposeQuery(q);
qs.query = JSON.stringify(q);
+2
View File
@@ -1,4 +1,6 @@
"use strict";
const filterParser = require("../common/filter-parser.js");
exports.PORT = 3000;
exports.GENIEACS_NBI = "http://127.0.0.1:7557/";
+18
View File
@@ -150,6 +150,8 @@ function transposeQuery(query) {
newQuery["_deviceId._SerialNumber"] = val;
} else if (key === "DeviceID.OUI") {
newQuery["_deviceId._OUI"] = val;
} else if (key === "DeviceID.ProductClass") {
newQuery["_deviceId._ProductClass"] = val;
} else if (key === "DeviceID.Manufacturer") {
newQuery["_deviceId._Manufacturer"] = val;
} else if (key.startsWith("Tags.")) {
@@ -162,6 +164,22 @@ function transposeQuery(query) {
newQuery["_tags"]["$nin"] = newQuery["_tags"]["$nin"] || [];
newQuery["_tags"]["$nin"].push(key.slice(5));
}
} else if (key === "tag") {
if (typeof val === "string") {
newQuery["_tags"] = newQuery["_tags"] || {};
newQuery["_tags"]["$all"] = newQuery["_tags"]["$all"] || [];
newQuery["_tags"]["$all"].push(val);
} else if (val["$eq"]) {
newQuery["_tags"] = newQuery["_tags"] || {};
newQuery["_tags"]["$all"] = newQuery["_tags"]["$all"] || [];
newQuery["_tags"]["$all"].push(val["$eq"]);
} else if (val["$ne"]) {
newQuery["_tags"] = newQuery["_tags"] || {};
newQuery["_tags"]["$nin"] = newQuery["_tags"]["$nin"] || [];
newQuery["_tags"]["$nin"].push(val["$ne"]);
}
} else if (key === "Events.Inform") {
newQuery["_lastInform"] = val;
} else {
newQuery[key] = val;
}
-207
View File
@@ -1,207 +0,0 @@
"use strict";
const parsimmon = require("parsimmon");
// Turn escaped characters into real ones (e.g. "\\n" becomes "\n").
function interpretEscapes(str) {
let escapes = {
b: "\b",
f: "\f",
n: "\n",
r: "\r",
t: "\t"
};
return str.replace(/\\(u[0-9a-fA-F]{4}|[^u])/, (_, escape) => {
let type = escape.charAt(0);
let hex = escape.slice(1);
if (type === "u") return String.fromCharCode(parseInt(hex, 16));
if (escapes.hasOwnProperty(type)) return escapes[type];
return type;
});
}
const lang = parsimmon.createLanguage({
ComparisonOperator: function(r) {
return parsimmon
.alt(
parsimmon.string(">="),
parsimmon.string("<>"),
parsimmon.string("<="),
parsimmon.string("="),
parsimmon.string(">"),
parsimmon.string("<")
)
.skip(r._);
},
IsNullOperator: function(r) {
return parsimmon
.alt(
parsimmon
.regexp(/is\s+null/i)
.result("IS NULL")
.desc("IS NULL"),
parsimmon
.regexp(/is\s+not\s+null/i)
.result("IS NOT NULL")
.desc("IS NOT NULL")
)
.skip(r._);
},
NotOperator: function(r) {
return parsimmon
.regexp(/not/i)
.result("NOT")
.skip(r._)
.desc("NOT");
},
AndOperator: function(r) {
return parsimmon
.regexp(/and/i)
.result("AND")
.skip(r._)
.desc("AND");
},
OrOperator: function(r) {
return parsimmon
.regexp(/or/i)
.result("OR")
.skip(r._)
.desc("OR");
},
Parameter: function(r) {
return parsimmon
.regexp(/[a-zA-Z0-9_.*]+/)
.skip(r._)
.desc("parameter");
},
StringValueSql: function(r) {
return parsimmon
.regexp(/'([^']*)'/, 1)
.atLeast(1)
.skip(r._)
.map(s => s.join("'"))
.desc("string");
},
StringValueJs: function(r) {
return parsimmon
.regexp(/"((?:\\.|.)*?)"/, 1)
.skip(r._)
.map(interpretEscapes)
.desc("string");
},
NumberValue: function(r) {
return parsimmon
.regexp(/-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/)
.skip(r._)
.map(Number)
.desc("number");
},
FuncValue: function(r) {
return parsimmon.seqMap(
parsimmon
.regexp(/([a-zA-Z0-0_]+)/, 1)
.skip(r._)
.desc("function"),
r.Value.sepBy(parsimmon.string(",").skip(r._)).wrap(
parsimmon.string("(").skip(r._),
parsimmon.string(")").skip(r._)
),
(f, args) => ["FUNC", f.toUpperCase()].concat(args)
);
},
Value: function(r) {
return parsimmon.alt(
r.NumberValue,
r.StringValueSql,
r.StringValueJs,
r.FuncValue
);
},
Comparison: function(r) {
return parsimmon.alt(
parsimmon.seqMap(r.Parameter, r.IsNullOperator, (p, o) => [o, p]),
parsimmon.seqMap(
r.Parameter,
r.ComparisonOperator,
r.Value,
(p, o, v) => [o, p, v]
)
);
},
Expression: function(r) {
function binaryLeft(operatorsParser, nextParser) {
return parsimmon.seqMap(
nextParser,
parsimmon.seq(operatorsParser, nextParser).many(),
(first, rest) =>
rest.reduce((acc, ch) => {
let [op, another] = ch;
if (op === acc[0]) return acc.concat([another]);
if (op === another[0]) return [op, acc].concat(another.slice(1));
return [op, acc, another];
}, first)
);
}
function unary(operatorsParser, nextParser) {
return parsimmon.seq(operatorsParser, nextParser).or(nextParser);
}
return binaryLeft(
r.OrOperator,
binaryLeft(
r.AndOperator,
unary(
r.NotOperator,
r.Comparison.or(
r.Expression.wrap(
parsimmon.string("("),
parsimmon.string(")").skip(r._)
)
)
)
)
).trim(r._);
},
_: function() {
return parsimmon.optWhitespace;
}
});
function parse(filter) {
return lang.Expression.tryParse(filter);
}
function stringify(filter) {
function value(v) {
if (Array.isArray(v) && v[0].toUpperCase() === "FUNC")
return `${v[1]}(${v
.slice(2)
.map(e => value(e))
.join(", ")})`;
return JSON.stringify(v);
}
function expressions(v) {
return v.map(e => {
let str = stringify(e);
if (e[0] === "AND" || e[0] === "OR") return `(${str})`;
else return str;
});
}
const op = filter[0].toUpperCase();
if (["AND", "OR"].includes(op))
return expressions(filter.slice(1)).join(` ${op} `);
else if (op === "NOT") return `NOT ${expressions(filter.slice(1))[0]}`;
else if ([">=", "<>", "<=", "=", ">", "<"].includes(op))
return `${filter[1]} ${op} ${value(filter[2])}`;
else if (["IS NULL", "IS NOT NULL"].includes(op)) return `${filter[1]} ${op}`;
else throw new Error("Unrecognized operator");
}
exports.parse = parse;
exports.stringify = stringify;