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
- // console.log('beforeExpr:', beforeDotExpr);
49651
- // console.log('afterExpr:', afterDotExpr);
49652
- return dotJoin(beforeDotExpr, afterDotExpr);
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
- const relations = this.instanceData.getRelations();
50097
- for (const relation of relations) {
50098
- if (relation.name === identifier) {
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 allFreeVars;
50460
+ let barExprFreeVars;
50398
50461
  if (blockOrBar.block() !== undefined) {
50399
- allFreeVars = this.visit(blockOrBar.block());
50462
+ barExprFreeVars = this.visit(blockOrBar.block());
50400
50463
  }
50401
50464
  else {
50402
- allFreeVars = this.visit(blockOrBar.expr());
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-graph-query",
3
- "version": "2.1.4",
3
+ "version": "2.2.0",
4
4
  "description": "TypeScript evaluator for Forge expressions with browser-compatible UMD bundle",
5
5
  "main": "dist/simple-graph-query.bundle.js",
6
6
  "types": "dist/index.d.ts",