Refactor expression to Mongo query translation

The previous approach was simplistic and could result in inefficient queries
in certain edge cases, notably causing MongoDB to not utilize existing indexes
when querying by last inform timestamps given upper and lower bounds. The new
implementation leverages our existing expression minimizer code and can
therefore generate more efficient queries or at least queries that can take
advantage of existing indexes.
This commit is contained in:
Zaid Humaimidi committed 2023-08-09 19:48:49 -07:00
1 parent 947f96299d
commit 3169e829e7
37 files changed
+2175 -1632

No files matched your search

+1 -1
View File
@@ -20,7 +20,7 @@
import { ClosureComponent, Component } from "mithril";
import { m } from "../components";
import * as taskQueue from "../task-queue";
import { parse } from "../../lib/common/expression-parser";
import { parse } from "../../lib/common/expression/parser";
import memoize from "../../lib/common/memoize";
import { getIcon } from "../icons";
import { evaluateExpression } from "../store";
+1 -1
View File
@@ -18,7 +18,7 @@
*/
import { Attributes, ClosureComponent, Component } from "mithril";
import { map } from "../../lib/common/expression-parser";
import { map } from "../../lib/common/expression/parser";
import memoize from "../../lib/common/memoize";
import { Expression } from "../../lib/types";
import { m } from "../components";
+1 -1
View File
@@ -21,7 +21,7 @@ import { ClosureComponent, Component } from "mithril";
import { m } from "../components";
import * as taskQueue from "../task-queue";
import { evaluateExpression } from "../store";
import * as expressionParser from "../../lib/common/expression-parser";
import * as expressionParser from "../../lib/common/expression/parser";
import { getIcon } from "../icons";
const component: ClosureComponent = (): Component => {
+2 -2
View File
@@ -21,14 +21,14 @@ import { ClosureComponent, Component, VnodeDOM } from "mithril";
import { m } from "../components";
import * as taskQueue from "../task-queue";
import * as store from "../store";
import * as expression from "../../lib/common/expression";
import { evaluate } from "../../lib/common/expression/util";
import memoize from "../../lib/common/memoize";
import timeAgo from "../timeago";
import { getIcon } from "../icons";
const evaluateParam = memoize((exp, obj, now: number) => {
let timestamp = now;
exp = expression.evaluate(exp, null, now, (e) => {
exp = evaluate(exp, null, now, (e) => {
if (!Array.isArray(e)) return e;
for (let i = 1; i < e.length; ++i) {
if (
+1 -1
View File
@@ -23,7 +23,7 @@ import * as notifications from "./notifications";
import putFormComponent from "./put-form-component";
import uiConfigComponent from "./ui-config-component";
import * as overlay from "./overlay";
import { parse, stringify } from "../lib/common/expression-parser";
import { parse, stringify } from "../lib/common/expression/parser";
import { loadCodeMirror, loadYaml } from "./dynamic-loader";
import { ClosureComponent, Component, Children } from "mithril";
import { getIcon } from "./icons";
+3 -4
View File
@@ -25,11 +25,10 @@ import filterComponent from "./filter-component";
import * as store from "./store";
import { queueTask, stageDownload } from "./task-queue";
import * as notifications from "./notifications";
import { parse, stringify } from "../lib/common/expression-parser";
import { evaluate, extractParams } from "../lib/common/expression";
import { parse, stringify, map } from "../lib/common/expression/parser";
import { evaluate, extractParams } from "../lib/common/expression/util";
import memoize from "../lib/common/memoize";
import * as smartQuery from "./smart-query";
import * as expressionParser from "../lib/common/expression-parser";
const PAGE_SIZE = config.ui.pageSize || 10;
@@ -54,7 +53,7 @@ const getDownloadUrl = memoize((filter, indexParameters) => {
});
const unpackSmartQuery = memoize((query) => {
return expressionParser.map(query, (e) => {
return map(query, (e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
return smartQuery.unpack("devices", e[2], e[3]);
return e;
+1 -1
View File
@@ -26,7 +26,7 @@ import * as store from "./store";
import * as notifications from "./notifications";
import memoize from "../lib/common/memoize";
import * as smartQuery from "./smart-query";
import { map, parse, stringify } from "../lib/common/expression-parser";
import { map, parse, stringify } from "../lib/common/expression/parser";
import { stringify as yamlStringify } from "../lib/common/yaml";
const PAGE_SIZE = config.ui.pageSize || 10;
+1 -1
View File
@@ -28,7 +28,7 @@ import putFormComponent from "./put-form-component";
import indexTableComponent from "./index-table-component";
import * as overlay from "./overlay";
import * as smartQuery from "./smart-query";
import { map, parse, stringify } from "../lib/common/expression-parser";
import { map, parse, stringify } from "../lib/common/expression/parser";
const PAGE_SIZE = config.ui.pageSize || 10;
+11 -22
View File
@@ -18,13 +18,12 @@
*/
import m, { ClosureComponent } from "mithril";
import { parse, stringify, map } from "../lib/common/expression-parser";
import { parse, stringify, map } from "../lib/common/expression/parser";
import memoize from "../lib/common/memoize";
import Autocomplete from "./autocomplete-compnent";
import * as smartQuery from "./smart-query";
import { filterToMongoQuery } from "../lib/mongodb-functions";
import { validQuery } from "../lib/mongodb-functions";
import { Expression } from "../lib/types";
import { normalize } from "../lib/common/boolean-expression";
const getAutocomplete = memoize((resource) => {
const labels = smartQuery.getLabels(resource);
@@ -42,16 +41,6 @@ const getAutocomplete = memoize((resource) => {
return autocomplete;
});
function validateQuery(q: Expression): void {
q = normalize(q);
if (Array.isArray(q) && q[0] === "CASE") {
q = q.slice(1).filter((a) => Array.isArray(a));
if (q.length > 1) q = ["AND", ...q];
else q = q[0];
}
if (Array.isArray(q)) filterToMongoQuery(q);
}
function parseFilter(resource, f): Expression {
let exp;
if (/^[\s0-9a-zA-Z]+:/.test(f)) {
@@ -62,16 +51,16 @@ function parseFilter(resource, f): Expression {
exp = parse(f);
}
const unpacked = map(exp, (e) => {
if (Array.isArray(e) && e[0] === "FUNC") {
if (e[1] === "Q") return smartQuery.unpack(resource, e[2], e[3]);
else if (e[1] === "NOW") return Date.now();
}
return e;
});
// Throws exception if invalid Mongo query
validateQuery(
map(exp, (e) => {
if (Array.isArray(e) && e[0] === "FUNC") {
if (e[1] === "Q") return smartQuery.unpack(resource, e[2], e[3]);
else if (e[1] === "NOW") return Date.now();
}
return e;
})
);
validQuery(unpacked, resource);
return exp;
}
+1 -1
View File
@@ -26,7 +26,7 @@ import putFormComponent from "./put-form-component";
import indexTableComponent from "./index-table-component";
import * as overlay from "./overlay";
import * as smartQuery from "./smart-query";
import { map, parse, stringify } from "../lib/common/expression-parser";
import { map, parse, stringify } from "../lib/common/expression/parser";
import filterComponent from "./filter-component";
import { Children, ClosureComponent, Component } from "mithril";
import { getIcon } from "./icons";
+1 -1
View File
@@ -19,7 +19,7 @@
import { ClosureComponent, Component, Children } from "mithril";
import { m } from "./components";
import { stringify } from "../lib/common/expression-parser";
import { stringify } from "../lib/common/expression/parser";
import memoize from "../lib/common/memoize";
const memoizedStringify = memoize(stringify);
+1 -1
View File
@@ -28,7 +28,7 @@ import putFormComponent from "./put-form-component";
import indexTableComponent from "./index-table-component";
import memoize from "../lib/common/memoize";
import * as smartQuery from "./smart-query";
import { map, parse, stringify } from "../lib/common/expression-parser";
import { map, parse, stringify } from "../lib/common/expression/parser";
const PAGE_SIZE = config.ui.pageSize || 10;
+1 -1
View File
@@ -28,7 +28,7 @@ import putFormComponent from "./put-form-component";
import indexTableComponent from "./index-table-component";
import * as overlay from "./overlay";
import * as smartQuery from "./smart-query";
import { map, parse, stringify } from "../lib/common/expression-parser";
import { map, parse, stringify } from "../lib/common/expression/parser";
import { loadCodeMirror } from "./dynamic-loader";
const PAGE_SIZE = config.ui.pageSize || 10;
+3 -3
View File
@@ -18,15 +18,15 @@
*/
import m from "mithril";
import { stringify } from "../lib/common/expression-parser";
import { or, and, evaluate } from "../lib/common/expression";
import { stringify } from "../lib/common/expression/parser";
import { or, and, evaluate } from "../lib/common/expression/util";
import memoize from "../lib/common/memoize";
import { QueryOptions, Expression } from "../lib/types";
import * as notifications from "./notifications";
import { configSnapshot, genieacsVersion } from "./config";
import { QueueTask } from "./task-queue";
import { PingResult } from "../lib/ping";
import { unionDiff, covers } from "../lib/common/boolean-expression";
import { unionDiff, covers } from "../lib/common/expression/synth";
const memoizedStringify = memoize(stringify);
const memoizedEvaluate = memoize(evaluate);
+1 -1
View File
@@ -23,7 +23,7 @@ import * as store from "./store";
import { yaml } from "./dynamic-loader";
import * as configFunctions from "./config-functions";
import codeEditorComponent from "./code-editor-component";
import { parse } from "../lib/common/expression-parser";
import { parse } from "../lib/common/expression/parser";
function putActionHandler(prefix: string[], dataYaml: string): Promise<any> {
return new Promise((resolve, reject) => {
+1 -1
View File
@@ -26,7 +26,7 @@ import putFormComponent from "./put-form-component";
import indexTableComponent from "./index-table-component";
import * as overlay from "./overlay";
import * as smartQuery from "./smart-query";
import { map, parse, stringify } from "../lib/common/expression-parser";
import { map, parse, stringify } from "../lib/common/expression/parser";
import filterComponent from "./filter-component";
import changePasswordComponent from "./change-password-component";
import { Children, ClosureComponent, Component } from "mithril";
+1 -1
View File
@@ -28,7 +28,7 @@ import putFormComponent from "./put-form-component";
import indexTableComponent from "./index-table-component";
import * as overlay from "./overlay";
import * as smartQuery from "./smart-query";
import { map, parse, stringify } from "../lib/common/expression-parser";
import { map, parse, stringify } from "../lib/common/expression/parser";
import { loadCodeMirror } from "./dynamic-loader";
const PAGE_SIZE = config.ui.pageSize || 10;