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
- // console.log('beforeExpr:', beforeDotExpr);
49650
- // console.log('afterExpr:', afterDotExpr);
49651
- 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);
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
- const relations = this.instanceData.getRelations();
50096
- for (const relation of relations) {
50097
- if (relation.name === identifier) {
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 allFreeVars;
50460
+ let barExprFreeVars;
50397
50461
  if (blockOrBar.block() !== undefined) {
50398
- allFreeVars = this.visit(blockOrBar.block());
50462
+ barExprFreeVars = this.visit(blockOrBar.block());
50399
50463
  }
50400
50464
  else {
50401
- allFreeVars = this.visit(blockOrBar.expr());
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-graph-query",
3
- "version": "2.1.3",
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",