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 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
- return a.every((tupleA) => b.some((tupleB) => areTuplesEqual(tupleA, tupleB)));
48484
+ // Optimize using Set for O(n) lookup instead of O()
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
- if (!result.some((existing) => areTuplesEqual(existing, tuple))) {
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
- // now, recursively compute the cartesian product
48501
- function cartesianProduct(arrays) {
48502
- if (arrays.length === 0)
48503
- return [[]];
48504
- const [first, ...rest] = arrays;
48505
- const restProduct = cartesianProduct(rest);
48506
- return first.flatMap((value) => restProduct.map((product) => [value, ...product]));
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 cartesianProduct(valueSets);
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
- // perform BFS from each node to get the transitive closure
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 transitiveClosure = new Set();
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
- while (queue.length > 0) {
48536
- const current = queue.shift();
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
- transitiveClosure.add(JSON.stringify([start, current]));
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(transitiveClosure).map((pair) => JSON.parse(pair));
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
- leftExpr.forEach((leftTuple) => {
48559
- rightExpr.forEach((rightTuple) => {
48560
- if (leftTuple[leftTuple.length - 1] === rightTuple[0]) {
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
- return keys.map((key) => `${key}=${freeVarValues[key]}`).join("|");
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
- const quantDeclEnv = {
48870
- env: {},
48871
- type: "quantDecl",
48872
- };
48927
+ // Update environment values in place
48873
48928
  for (let j = 0; j < varNames.length; j++) {
48874
- const varName = varNames[j];
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
- return leftChildValue.filter((tuple) => !rightChildValue.some((rightTuple) => areTuplesEqual(tuple, rightTuple)));
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
- return leftChildValue.filter((tuple) => rightChildValue.some((rightTuple) => areTuplesEqual(tuple, rightTuple)));
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
- return deduplicateTuples([...idenResult, ...transitiveClosureResult]);
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
- const quantDeclEnv = {
49839
- env: {},
49840
- type: "quantDecl",
49841
- };
49913
+ // Update environment values in place
49842
49914
  for (let j = 0; j < varNames.length; j++) {
49843
- const varName = varNames[j];
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
- return result;
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
- try { // now, we can actually evaluate the expression
61142
- var tree = this.getExpressionParseTree(forgeExpr);
61213
+ // Check cache first
61214
+ let tree;
61215
+ if (this.parseTreeCache.has(forgeExpr)) {
61216
+ tree = this.parseTreeCache.get(forgeExpr);
61143
61217
  }
61144
- catch (e) {
61145
- // if we can't parse the expression, we return an error
61146
- return {
61147
- error: new Error(`Error parsing expression "${forgeExpr}"`)
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 instanceof ForgeParser_1.ExprContext ? tree : tree.getChild(0));
61234
+ let result = evaluator.visit(tree);
61153
61235
  // ensure we're visiting an ExprContext
61154
61236
  return result;
61155
61237
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-graph-query",
3
- "version": "2.0.2",
3
+ "version": "2.1.2",
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",