simple-graph-query 2.0.1 → 2.1.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.
|
@@ -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
|
-
|
|
48484
|
+
// Optimize using Set for O(n) lookup instead of O(n²)
|
|
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
|
-
|
|
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
|
-
//
|
|
48501
|
-
|
|
48502
|
-
|
|
48503
|
-
|
|
48504
|
-
|
|
48505
|
-
|
|
48506
|
-
|
|
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
|
|
48526
|
+
return result;
|
|
48509
48527
|
}
|
|
48510
48528
|
function transitiveClosure(pairs) {
|
|
48511
48529
|
if (pairs.length === 0)
|
|
@@ -48554,17 +48572,29 @@ function transitiveClosure(pairs) {
|
|
|
48554
48572
|
function dotJoin(left, right) {
|
|
48555
48573
|
const leftExpr = isSingleValue(left) ? [[left]] : left;
|
|
48556
48574
|
const rightExpr = isSingleValue(right) ? [[right]] : right;
|
|
48575
|
+
// Optimize join using a Map for O(n+m) instead of O(n*m) lookup
|
|
48576
|
+
// Group right tuples by their first element for fast lookup
|
|
48577
|
+
const rightIndex = new Map();
|
|
48578
|
+
for (const rightTuple of rightExpr) {
|
|
48579
|
+
const key = rightTuple[0];
|
|
48580
|
+
if (!rightIndex.has(key)) {
|
|
48581
|
+
rightIndex.set(key, []);
|
|
48582
|
+
}
|
|
48583
|
+
rightIndex.get(key).push(rightTuple);
|
|
48584
|
+
}
|
|
48557
48585
|
const result = [];
|
|
48558
|
-
|
|
48559
|
-
|
|
48560
|
-
|
|
48586
|
+
for (const leftTuple of leftExpr) {
|
|
48587
|
+
const joinKey = leftTuple[leftTuple.length - 1];
|
|
48588
|
+
const matchingRightTuples = rightIndex.get(joinKey);
|
|
48589
|
+
if (matchingRightTuples) {
|
|
48590
|
+
for (const rightTuple of matchingRightTuples) {
|
|
48561
48591
|
result.push([
|
|
48562
48592
|
...leftTuple.slice(0, leftTuple.length - 1),
|
|
48563
48593
|
...rightTuple.slice(1),
|
|
48564
48594
|
]);
|
|
48565
48595
|
}
|
|
48566
|
-
}
|
|
48567
|
-
}
|
|
48596
|
+
}
|
|
48597
|
+
}
|
|
48568
48598
|
if (result.some(tuple => tuple.length === 0)) {
|
|
48569
48599
|
throw new Error("Join would create a relation of arity 0");
|
|
48570
48600
|
}
|
|
@@ -48628,7 +48658,12 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48628
48658
|
constructFreeVariableKey(freeVarValues) {
|
|
48629
48659
|
const keys = Object.keys(freeVarValues);
|
|
48630
48660
|
keys.sort(); // sort the keys to ensure consistent ordering
|
|
48631
|
-
|
|
48661
|
+
// Use JSON.stringify for complex values to ensure proper serialization
|
|
48662
|
+
return keys.map((key) => {
|
|
48663
|
+
const value = freeVarValues[key];
|
|
48664
|
+
const valueStr = Array.isArray(value) ? JSON.stringify(value) : String(value);
|
|
48665
|
+
return `${key}=${valueStr}`;
|
|
48666
|
+
}).join("|");
|
|
48632
48667
|
}
|
|
48633
48668
|
// helper function to get the label for a value (used for @: operator)
|
|
48634
48669
|
getLabelForValue(value) {
|
|
@@ -49418,7 +49453,9 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49418
49453
|
return leftChildValue;
|
|
49419
49454
|
}
|
|
49420
49455
|
if (leftChildValue[0].length === rightChildValue[0].length) {
|
|
49421
|
-
|
|
49456
|
+
// Optimize set difference using Set for O(n+m) instead of O(n*m)
|
|
49457
|
+
const rightSet = new Set(rightChildValue.map(tupleToKey));
|
|
49458
|
+
return leftChildValue.filter(tuple => !rightSet.has(tupleToKey(tuple)));
|
|
49422
49459
|
}
|
|
49423
49460
|
}
|
|
49424
49461
|
else {
|
|
@@ -49491,7 +49528,9 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49491
49528
|
return [];
|
|
49492
49529
|
}
|
|
49493
49530
|
if (leftChildValue[0].length === rightChildValue[0].length) {
|
|
49494
|
-
|
|
49531
|
+
// Optimize set intersection using Set for O(n+m) instead of O(n*m)
|
|
49532
|
+
const rightSet = new Set(rightChildValue.map(tupleToKey));
|
|
49533
|
+
return leftChildValue.filter(tuple => rightSet.has(tupleToKey(tuple)));
|
|
49495
49534
|
}
|
|
49496
49535
|
}
|
|
49497
49536
|
else {
|
|
@@ -50025,12 +50064,12 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
50025
50064
|
if (exports.SUPPORTED_BUILTINS.includes(identifier)) {
|
|
50026
50065
|
return identifier;
|
|
50027
50066
|
}
|
|
50028
|
-
// Check if this looks like a
|
|
50029
|
-
//
|
|
50030
|
-
|
|
50067
|
+
// Check if this looks like a label identifier (for label comparison).
|
|
50068
|
+
// Labels can be any alphanumeric string with underscores.
|
|
50069
|
+
// This allows identifiers like "Black", "red", "my_label", "Color_123", etc.
|
|
50070
|
+
// to be treated as string literals in label comparisons (e.g., @:y = Black).
|
|
50071
|
+
const labelLikePattern = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
50031
50072
|
if (labelLikePattern.test(identifier)) {
|
|
50032
|
-
// This looks like a simple label (e.g., "black", "red", "blue", etc.)
|
|
50033
|
-
// Return it as a string literal to enable label comparison syntax
|
|
50034
50073
|
return identifier;
|
|
50035
50074
|
}
|
|
50036
50075
|
throw new NameNotFoundError(`bad name ${identifier} referenced!`);
|
package/package.json
CHANGED