Implement query sorting

This commit is contained in:
Zaid Abdulla committed 2018-12-26 00:22:07 -08:00
1 parent db900622a0
commit b77f98a8cb
6 files changed
+151 -44

No files matched your search

+1 -1
View File
@@ -288,7 +288,7 @@ const component = {
let devs = store.fetch( let devs = store.fetch(
"devices", "devices",
vnode.attrs.filter == null ? true : vnode.attrs.filter, vnode.attrs.filter == null ? true : vnode.attrs.filter,
vnode.state.showCount || 10 { limit: vnode.state.showCount || 10 }
); );
let count = store.count( let count = store.count(
"devices", "devices",
+1 -1
View File
@@ -160,7 +160,7 @@ const component = {
let faults = store.fetch( let faults = store.fetch(
"faults", "faults",
vnode.attrs.filter == null ? true : vnode.attrs.filter, vnode.attrs.filter == null ? true : vnode.attrs.filter,
vnode.state.showCount || 10 { limit: vnode.state.showCount || 10 }
); );
let count = store.count( let count = store.count(
"faults", "faults",
+132 -39
View File
@@ -13,11 +13,11 @@ let evaluateExpressionCache = new WeakMap();
const queries = { const queries = {
filter: new WeakMap(), filter: new WeakMap(),
last: new WeakMap(), bookmark: new WeakMap(),
limit: new WeakMap(), limit: new WeakMap(),
sort: new WeakMap(),
fulfilled: new WeakMap(), fulfilled: new WeakMap(),
fulfilling: new WeakSet(), fulfilling: new WeakSet(),
mapper: new WeakMap(),
accessed: new WeakMap(), accessed: new WeakMap(),
value: new WeakMap() value: new WeakMap()
}; };
@@ -39,11 +39,7 @@ class QueryResponse {
get value() { get value() {
queries.accessed.set(this, Date.now()); queries.accessed.set(this, Date.now());
const mapper = queries.mapper.get(this); return queries.value.get(this);
const value = queries.value.get(this);
if (mapper) return mapper(queries.value.get(this));
else return value;
} }
} }
@@ -69,13 +65,89 @@ function count(resourceType, filter) {
return queryResponse; return queryResponse;
} }
function limitFilter(resourceType, filter, last) { function limitFilter(filter, sort, bookmark) {
if (resourceType === "devices") const sortSort = (a, b) => Math.abs(b[1]) - Math.abs(a[1]);
return expression.and(filter, ["<=", ["PARAM", "DeviceID.ID"], last]); const arr = Object.entries(sort)
else return expression.and(filter, ["<=", ["PARAM", "_id"], last]); .sort(sortSort)
.reverse();
return expression.and(
filter,
arr.reduce((cur, kv) => {
const [param, asc] = kv;
if (asc <= 0) {
if (bookmark[param] == null)
return expression.or(
["IS NOT NULL", ["PARAM", param]],
expression.and(["IS NULL", ["PARAM", param]], cur)
);
let f = null;
if (typeof bookmark[param] !== "string")
f = expression.or(f, [">=", ["PARAM", param], ""]);
f = expression.or(f, [">", ["PARAM", param], bookmark[param]]);
return expression.or(
f,
expression.and(["=", ["PARAM", param], bookmark[param]], cur)
);
} else {
let f = ["IS NULL", ["PARAM", param]];
if (bookmark[param] == null) return expression.and(f, cur);
if (typeof bookmark[param] !== "number") {
f = expression.or(f, [">=", ["PARAM", param], 0]);
f = expression.or(f, ["<", ["PARAM", param], 0]);
}
f = expression.or(f, ["<", ["PARAM", param], bookmark[param]]);
return expression.or(
f,
expression.and(["=", ["PARAM", param], bookmark[param]], cur)
);
}
}, true)
);
} }
function findMatches(resourceType, filter, limit) { function compareFunction(sort) {
const sortEntries = Object.entries(sort).sort(
(a, b) => Math.abs(b[1]) - Math.abs(a[1])
);
return (a, b) => {
for (const [param, asc] of sortEntries) {
let v1 = a[param];
let v2 = b[param];
if (v1 != null && typeof v1 === "object")
if (v1.value) v1 = v1.value[0];
else v1 = null;
if (v2 != null && typeof v2 === "object")
if (v2.value) v2 = v2.value[0];
else v2 = null;
if (v1 > v2) {
return asc;
} else if (v1 < v2) {
return asc * -1;
} else if (v1 !== v2) {
const w = {
null: 1,
number: 2,
string: 3
};
const w1 = w[v1 == null ? "null" : typeof v1] || 4;
const w2 = w[v2 == null ? "null" : typeof v2] || 4;
return Math.max(-1, Math.min(1, w1 - w2)) * asc;
}
}
return 0;
};
}
function findMatches(resourceType, filter, sort, limit) {
// Handle "tag =" and "tag <>" special cases // Handle "tag =" and "tag <>" special cases
if (resourceType === "devices") if (resourceType === "devices")
filter = expressionParser.map(filter, e => { filter = expressionParser.map(filter, e => {
@@ -91,11 +163,11 @@ function findMatches(resourceType, filter, limit) {
}); });
let value = []; let value = [];
for (let [id, obj] of resources[resourceType].objects.entries()) for (let obj of resources[resourceType].objects.values())
if (expression.evaluate(filter, obj, fulfillTimestamp)) if (expression.evaluate(filter, obj, fulfillTimestamp))
value.push(id); value.push(obj);
value = value.sort(); value = value.sort(compareFunction(sort));
if (limit) value = value.slice(0, limit); if (limit) value = value.slice(0, limit);
return value; return value;
@@ -105,9 +177,10 @@ function inferQuery(resourceType, queryResponse) {
const limit = queries.limit.get(queryResponse); const limit = queries.limit.get(queryResponse);
let filter = queries.filter.get(queryResponse); let filter = queries.filter.get(queryResponse);
filter = unpackExpression(filter); filter = unpackExpression(filter);
let last = queries.last.get(queryResponse); let bookmark = queries.bookmark.get(queryResponse);
if (last || !limit) { const sort = queries.sort.get(queryResponse);
if (last) filter = limitFilter(resourceType, filter, last); if (bookmark || !limit) {
if (bookmark) filter = limitFilter(filter, sort, bookmark);
if ( if (
resources[resourceType].combinedFilter && resources[resourceType].combinedFilter &&
expression.subset(filter, resources[resourceType].combinedFilter) expression.subset(filter, resources[resourceType].combinedFilter)
@@ -115,12 +188,21 @@ function inferQuery(resourceType, queryResponse) {
queries.fulfilled.set(queryResponse, fulfillTimestamp); queries.fulfilled.set(queryResponse, fulfillTimestamp);
} }
queries.value.set(queryResponse, findMatches(resourceType, filter, limit)); queries.value.set(
queryResponse,
findMatches(resourceType, filter, sort, limit)
);
} }
function fetch(resourceType, filter, limit = 0) { function fetch(resourceType, filter, options = {}) {
const filterStr = funcCache.get(expression.stringify, filter); const filterStr = funcCache.get(expression.stringify, filter);
const key = `${limit}:${filterStr}`; const sort = options.sort || {};
const limit = options.limit || 0;
if (resourceType === "devices")
sort["DeviceID.ID"] = sort["DeviceID.ID"] || 1;
else sort["_id"] = sort["_id"] || 1;
let key = `${filterStr}:${limit}:${JSON.stringify(sort)}`;
let queryResponse = resources[resourceType].fetch.get(key); let queryResponse = resources[resourceType].fetch.get(key);
if (queryResponse) return queryResponse; if (queryResponse) return queryResponse;
@@ -128,9 +210,7 @@ function fetch(resourceType, filter, limit = 0) {
resources[resourceType].fetch.set(key, queryResponse); resources[resourceType].fetch.set(key, queryResponse);
queries.filter.set(queryResponse, filter); queries.filter.set(queryResponse, filter);
queries.limit.set(queryResponse, limit); queries.limit.set(queryResponse, limit);
queries.mapper.set(queryResponse, list => queries.sort.set(queryResponse, sort);
list.map(x => resources[resourceType].objects.get(x))
);
inferQuery(resourceType, queryResponse); inferQuery(resourceType, queryResponse);
return queryResponse; return queryResponse;
} }
@@ -204,6 +284,7 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
toFetchAll[resourceType] = toFetchAll[resourceType] || []; toFetchAll[resourceType] = toFetchAll[resourceType] || [];
toFetchAll[resourceType].push(queryResponse); toFetchAll[resourceType].push(queryResponse);
const limit = queries.limit.get(queryResponse); const limit = queries.limit.get(queryResponse);
const sort = queries.sort.get(queryResponse);
if (limit) if (limit)
allPromises.push( allPromises.push(
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
@@ -219,18 +300,26 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
filter: funcCache.get(expression.stringify, filter), filter: funcCache.get(expression.stringify, filter),
limit: 1, limit: 1,
skip: limit - 1, skip: limit - 1,
projection: "_id" sort: JSON.stringify(sort),
projection: Object.keys(sort).join(",")
}) })
}) })
.then(res => { .then(res => {
if (res.length) if (res.length) {
if (resourceType === "devices") // Generate bookmark object
queries.last.set( let bm = Object.keys(sort).reduce((b, k) => {
queryResponse, if (res[0][k] != null)
res[0]["DeviceID.ID"].value[0] if (typeof res[0][k] === "object") {
); if (res[0][k].value != null)
else queries.last.set(queryResponse, res[0]["_id"]); b[k] = res[0][k].value[0];
else queries.last.delete(queryResponse); } else b[k] = res[0][k];
return b;
}, {});
queries.bookmark.set(queryResponse, bm);
} else {
queries.bookmark.delete(queryResponse);
}
resolve(); resolve();
}) })
.catch(reject); .catch(reject);
@@ -249,8 +338,10 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
toFetch = toFetch.filter(queryResponse => { toFetch = toFetch.filter(queryResponse => {
let filter = queries.filter.get(queryResponse); let filter = queries.filter.get(queryResponse);
filter = unpackExpression(filter); filter = unpackExpression(filter);
let last = queries.last.get(queryResponse); const limit = queries.limit.get(queryResponse);
if (last) filter = limitFilter(resourceType, filter, last); const bookmark = queries.bookmark.get(queryResponse);
const sort = queries.sort.get(queryResponse);
if (bookmark) filter = limitFilter(filter, sort, bookmark);
if ( if (
resources[resourceType].combinedFilter && resources[resourceType].combinedFilter &&
@@ -258,7 +349,7 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
) { ) {
queries.value.set( queries.value.set(
queryResponse, queryResponse,
findMatches(resourceType, filter) findMatches(resourceType, filter, sort, limit)
); );
queries.fulfilled.set(queryResponse, fulfillTimestamp); queries.fulfilled.set(queryResponse, fulfillTimestamp);
queries.fulfilling.delete(queryResponse); queries.fulfilling.delete(queryResponse);
@@ -325,12 +416,14 @@ function fulfill(accessTimestamp, _fulfillTimestamp) {
for (let queryResponse of toFetch) { for (let queryResponse of toFetch) {
let filter = queries.filter.get(queryResponse); let filter = queries.filter.get(queryResponse);
filter = unpackExpression(filter); filter = unpackExpression(filter);
let last = queries.last.get(queryResponse); const limit = queries.limit.get(queryResponse);
if (last) filter = limitFilter(resourceType, filter, last); const bookmark = queries.bookmark.get(queryResponse);
const sort = queries.sort.get(queryResponse);
if (bookmark) filter = limitFilter(filter, sort, bookmark);
queries.value.set( queries.value.set(
queryResponse, queryResponse,
findMatches(resourceType, filter) findMatches(resourceType, filter, sort, limit)
); );
queries.fulfilled.set(queryResponse, fulfillTimestamp); queries.fulfilled.set(queryResponse, fulfillTimestamp);
queries.fulfilling.delete(queryResponse); queries.fulfilling.delete(queryResponse);
+2 -2
View File
@@ -48,7 +48,7 @@ function booleanCnf(exp) {
let m = mutuallyExclusive.get(lhs); let m = mutuallyExclusive.get(lhs);
if (!m) mutuallyExclusive.set(lhs, (m = new Set())); if (!m) mutuallyExclusive.set(lhs, (m = new Set()));
m.add(f); m.add(f);
if (rhs || key !== "null") m.add(getVariable(lhs, "null")); if (rhs || op !== "null") m.add(getVariable(lhs, "null"));
} }
return f; return f;
} }
@@ -66,7 +66,7 @@ function booleanCnf(exp) {
for (let i = 1; i < range.length; ++i) for (let i = 1; i < range.length; ++i)
if ((from == null || range[i] > from) && (to == null || range[i] <= to)) { if ((from == null || range[i] > from) && (to == null || range[i] <= to)) {
vars.push(`${lhs}:${range[i - 1]}`); vars.push(`${lhs}:${range[i - 1]}`);
vars.push(`${lhs}:${range[i]}`); if (range[i] !== to) vars.push(`${lhs}:${range[i]}`);
vars.push(`${lhs}:${range[i - 1]}--${range[i]}`); vars.push(`${lhs}:${range[i - 1]}--${range[i]}`);
} }
+2
View File
@@ -58,6 +58,8 @@ for (let [resource, flags] of Object.entries(resources)) {
filter = expression.parse(ctx.request.query.filter); filter = expression.parse(ctx.request.query.filter);
if (ctx.request.query.limit) options.limit = +ctx.request.query.limit; if (ctx.request.query.limit) options.limit = +ctx.request.query.limit;
if (ctx.request.query.skip) options.skip = +ctx.request.query.skip; if (ctx.request.query.skip) options.skip = +ctx.request.query.skip;
if (ctx.request.query.sort)
options.sort = JSON.parse(ctx.request.query.sort);
if (ctx.request.query.projection) if (ctx.request.query.projection)
options.projection = ctx.request.query.projection options.projection = ctx.request.query.projection
.split(",") .split(",")
+13 -1
View File
@@ -56,7 +56,19 @@ function query(resource, filter, options, callback) {
); );
if (options.skip) cursor = cursor.skip(options.skip); if (options.skip) cursor = cursor.skip(options.skip);
if (options.limit) cursor = cursor.limit(options.limit); if (options.limit) cursor = cursor.limit(options.limit);
cursor = cursor.sort({ _id: 1 });
if (options.sort) {
let s = Object.entries(options.sort)
.sort((a, b) => Math.abs(b[1]) - Math.abs(a[1]))
.reduce(
(obj, [k, v]) =>
Object.assign(obj, { [k]: Math.min(Math.max(v, -1), 1) }),
{}
);
if (resource === "devices") s = mongodbFunctions.processDeviceSort(s);
cursor = cursor.sort(s);
}
if (!callback) if (!callback)
cursor.toArray((err, docs) => { cursor.toArray((err, docs) => {