simple-graph-query 2.0.2 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/simple-graph-query.bundle.js +134 -52
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare class SimpleGraphQueryEvaluator {
|
|
|
11
11
|
datum: IDataInstance;
|
|
12
12
|
forgeListener: ForgeListenerImpl;
|
|
13
13
|
walker: ParseTreeWalker;
|
|
14
|
+
private parseTreeCache;
|
|
14
15
|
constructor(datum: IDataInstance);
|
|
15
16
|
getExpressionParseTree(forgeExpr: string): import("./forge-antlr/ForgeParser").ParseExprContext;
|
|
16
17
|
evaluateExpression(forgeExpr: string): EvaluationResult;
|
|
@@ -48473,11 +48473,17 @@ function extractNumber(val) {
|
|
|
48473
48473
|
function isString(value) {
|
|
48474
48474
|
return typeof value === "string";
|
|
48475
48475
|
}
|
|
48476
|
+
// Helper to create a string key from a tuple for fast lookup
|
|
48477
|
+
function tupleToKey(tuple) {
|
|
48478
|
+
return JSON.stringify(tuple);
|
|
48479
|
+
}
|
|
48476
48480
|
function areTuplesEqual(a, b) {
|
|
48477
48481
|
return a.length === b.length && a.every((val, i) => val === b[i]);
|
|
48478
48482
|
}
|
|
48479
48483
|
function isTupleArraySubset(a, b) {
|
|
48480
|
-
|
|
48484
|
+
// Optimize using Set for O(n) lookup instead of O(n²)
|
|
48485
|
+
const bSet = new Set(b.map(tupleToKey));
|
|
48486
|
+
return a.every((tupleA) => bSet.has(tupleToKey(tupleA)));
|
|
48481
48487
|
}
|
|
48482
48488
|
function areTupleArraysEqual(a, b) {
|
|
48483
48489
|
if (a.length !== b.length) {
|
|
@@ -48486,9 +48492,13 @@ function areTupleArraysEqual(a, b) {
|
|
|
48486
48492
|
return isTupleArraySubset(a, b) && isTupleArraySubset(b, a);
|
|
48487
48493
|
}
|
|
48488
48494
|
function deduplicateTuples(tuples) {
|
|
48495
|
+
// Optimize using Set for O(n) deduplication instead of O(n²)
|
|
48496
|
+
const seen = new Set();
|
|
48489
48497
|
const result = [];
|
|
48490
48498
|
for (const tuple of tuples) {
|
|
48491
|
-
|
|
48499
|
+
const key = tupleToKey(tuple);
|
|
48500
|
+
if (!seen.has(key)) {
|
|
48501
|
+
seen.add(key);
|
|
48492
48502
|
result.push(tuple);
|
|
48493
48503
|
}
|
|
48494
48504
|
}
|
|
@@ -48497,15 +48507,23 @@ function deduplicateTuples(tuples) {
|
|
|
48497
48507
|
function getCombinations(arrays) {
|
|
48498
48508
|
// first, turn each string[][] into a string[] by flattening
|
|
48499
48509
|
const valueSets = arrays.map((tuple) => tuple.flat());
|
|
48500
|
-
//
|
|
48501
|
-
|
|
48502
|
-
|
|
48503
|
-
|
|
48504
|
-
|
|
48505
|
-
|
|
48506
|
-
|
|
48510
|
+
// Early exit for empty arrays
|
|
48511
|
+
if (valueSets.length === 0)
|
|
48512
|
+
return [[]];
|
|
48513
|
+
if (valueSets.some(arr => arr.length === 0))
|
|
48514
|
+
return [];
|
|
48515
|
+
// Iterative approach for better performance on large cartesian products
|
|
48516
|
+
let result = [[]];
|
|
48517
|
+
for (const valueSet of valueSets) {
|
|
48518
|
+
const newResult = [];
|
|
48519
|
+
for (const existing of result) {
|
|
48520
|
+
for (const value of valueSet) {
|
|
48521
|
+
newResult.push([...existing, value]);
|
|
48522
|
+
}
|
|
48523
|
+
}
|
|
48524
|
+
result = newResult;
|
|
48507
48525
|
}
|
|
48508
|
-
return
|
|
48526
|
+
return result;
|
|
48509
48527
|
}
|
|
48510
48528
|
function transitiveClosure(pairs) {
|
|
48511
48529
|
if (pairs.length === 0)
|
|
@@ -48524,20 +48542,21 @@ function transitiveClosure(pairs) {
|
|
|
48524
48542
|
}
|
|
48525
48543
|
graph.get(from).add(to);
|
|
48526
48544
|
}
|
|
48527
|
-
//
|
|
48545
|
+
// Use more efficient BFS with index-based queue to avoid O(n) shift() operations
|
|
48528
48546
|
// NOTE: we use Set<string> instead of Set<[SingleValue, SingleValue]> since
|
|
48529
48547
|
// TS would compute equality over the object's reference instead of the value
|
|
48530
48548
|
// when the value is an array
|
|
48531
|
-
const
|
|
48549
|
+
const transitiveClosureSet = new Set();
|
|
48532
48550
|
for (const start of graph.keys()) {
|
|
48533
48551
|
const visited = new Set();
|
|
48534
48552
|
const queue = [...(graph.get(start) ?? [])];
|
|
48535
|
-
|
|
48536
|
-
|
|
48553
|
+
let queueIndex = 0; // Use index instead of shift() for O(1) access
|
|
48554
|
+
while (queueIndex < queue.length) {
|
|
48555
|
+
const current = queue[queueIndex++];
|
|
48537
48556
|
if (visited.has(current))
|
|
48538
48557
|
continue;
|
|
48539
48558
|
visited.add(current);
|
|
48540
|
-
|
|
48559
|
+
transitiveClosureSet.add(JSON.stringify([start, current]));
|
|
48541
48560
|
const neighbors = graph.get(current);
|
|
48542
48561
|
if (neighbors) {
|
|
48543
48562
|
for (const neighbor of neighbors) {
|
|
@@ -48549,22 +48568,34 @@ function transitiveClosure(pairs) {
|
|
|
48549
48568
|
}
|
|
48550
48569
|
}
|
|
48551
48570
|
// convert the result back to a Tuple[] and return
|
|
48552
|
-
return Array.from(
|
|
48571
|
+
return Array.from(transitiveClosureSet).map((pair) => JSON.parse(pair));
|
|
48553
48572
|
}
|
|
48554
48573
|
function dotJoin(left, right) {
|
|
48555
48574
|
const leftExpr = isSingleValue(left) ? [[left]] : left;
|
|
48556
48575
|
const rightExpr = isSingleValue(right) ? [[right]] : right;
|
|
48576
|
+
// Optimize join using a Map for O(n+m) instead of O(n*m) lookup
|
|
48577
|
+
// Group right tuples by their first element for fast lookup
|
|
48578
|
+
const rightIndex = new Map();
|
|
48579
|
+
for (const rightTuple of rightExpr) {
|
|
48580
|
+
const key = rightTuple[0];
|
|
48581
|
+
if (!rightIndex.has(key)) {
|
|
48582
|
+
rightIndex.set(key, []);
|
|
48583
|
+
}
|
|
48584
|
+
rightIndex.get(key).push(rightTuple);
|
|
48585
|
+
}
|
|
48557
48586
|
const result = [];
|
|
48558
|
-
|
|
48559
|
-
|
|
48560
|
-
|
|
48587
|
+
for (const leftTuple of leftExpr) {
|
|
48588
|
+
const joinKey = leftTuple[leftTuple.length - 1];
|
|
48589
|
+
const matchingRightTuples = rightIndex.get(joinKey);
|
|
48590
|
+
if (matchingRightTuples) {
|
|
48591
|
+
for (const rightTuple of matchingRightTuples) {
|
|
48561
48592
|
result.push([
|
|
48562
48593
|
...leftTuple.slice(0, leftTuple.length - 1),
|
|
48563
48594
|
...rightTuple.slice(1),
|
|
48564
48595
|
]);
|
|
48565
48596
|
}
|
|
48566
|
-
}
|
|
48567
|
-
}
|
|
48597
|
+
}
|
|
48598
|
+
}
|
|
48568
48599
|
if (result.some(tuple => tuple.length === 0)) {
|
|
48569
48600
|
throw new Error("Join would create a relation of arity 0");
|
|
48570
48601
|
}
|
|
@@ -48628,7 +48659,12 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48628
48659
|
constructFreeVariableKey(freeVarValues) {
|
|
48629
48660
|
const keys = Object.keys(freeVarValues);
|
|
48630
48661
|
keys.sort(); // sort the keys to ensure consistent ordering
|
|
48631
|
-
|
|
48662
|
+
// Use JSON.stringify for complex values to ensure proper serialization
|
|
48663
|
+
return keys.map((key) => {
|
|
48664
|
+
const value = freeVarValues[key];
|
|
48665
|
+
const valueStr = Array.isArray(value) ? JSON.stringify(value) : String(value);
|
|
48666
|
+
return `${key}=${valueStr}`;
|
|
48667
|
+
}).join("|");
|
|
48632
48668
|
}
|
|
48633
48669
|
// helper function to get the label for a value (used for @: operator)
|
|
48634
48670
|
getLabelForValue(value) {
|
|
@@ -48650,11 +48686,23 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48650
48686
|
}
|
|
48651
48687
|
// helper function to get the label as string (used for @: and @str: operators)
|
|
48652
48688
|
getLabelAsString(value) {
|
|
48689
|
+
// Optimization: for primitive types, convert directly
|
|
48690
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
48691
|
+
return String(value);
|
|
48692
|
+
}
|
|
48653
48693
|
const label = this.getLabelForValue(value);
|
|
48654
48694
|
return String(label);
|
|
48655
48695
|
}
|
|
48656
48696
|
// helper function to get the label as boolean (used for @bool: operator)
|
|
48657
48697
|
getLabelAsBoolean(value) {
|
|
48698
|
+
// Optimization: for boolean values, return directly
|
|
48699
|
+
if (typeof value === "boolean") {
|
|
48700
|
+
return value;
|
|
48701
|
+
}
|
|
48702
|
+
// Optimization: for number values, apply rule directly
|
|
48703
|
+
if (typeof value === "number") {
|
|
48704
|
+
return value !== 0;
|
|
48705
|
+
}
|
|
48658
48706
|
const label = this.getLabelForValue(value);
|
|
48659
48707
|
const labelStr = String(label).toLowerCase();
|
|
48660
48708
|
// Convert string representations to boolean
|
|
@@ -48672,6 +48720,10 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48672
48720
|
}
|
|
48673
48721
|
// helper function to get the label as number (used for @num: operator)
|
|
48674
48722
|
getLabelAsNumber(value) {
|
|
48723
|
+
// Optimization: if value is already a number, return it directly
|
|
48724
|
+
if (typeof value === "number") {
|
|
48725
|
+
return value;
|
|
48726
|
+
}
|
|
48675
48727
|
const label = this.getLabelForValue(value);
|
|
48676
48728
|
const labelNum = Number(label);
|
|
48677
48729
|
if (isNaN(labelNum)) {
|
|
@@ -48849,6 +48901,12 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48849
48901
|
const result = [];
|
|
48850
48902
|
let foundTrue = false;
|
|
48851
48903
|
let foundFalse = false;
|
|
48904
|
+
// Optimize: create environment once and reuse it
|
|
48905
|
+
const quantDeclEnv = {
|
|
48906
|
+
env: {},
|
|
48907
|
+
type: "quantDecl",
|
|
48908
|
+
};
|
|
48909
|
+
this.environmentStack.push(quantDeclEnv);
|
|
48852
48910
|
for (let i = 0; i < product.length; i++) {
|
|
48853
48911
|
const tuple = product[i];
|
|
48854
48912
|
if (isDisjoint) {
|
|
@@ -48866,16 +48924,10 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48866
48924
|
continue;
|
|
48867
48925
|
}
|
|
48868
48926
|
}
|
|
48869
|
-
|
|
48870
|
-
env: {},
|
|
48871
|
-
type: "quantDecl",
|
|
48872
|
-
};
|
|
48927
|
+
// Update environment values in place
|
|
48873
48928
|
for (let j = 0; j < varNames.length; j++) {
|
|
48874
|
-
|
|
48875
|
-
const varValue = tuple[j];
|
|
48876
|
-
quantDeclEnv.env[varName] = varValue;
|
|
48929
|
+
quantDeclEnv.env[varNames[j]] = tuple[j];
|
|
48877
48930
|
}
|
|
48878
|
-
this.environmentStack.push(quantDeclEnv);
|
|
48879
48931
|
// now, we want to evaluate the barExpr
|
|
48880
48932
|
const barExprValue = this.visit(barExpr);
|
|
48881
48933
|
if (!isBoolean(barExprValue)) {
|
|
@@ -48888,14 +48940,15 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48888
48940
|
else {
|
|
48889
48941
|
foundFalse = true;
|
|
48890
48942
|
}
|
|
48891
|
-
this.environmentStack.pop();
|
|
48892
48943
|
// short-circuit if possible
|
|
48893
48944
|
if (ctx.quant().ALL_TOK() && foundFalse) {
|
|
48945
|
+
this.environmentStack.pop();
|
|
48894
48946
|
const value = false;
|
|
48895
48947
|
this.cacheResult(ctx, freeVarsKey, value);
|
|
48896
48948
|
return value;
|
|
48897
48949
|
}
|
|
48898
48950
|
if (ctx.quant().NO_TOK() && foundTrue) {
|
|
48951
|
+
this.environmentStack.pop();
|
|
48899
48952
|
const value = false;
|
|
48900
48953
|
this.cacheResult(ctx, freeVarsKey, value);
|
|
48901
48954
|
return value;
|
|
@@ -48903,22 +48956,26 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48903
48956
|
if (ctx.quant().mult()) {
|
|
48904
48957
|
const multExpr = ctx.quant().mult();
|
|
48905
48958
|
if (multExpr.LONE_TOK() && result.length > 1) {
|
|
48959
|
+
this.environmentStack.pop();
|
|
48906
48960
|
const value = false;
|
|
48907
48961
|
this.cacheResult(ctx, freeVarsKey, value);
|
|
48908
48962
|
return value;
|
|
48909
48963
|
}
|
|
48910
48964
|
if (multExpr.SOME_TOK() && foundTrue) {
|
|
48965
|
+
this.environmentStack.pop();
|
|
48911
48966
|
const value = true;
|
|
48912
48967
|
this.cacheResult(ctx, freeVarsKey, value);
|
|
48913
48968
|
return value;
|
|
48914
48969
|
}
|
|
48915
48970
|
if (multExpr.ONE_TOK() && result.length > 1) {
|
|
48971
|
+
this.environmentStack.pop();
|
|
48916
48972
|
const value = false;
|
|
48917
48973
|
this.cacheResult(ctx, freeVarsKey, value);
|
|
48918
48974
|
return value;
|
|
48919
48975
|
}
|
|
48920
48976
|
}
|
|
48921
48977
|
}
|
|
48978
|
+
this.environmentStack.pop();
|
|
48922
48979
|
if (ctx.quant().ALL_TOK()) {
|
|
48923
48980
|
const value = !foundFalse;
|
|
48924
48981
|
this.cacheResult(ctx, freeVarsKey, value);
|
|
@@ -49418,7 +49475,9 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49418
49475
|
return leftChildValue;
|
|
49419
49476
|
}
|
|
49420
49477
|
if (leftChildValue[0].length === rightChildValue[0].length) {
|
|
49421
|
-
|
|
49478
|
+
// Optimize set difference using Set for O(n+m) instead of O(n*m)
|
|
49479
|
+
const rightSet = new Set(rightChildValue.map(tupleToKey));
|
|
49480
|
+
return leftChildValue.filter(tuple => !rightSet.has(tupleToKey(tuple)));
|
|
49422
49481
|
}
|
|
49423
49482
|
}
|
|
49424
49483
|
else {
|
|
@@ -49491,7 +49550,9 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49491
49550
|
return [];
|
|
49492
49551
|
}
|
|
49493
49552
|
if (leftChildValue[0].length === rightChildValue[0].length) {
|
|
49494
|
-
|
|
49553
|
+
// Optimize set intersection using Set for O(n+m) instead of O(n*m)
|
|
49554
|
+
const rightSet = new Set(rightChildValue.map(tupleToKey));
|
|
49555
|
+
return leftChildValue.filter(tuple => rightSet.has(tupleToKey(tuple)));
|
|
49495
49556
|
}
|
|
49496
49557
|
}
|
|
49497
49558
|
else {
|
|
@@ -49708,7 +49769,15 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49708
49769
|
if (isTupleArray(childrenResults)) {
|
|
49709
49770
|
const transitiveClosureResult = transitiveClosure(childrenResults);
|
|
49710
49771
|
const idenResult = this.getIden();
|
|
49711
|
-
|
|
49772
|
+
// Optimize: Use Set for efficient deduplication instead of deduplicateTuples
|
|
49773
|
+
const resultSet = new Set();
|
|
49774
|
+
for (const tuple of idenResult) {
|
|
49775
|
+
resultSet.add(tupleToKey(tuple));
|
|
49776
|
+
}
|
|
49777
|
+
for (const tuple of transitiveClosureResult) {
|
|
49778
|
+
resultSet.add(tupleToKey(tuple));
|
|
49779
|
+
}
|
|
49780
|
+
return Array.from(resultSet).map(key => JSON.parse(key));
|
|
49712
49781
|
}
|
|
49713
49782
|
}
|
|
49714
49783
|
return childrenResults;
|
|
@@ -49833,18 +49902,18 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49833
49902
|
}
|
|
49834
49903
|
const product = getCombinations(quantifiedSets);
|
|
49835
49904
|
const result = [];
|
|
49905
|
+
// Optimize: create environment once and reuse it
|
|
49906
|
+
const quantDeclEnv = {
|
|
49907
|
+
env: {},
|
|
49908
|
+
type: "quantDecl",
|
|
49909
|
+
};
|
|
49910
|
+
this.environmentStack.push(quantDeclEnv);
|
|
49836
49911
|
for (let i = 0; i < product.length; i++) {
|
|
49837
49912
|
const tuple = product[i];
|
|
49838
|
-
|
|
49839
|
-
env: {},
|
|
49840
|
-
type: "quantDecl",
|
|
49841
|
-
};
|
|
49913
|
+
// Update environment values in place
|
|
49842
49914
|
for (let j = 0; j < varNames.length; j++) {
|
|
49843
|
-
|
|
49844
|
-
const varValue = tuple[j];
|
|
49845
|
-
quantDeclEnv.env[varName] = varValue;
|
|
49915
|
+
quantDeclEnv.env[varNames[j]] = tuple[j];
|
|
49846
49916
|
}
|
|
49847
|
-
this.environmentStack.push(quantDeclEnv);
|
|
49848
49917
|
// now, we want to evaluate the barExpr
|
|
49849
49918
|
const barExprValue = this.visit(barExpr);
|
|
49850
49919
|
if (!isBoolean(barExprValue)) {
|
|
@@ -49854,9 +49923,10 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49854
49923
|
// will error if not boolean val, which we want
|
|
49855
49924
|
result.push(tuple);
|
|
49856
49925
|
}
|
|
49857
|
-
this.environmentStack.pop();
|
|
49858
49926
|
}
|
|
49859
|
-
|
|
49927
|
+
this.environmentStack.pop();
|
|
49928
|
+
// Deduplicate results to ensure set semantics
|
|
49929
|
+
return deduplicateTuples(result);
|
|
49860
49930
|
}
|
|
49861
49931
|
if (ctx.LEFT_PAREN_TOK()) {
|
|
49862
49932
|
// NOTE: we just return the result of evaluating the expr that is inside
|
|
@@ -61125,6 +61195,8 @@ class SimpleGraphQueryEvaluator {
|
|
|
61125
61195
|
constructor(datum) {
|
|
61126
61196
|
this.forgeListener = new ForgeListenerImpl_1.ForgeListenerImpl();
|
|
61127
61197
|
this.walker = new ParseTreeWalker_1.ParseTreeWalker();
|
|
61198
|
+
// Cache for parsed expressions to avoid re-parsing the same expression
|
|
61199
|
+
this.parseTreeCache = new Map();
|
|
61128
61200
|
this.datum = datum;
|
|
61129
61201
|
}
|
|
61130
61202
|
getExpressionParseTree(forgeExpr) {
|
|
@@ -61138,18 +61210,28 @@ class SimpleGraphQueryEvaluator {
|
|
|
61138
61210
|
return tree;
|
|
61139
61211
|
}
|
|
61140
61212
|
evaluateExpression(forgeExpr) {
|
|
61141
|
-
|
|
61142
|
-
|
|
61213
|
+
// Check cache first
|
|
61214
|
+
let tree;
|
|
61215
|
+
if (this.parseTreeCache.has(forgeExpr)) {
|
|
61216
|
+
tree = this.parseTreeCache.get(forgeExpr);
|
|
61143
61217
|
}
|
|
61144
|
-
|
|
61145
|
-
//
|
|
61146
|
-
|
|
61147
|
-
|
|
61148
|
-
|
|
61218
|
+
else {
|
|
61219
|
+
try { // now, we can actually evaluate the expression
|
|
61220
|
+
const parsedTree = this.getExpressionParseTree(forgeExpr);
|
|
61221
|
+
tree = parsedTree instanceof ForgeParser_1.ExprContext ? parsedTree : parsedTree.getChild(0);
|
|
61222
|
+
// Cache the parsed tree
|
|
61223
|
+
this.parseTreeCache.set(forgeExpr, tree);
|
|
61224
|
+
}
|
|
61225
|
+
catch (e) {
|
|
61226
|
+
// if we can't parse the expression, we return an error
|
|
61227
|
+
return {
|
|
61228
|
+
error: new Error(`Error parsing expression "${forgeExpr}"`)
|
|
61229
|
+
};
|
|
61230
|
+
}
|
|
61149
61231
|
}
|
|
61150
61232
|
const evaluator = new ForgeExprEvaluator_1.ForgeExprEvaluator(this.datum);
|
|
61151
61233
|
try {
|
|
61152
|
-
let result = evaluator.visit(tree
|
|
61234
|
+
let result = evaluator.visit(tree);
|
|
61153
61235
|
// ensure we're visiting an ExprContext
|
|
61154
61236
|
return result;
|
|
61155
61237
|
}
|
package/package.json
CHANGED