refactor: replace array-based expressions with typed class hierarchy
Replace the old Lisp-like array expression representation (["AND", ["PARAM", ...], ...]) with a class hierarchy rooted in an abstract Expression class (Literal, Parameter, Binary, Unary, FunctionCall, Conditional). This gives compile-time type safety to expression construction, evaluation, and pattern matching. Key changes: - New lib/common/expression.ts defines the class hierarchy with map/evaluate/toString methods - Rewrite parser as hand-rolled recursive descent, removing the parsimmon dependency - Extend Path with a colon separator to unify parameter identity and attributes into a single address space (e.g. "Device.WiFi.SSID.1.Name:writable"). The colon field tracks how many trailing segments are attribute qualifiers. PathSet rewritten with separate param/attr segment indexes - Flatten device attribute representation in the UI to colon-delimited keys mirroring the new path syntax
This commit is contained in:
1 parent
75908e624f
commit
6f3ce6f7c8
71 files changed
+4045
-3449
No files matched your search
+6
-9
@@ -2,13 +2,13 @@ import test from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { EJSON } from "bson";
|
||||
import { Filter } from "mongodb";
|
||||
import { stringify, parse } from "../lib/common/expression/parser.ts";
|
||||
import Expression from "../lib/common/expression.ts";
|
||||
import { convertOldPrecondition } from "../lib/db/util.ts";
|
||||
import { toMongoQuery } from "../lib/db/synth.ts";
|
||||
|
||||
void test("convertOldPrecondition", () => {
|
||||
const tests = [
|
||||
[{}, "true"],
|
||||
[{}, "TRUE"],
|
||||
[{ test: "test" }, 'test = "test"'],
|
||||
[{ test: { $eq: "test" } }, 'test = "test"'],
|
||||
[{ test: { $ne: "test" } }, 'test <> "test" OR test IS NULL'],
|
||||
@@ -31,7 +31,7 @@ void test("convertOldPrecondition", () => {
|
||||
[{ test: "test", test2: "test2" }, 'test = "test" AND test2 = "test2"'],
|
||||
[
|
||||
{ $or: [{ test: "test" }, { test: { $ne: "test" } }] },
|
||||
'test = "test" OR (test <> "test" OR test IS NULL)',
|
||||
'test = "test" OR test <> "test" OR test IS NULL',
|
||||
],
|
||||
[
|
||||
{ test: { $gte: "test1", $ne: "test2" } },
|
||||
@@ -54,7 +54,7 @@ void test("convertOldPrecondition", () => {
|
||||
|
||||
for (const t of tests) {
|
||||
assert.strictEqual(
|
||||
stringify(convertOldPrecondition(t[0] as Record<string, unknown>)),
|
||||
convertOldPrecondition(t[0] as Record<string, unknown>).toString(),
|
||||
t[1],
|
||||
);
|
||||
}
|
||||
@@ -86,7 +86,6 @@ void test("toMongoQuery", async () => {
|
||||
"Param1 <> 1657844103524",
|
||||
{
|
||||
"Param1._value": { $ne: 1657844103524 },
|
||||
|
||||
$and: [
|
||||
{
|
||||
"Param1._value": { $ne: { $date: "2022-07-15T00:15:03.524Z" } },
|
||||
@@ -114,7 +113,6 @@ void test("toMongoQuery", async () => {
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
"LOWER(Param1) LIKE 'value'",
|
||||
{
|
||||
@@ -146,7 +144,7 @@ void test("toMongoQuery", async () => {
|
||||
];
|
||||
|
||||
for (const [expStr, expect] of queries) {
|
||||
const exp = parse(expStr);
|
||||
const exp = Expression.parse(expStr);
|
||||
let query = toMongoQuery(exp, "devices");
|
||||
if (query) query = EJSON.serialize(query);
|
||||
assert.deepStrictEqual(query, expect);
|
||||
@@ -156,11 +154,10 @@ void test("toMongoQuery", async () => {
|
||||
["Param1 = Param2", "Right-hand operand must be a literal value"],
|
||||
["Param1 LIKE Param2", "Right-hand operand of 'LIKE' must be a string"],
|
||||
["NOW() = 1", "Left-hand operand must be a parameter"],
|
||||
["param{param2} = 1", "Left-hand operand must be a parameter"],
|
||||
];
|
||||
|
||||
for (const [expStr, err] of failQueries) {
|
||||
const exp = parse(expStr);
|
||||
const exp = Expression.parse(expStr);
|
||||
assert.throws(() => toMongoQuery(exp, "devices"), {
|
||||
message: err,
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user