simple-graph-query 2.1.4 → 2.2.0
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.
|
@@ -17,13 +17,17 @@ export declare class ForgeExprEvaluator extends AbstractParseTreeVisitor<EvalRes
|
|
|
17
17
|
private freeVariables;
|
|
18
18
|
private cachedResults;
|
|
19
19
|
private instanceData;
|
|
20
|
+
private relationCache;
|
|
21
|
+
private relationIndexCache;
|
|
20
22
|
constructor(datum: IDataInstance);
|
|
23
|
+
private buildRelationCache;
|
|
21
24
|
private updateFreeVariables;
|
|
22
25
|
private constructFreeVariableKey;
|
|
23
26
|
private getLabelForValue;
|
|
24
27
|
private getLabelAsString;
|
|
25
28
|
private getLabelAsBoolean;
|
|
26
29
|
private getLabelAsNumber;
|
|
30
|
+
private dotJoin;
|
|
27
31
|
private cacheResult;
|
|
28
32
|
private getIden;
|
|
29
33
|
protected aggregateResult(aggregate: EvalResult, nextResult: EvalResult): EvalResult;
|
|
@@ -48570,38 +48570,6 @@ function transitiveClosure(pairs) {
|
|
|
48570
48570
|
// convert the result back to a Tuple[] and return
|
|
48571
48571
|
return Array.from(transitiveClosureSet).map((pair) => JSON.parse(pair));
|
|
48572
48572
|
}
|
|
48573
|
-
function dotJoin(left, right) {
|
|
48574
|
-
const leftExpr = isSingleValue(left) ? [[left]] : left;
|
|
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
|
-
}
|
|
48586
|
-
const result = [];
|
|
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) {
|
|
48592
|
-
result.push([
|
|
48593
|
-
...leftTuple.slice(0, leftTuple.length - 1),
|
|
48594
|
-
...rightTuple.slice(1),
|
|
48595
|
-
]);
|
|
48596
|
-
}
|
|
48597
|
-
}
|
|
48598
|
-
}
|
|
48599
|
-
if (result.some(tuple => tuple.length === 0)) {
|
|
48600
|
-
throw new Error("Join would create a relation of arity 0");
|
|
48601
|
-
}
|
|
48602
|
-
// Deduplicate results to ensure set semantics
|
|
48603
|
-
return deduplicateTuples(result);
|
|
48604
|
-
}
|
|
48605
48573
|
function bitwidthWraparound(value, bitwidth) {
|
|
48606
48574
|
const modulus = Math.pow(2, bitwidth); // total number of Int values
|
|
48607
48575
|
const halfValue = Math.pow(2, bitwidth - 1); // halfway point
|
|
@@ -48632,11 +48600,61 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48632
48600
|
// NOTE: strings will be of the format "<var-name>=<value>|..." sorted in
|
|
48633
48601
|
// increasing lexicographic order of variable names
|
|
48634
48602
|
this.cachedResults = new Map();
|
|
48603
|
+
// Cache for relation lookups to avoid O(n) scans
|
|
48604
|
+
this.relationCache = null;
|
|
48605
|
+
// Index for relations: Map<relationName, Map<firstElement, Tuple[]>>
|
|
48606
|
+
// This allows O(1) lookup for patterns like atom.field
|
|
48607
|
+
this.relationIndexCache = null;
|
|
48635
48608
|
this.instanceData = datum;
|
|
48636
48609
|
this.environmentStack = [];
|
|
48637
48610
|
this.freeVariableFinder = new ForgeExprFreeVariableFinder_1.ForgeExprFreeVariableFinder(datum);
|
|
48638
48611
|
this.freeVariables = new Map();
|
|
48639
48612
|
}
|
|
48613
|
+
// helper function to build relation cache and indexes
|
|
48614
|
+
buildRelationCache() {
|
|
48615
|
+
if (this.relationCache !== null) {
|
|
48616
|
+
return; // already built
|
|
48617
|
+
}
|
|
48618
|
+
this.relationCache = new Map();
|
|
48619
|
+
this.relationIndexCache = new Map();
|
|
48620
|
+
const relations = this.instanceData.getRelations();
|
|
48621
|
+
const isConvertibleToNumber = (value) => {
|
|
48622
|
+
return typeof value === "string" && !isNaN(Number(value));
|
|
48623
|
+
};
|
|
48624
|
+
const isConvertibleToBoolean = (value) => {
|
|
48625
|
+
if (typeof value === "boolean")
|
|
48626
|
+
return false; // already boolean
|
|
48627
|
+
return value === "true" || value === "#t" || value === "false" || value === "#f";
|
|
48628
|
+
};
|
|
48629
|
+
const convertToBoolean = (value) => {
|
|
48630
|
+
if (typeof value === "boolean")
|
|
48631
|
+
return value;
|
|
48632
|
+
if (value === "true" || value === "#t")
|
|
48633
|
+
return true;
|
|
48634
|
+
if (value === "false" || value === "#f")
|
|
48635
|
+
return false;
|
|
48636
|
+
throw new Error(`Cannot convert ${value} to boolean`);
|
|
48637
|
+
};
|
|
48638
|
+
for (const relation of relations) {
|
|
48639
|
+
let relationAtoms = relation.tuples.map((tuple) => tuple.atoms);
|
|
48640
|
+
// Convert numeric and boolean strings to their actual types
|
|
48641
|
+
relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => isConvertibleToNumber(value) ? Number(value) : value));
|
|
48642
|
+
relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => isConvertibleToBoolean(value) ? convertToBoolean(value) : value));
|
|
48643
|
+
this.relationCache.set(relation.name, relationAtoms);
|
|
48644
|
+
// Build index for this relation: first element -> all tuples starting with that element
|
|
48645
|
+
const relationIndex = new Map();
|
|
48646
|
+
for (const tuple of relationAtoms) {
|
|
48647
|
+
if (tuple.length > 0) {
|
|
48648
|
+
const key = tuple[0];
|
|
48649
|
+
if (!relationIndex.has(key)) {
|
|
48650
|
+
relationIndex.set(key, []);
|
|
48651
|
+
}
|
|
48652
|
+
relationIndex.get(key).push(tuple);
|
|
48653
|
+
}
|
|
48654
|
+
}
|
|
48655
|
+
this.relationIndexCache.set(relation.name, relationIndex);
|
|
48656
|
+
}
|
|
48657
|
+
}
|
|
48640
48658
|
//helper function
|
|
48641
48659
|
updateFreeVariables(freeVars) {
|
|
48642
48660
|
if (this.freeVariables.size === 0) {
|
|
@@ -48732,6 +48750,45 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48732
48750
|
}
|
|
48733
48751
|
return labelNum;
|
|
48734
48752
|
}
|
|
48753
|
+
// Optimized dotJoin that can use pre-built relation indexes
|
|
48754
|
+
dotJoin(left, right, rightRelationName) {
|
|
48755
|
+
const leftExpr = isSingleValue(left) ? [[left]] : left;
|
|
48756
|
+
const rightExpr = isSingleValue(right) ? [[right]] : right;
|
|
48757
|
+
// Try to use pre-built index if available
|
|
48758
|
+
let rightIndex;
|
|
48759
|
+
if (rightRelationName && this.relationIndexCache) {
|
|
48760
|
+
rightIndex = this.relationIndexCache.get(rightRelationName);
|
|
48761
|
+
}
|
|
48762
|
+
// If no pre-built index, build one on the fly
|
|
48763
|
+
if (!rightIndex) {
|
|
48764
|
+
rightIndex = new Map();
|
|
48765
|
+
for (const rightTuple of rightExpr) {
|
|
48766
|
+
const key = rightTuple[0];
|
|
48767
|
+
if (!rightIndex.has(key)) {
|
|
48768
|
+
rightIndex.set(key, []);
|
|
48769
|
+
}
|
|
48770
|
+
rightIndex.get(key).push(rightTuple);
|
|
48771
|
+
}
|
|
48772
|
+
}
|
|
48773
|
+
const result = [];
|
|
48774
|
+
for (const leftTuple of leftExpr) {
|
|
48775
|
+
const joinKey = leftTuple[leftTuple.length - 1];
|
|
48776
|
+
const matchingRightTuples = rightIndex.get(joinKey);
|
|
48777
|
+
if (matchingRightTuples) {
|
|
48778
|
+
for (const rightTuple of matchingRightTuples) {
|
|
48779
|
+
result.push([
|
|
48780
|
+
...leftTuple.slice(0, leftTuple.length - 1),
|
|
48781
|
+
...rightTuple.slice(1),
|
|
48782
|
+
]);
|
|
48783
|
+
}
|
|
48784
|
+
}
|
|
48785
|
+
}
|
|
48786
|
+
if (result.some(tuple => tuple.length === 0)) {
|
|
48787
|
+
throw new Error("Join would create a relation of arity 0");
|
|
48788
|
+
}
|
|
48789
|
+
// Deduplicate results to ensure set semantics
|
|
48790
|
+
return deduplicateTuples(result);
|
|
48791
|
+
}
|
|
48735
48792
|
// helper function
|
|
48736
48793
|
cacheResult(ctx, freeVarsKey, result) {
|
|
48737
48794
|
if (!this.cachedResults.has(ctx)) {
|
|
@@ -48862,9 +48919,6 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48862
48919
|
if (foundAllVars && this.cachedResults.has(ctx)) {
|
|
48863
48920
|
if (this.cachedResults.get(ctx).has(freeVarsKey)) {
|
|
48864
48921
|
// cache hit!
|
|
48865
|
-
// console.log('cache hit for ctx:', ctx.text);
|
|
48866
|
-
// console.log('freeVarsKey:', freeVarsKey);
|
|
48867
|
-
// console.log('cachedResults:', this.cachedResults.get(ctx));
|
|
48868
48922
|
return this.cachedResults.get(ctx).get(freeVarsKey);
|
|
48869
48923
|
}
|
|
48870
48924
|
}
|
|
@@ -49634,7 +49688,7 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49634
49688
|
}
|
|
49635
49689
|
}
|
|
49636
49690
|
// Box join: <expr-a>[<expr-b>] == <expr-b> . <expr-a>
|
|
49637
|
-
return dotJoin(insideBracesExprs, beforeBracesExpr);
|
|
49691
|
+
return this.dotJoin(insideBracesExprs, beforeBracesExpr);
|
|
49638
49692
|
}
|
|
49639
49693
|
return this.visitChildren(ctx);
|
|
49640
49694
|
}
|
|
@@ -49647,9 +49701,20 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49647
49701
|
}
|
|
49648
49702
|
const beforeDotExpr = this.visit(ctx.expr15());
|
|
49649
49703
|
const afterDotExpr = this.visit(ctx.expr16());
|
|
49650
|
-
//
|
|
49651
|
-
|
|
49652
|
-
|
|
49704
|
+
// Try to extract the relation name if the right side is a simple identifier/relation name
|
|
49705
|
+
let rightRelationName;
|
|
49706
|
+
// Simple heuristic: check if it's a tuple array (likely a relation)
|
|
49707
|
+
// and if so, try to find which relation it matches
|
|
49708
|
+
if (isTupleArray(afterDotExpr) && this.relationCache) {
|
|
49709
|
+
// Check if this matches any cached relation
|
|
49710
|
+
for (const [relName, relTuples] of this.relationCache.entries()) {
|
|
49711
|
+
if (relTuples === afterDotExpr) {
|
|
49712
|
+
rightRelationName = relName;
|
|
49713
|
+
break;
|
|
49714
|
+
}
|
|
49715
|
+
}
|
|
49716
|
+
}
|
|
49717
|
+
return this.dotJoin(beforeDotExpr, afterDotExpr, rightRelationName);
|
|
49653
49718
|
}
|
|
49654
49719
|
if (ctx.LEFT_SQUARE_TOK()) {
|
|
49655
49720
|
const beforeBracesName = this.visit(ctx.name());
|
|
@@ -50092,15 +50157,10 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
50092
50157
|
throw new Error(`Cannot convert ${value} to boolean`);
|
|
50093
50158
|
};
|
|
50094
50159
|
// end of 3 helper functions
|
|
50095
|
-
// check if it is a relation
|
|
50096
|
-
|
|
50097
|
-
|
|
50098
|
-
|
|
50099
|
-
let relationAtoms = relation.tuples.map((tuple) => tuple.atoms);
|
|
50100
|
-
relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => isConvertibleToNumber(value) ? Number(value) : value));
|
|
50101
|
-
relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => isConvertibleToBoolean(value) ? convertToBoolean(value) : value));
|
|
50102
|
-
return relationAtoms;
|
|
50103
|
-
}
|
|
50160
|
+
// check if it is a relation - use cache for faster lookups
|
|
50161
|
+
this.buildRelationCache();
|
|
50162
|
+
if (this.relationCache.has(identifier)) {
|
|
50163
|
+
return this.relationCache.get(identifier);
|
|
50104
50164
|
}
|
|
50105
50165
|
if (result !== undefined) {
|
|
50106
50166
|
result = result.map((tuple) => tuple.map((value) => isConvertibleToNumber(value) ? Number(value) : value));
|
|
@@ -50386,6 +50446,9 @@ class ForgeExprFreeVariableFinder extends AbstractParseTreeVisitor_1.AbstractPar
|
|
|
50386
50446
|
const quantDeclListVars = this.getQuantDeclListVarNames(ctx.quantDeclList());
|
|
50387
50447
|
// we need to get all the vars referenced here other than the vars
|
|
50388
50448
|
// bound by the quantifier (in quantDeclListVars)
|
|
50449
|
+
// First, get free variables from the quantDeclList itself
|
|
50450
|
+
// (e.g., in "some p2 : c.parent | ...", c is a free variable)
|
|
50451
|
+
const quantDeclListFreeVars = this.visit(ctx.quantDeclList());
|
|
50389
50452
|
const blockOrBar = ctx.blockOrBar();
|
|
50390
50453
|
if (blockOrBar === undefined) {
|
|
50391
50454
|
throw new Error("expected to quantify over something!");
|
|
@@ -50394,13 +50457,15 @@ class ForgeExprFreeVariableFinder extends AbstractParseTreeVisitor_1.AbstractPar
|
|
|
50394
50457
|
blockOrBar.expr() === undefined) {
|
|
50395
50458
|
throw new Error("Expected the quantifier to have a bar followed by an expr!");
|
|
50396
50459
|
}
|
|
50397
|
-
let
|
|
50460
|
+
let barExprFreeVars;
|
|
50398
50461
|
if (blockOrBar.block() !== undefined) {
|
|
50399
|
-
|
|
50462
|
+
barExprFreeVars = this.visit(blockOrBar.block());
|
|
50400
50463
|
}
|
|
50401
50464
|
else {
|
|
50402
|
-
|
|
50465
|
+
barExprFreeVars = this.visit(blockOrBar.expr());
|
|
50403
50466
|
}
|
|
50467
|
+
// Merge free variables from quantDeclList and barExpr
|
|
50468
|
+
const allFreeVars = this.aggregateResult(barExprFreeVars, quantDeclListFreeVars);
|
|
50404
50469
|
// the context node for the quantifier as a whole shouldn't have _all_ of these
|
|
50405
50470
|
// free vars; specifically, it should not include the vars that are being
|
|
50406
50471
|
// bound by the quantifier
|
package/package.json
CHANGED