simple-graph-query 2.1.3 → 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,37 +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
|
-
return result;
|
|
48603
|
-
}
|
|
48604
48573
|
function bitwidthWraparound(value, bitwidth) {
|
|
48605
48574
|
const modulus = Math.pow(2, bitwidth); // total number of Int values
|
|
48606
48575
|
const halfValue = Math.pow(2, bitwidth - 1); // halfway point
|
|
@@ -48631,11 +48600,61 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48631
48600
|
// NOTE: strings will be of the format "<var-name>=<value>|..." sorted in
|
|
48632
48601
|
// increasing lexicographic order of variable names
|
|
48633
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;
|
|
48634
48608
|
this.instanceData = datum;
|
|
48635
48609
|
this.environmentStack = [];
|
|
48636
48610
|
this.freeVariableFinder = new ForgeExprFreeVariableFinder_1.ForgeExprFreeVariableFinder(datum);
|
|
48637
48611
|
this.freeVariables = new Map();
|
|
48638
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
|
+
}
|
|
48639
48658
|
//helper function
|
|
48640
48659
|
updateFreeVariables(freeVars) {
|
|
48641
48660
|
if (this.freeVariables.size === 0) {
|
|
@@ -48731,6 +48750,45 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48731
48750
|
}
|
|
48732
48751
|
return labelNum;
|
|
48733
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
|
+
}
|
|
48734
48792
|
// helper function
|
|
48735
48793
|
cacheResult(ctx, freeVarsKey, result) {
|
|
48736
48794
|
if (!this.cachedResults.has(ctx)) {
|
|
@@ -48861,9 +48919,6 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48861
48919
|
if (foundAllVars && this.cachedResults.has(ctx)) {
|
|
48862
48920
|
if (this.cachedResults.get(ctx).has(freeVarsKey)) {
|
|
48863
48921
|
// cache hit!
|
|
48864
|
-
// console.log('cache hit for ctx:', ctx.text);
|
|
48865
|
-
// console.log('freeVarsKey:', freeVarsKey);
|
|
48866
|
-
// console.log('cachedResults:', this.cachedResults.get(ctx));
|
|
48867
48922
|
return this.cachedResults.get(ctx).get(freeVarsKey);
|
|
48868
48923
|
}
|
|
48869
48924
|
}
|
|
@@ -49633,7 +49688,7 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49633
49688
|
}
|
|
49634
49689
|
}
|
|
49635
49690
|
// Box join: <expr-a>[<expr-b>] == <expr-b> . <expr-a>
|
|
49636
|
-
return dotJoin(insideBracesExprs, beforeBracesExpr);
|
|
49691
|
+
return this.dotJoin(insideBracesExprs, beforeBracesExpr);
|
|
49637
49692
|
}
|
|
49638
49693
|
return this.visitChildren(ctx);
|
|
49639
49694
|
}
|
|
@@ -49646,9 +49701,20 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49646
49701
|
}
|
|
49647
49702
|
const beforeDotExpr = this.visit(ctx.expr15());
|
|
49648
49703
|
const afterDotExpr = this.visit(ctx.expr16());
|
|
49649
|
-
//
|
|
49650
|
-
|
|
49651
|
-
|
|
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);
|
|
49652
49718
|
}
|
|
49653
49719
|
if (ctx.LEFT_SQUARE_TOK()) {
|
|
49654
49720
|
const beforeBracesName = this.visit(ctx.name());
|
|
@@ -50091,15 +50157,10 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
50091
50157
|
throw new Error(`Cannot convert ${value} to boolean`);
|
|
50092
50158
|
};
|
|
50093
50159
|
// end of 3 helper functions
|
|
50094
|
-
// check if it is a relation
|
|
50095
|
-
|
|
50096
|
-
|
|
50097
|
-
|
|
50098
|
-
let relationAtoms = relation.tuples.map((tuple) => tuple.atoms);
|
|
50099
|
-
relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => isConvertibleToNumber(value) ? Number(value) : value));
|
|
50100
|
-
relationAtoms = relationAtoms.map((tuple) => tuple.map((value) => isConvertibleToBoolean(value) ? convertToBoolean(value) : value));
|
|
50101
|
-
return relationAtoms;
|
|
50102
|
-
}
|
|
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);
|
|
50103
50164
|
}
|
|
50104
50165
|
if (result !== undefined) {
|
|
50105
50166
|
result = result.map((tuple) => tuple.map((value) => isConvertibleToNumber(value) ? Number(value) : value));
|
|
@@ -50385,6 +50446,9 @@ class ForgeExprFreeVariableFinder extends AbstractParseTreeVisitor_1.AbstractPar
|
|
|
50385
50446
|
const quantDeclListVars = this.getQuantDeclListVarNames(ctx.quantDeclList());
|
|
50386
50447
|
// we need to get all the vars referenced here other than the vars
|
|
50387
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());
|
|
50388
50452
|
const blockOrBar = ctx.blockOrBar();
|
|
50389
50453
|
if (blockOrBar === undefined) {
|
|
50390
50454
|
throw new Error("expected to quantify over something!");
|
|
@@ -50393,13 +50457,15 @@ class ForgeExprFreeVariableFinder extends AbstractParseTreeVisitor_1.AbstractPar
|
|
|
50393
50457
|
blockOrBar.expr() === undefined) {
|
|
50394
50458
|
throw new Error("Expected the quantifier to have a bar followed by an expr!");
|
|
50395
50459
|
}
|
|
50396
|
-
let
|
|
50460
|
+
let barExprFreeVars;
|
|
50397
50461
|
if (blockOrBar.block() !== undefined) {
|
|
50398
|
-
|
|
50462
|
+
barExprFreeVars = this.visit(blockOrBar.block());
|
|
50399
50463
|
}
|
|
50400
50464
|
else {
|
|
50401
|
-
|
|
50465
|
+
barExprFreeVars = this.visit(blockOrBar.expr());
|
|
50402
50466
|
}
|
|
50467
|
+
// Merge free variables from quantDeclList and barExpr
|
|
50468
|
+
const allFreeVars = this.aggregateResult(barExprFreeVars, quantDeclListFreeVars);
|
|
50403
50469
|
// the context node for the quantifier as a whole shouldn't have _all_ of these
|
|
50404
50470
|
// free vars; specifically, it should not include the vars that are being
|
|
50405
50471
|
// bound by the quantifier
|
package/package.json
CHANGED