Implement query sorting
This commit is contained in:
1 parent
db900622a0
commit
b77f98a8cb
6 files changed
+151
-44
No files matched your search
@@ -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",
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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]}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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) => {
|
||||||
|
|||||||
Reference in new issue
Block a user