simple-graph-query 2.2.0 → 2.2.1
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.
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ExprContext } from "./forge-antlr/ForgeParser";
|
|
2
|
+
import { Tuple } from "./ForgeExprEvaluator";
|
|
3
|
+
/**
|
|
4
|
+
* Analyzes set comprehension constraints to detect simple numeric comparison patterns
|
|
5
|
+
* that can be optimized by generating only valid combinations instead of filtering.
|
|
6
|
+
*/
|
|
7
|
+
export interface NumericConstraintPattern {
|
|
8
|
+
type: 'less_than' | 'greater_than' | 'less_equal' | 'greater_equal' | 'not_equal' | 'none';
|
|
9
|
+
leftVar: string;
|
|
10
|
+
rightVar: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Detects if a constraint expression is a simple numeric comparison between two variables.
|
|
14
|
+
* Currently detects patterns like: a < b, a > b, a <= b, a >= b, a != b
|
|
15
|
+
*
|
|
16
|
+
* Note: Uses string matching on expression text which is simple but may miss expressions
|
|
17
|
+
* with extra whitespace or parentheses. This is intentionally conservative - we prefer
|
|
18
|
+
* to fall back to the standard approach rather than risk incorrect optimization.
|
|
19
|
+
*
|
|
20
|
+
* @param constraintExpr The constraint expression to analyze
|
|
21
|
+
* @param varNames The variable names in scope
|
|
22
|
+
* @returns Pattern info if detected, null otherwise
|
|
23
|
+
*/
|
|
24
|
+
export declare function detectNumericComparisonPattern(constraintExpr: ExprContext, varNames: string[]): NumericConstraintPattern | null;
|
|
25
|
+
/**
|
|
26
|
+
* Checks if all values in the sets are numbers (or tuples containing single numbers).
|
|
27
|
+
* This is required for numeric comparison optimization to be applicable.
|
|
28
|
+
*/
|
|
29
|
+
export declare function areAllNumericSets(sets: Tuple[][]): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Generates optimized combinations for numeric comparison constraints.
|
|
32
|
+
* Instead of generating all combinations and filtering, generates only valid ones.
|
|
33
|
+
*
|
|
34
|
+
* @param varNames Variable names in order
|
|
35
|
+
* @param quantifiedSets The sets each variable ranges over
|
|
36
|
+
* @param pattern The detected numeric comparison pattern
|
|
37
|
+
* @returns Only the tuples that satisfy the constraint
|
|
38
|
+
*/
|
|
39
|
+
export declare function generateOptimizedNumericCombinations(varNames: string[], quantifiedSets: Tuple[][], pattern: NumericConstraintPattern): Tuple[];
|
|
@@ -48441,6 +48441,7 @@ exports.areTupleArraysEqual = areTupleArraysEqual;
|
|
|
48441
48441
|
const AbstractParseTreeVisitor_1 = __webpack_require__(/*! antlr4ts/tree/AbstractParseTreeVisitor */ "./node_modules/antlr4ts/tree/AbstractParseTreeVisitor.js");
|
|
48442
48442
|
const lodash_1 = __webpack_require__(/*! lodash */ "./node_modules/lodash/lodash.js");
|
|
48443
48443
|
const ForgeExprFreeVariableFinder_1 = __webpack_require__(/*! ./ForgeExprFreeVariableFinder */ "./src/ForgeExprFreeVariableFinder.ts");
|
|
48444
|
+
const NumericConstraintOptimizer_1 = __webpack_require__(/*! ./NumericConstraintOptimizer */ "./src/NumericConstraintOptimizer.ts");
|
|
48444
48445
|
///// HELPER FUNCTIONS /////
|
|
48445
48446
|
function isSingleValue(value) {
|
|
48446
48447
|
return (typeof value === "string" ||
|
|
@@ -48959,7 +48960,26 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48959
48960
|
varNames.push(varName);
|
|
48960
48961
|
quantifiedSets.push(varQuantifiedSets[varName]);
|
|
48961
48962
|
}
|
|
48962
|
-
|
|
48963
|
+
// Try to optimize numeric comparisons
|
|
48964
|
+
let product;
|
|
48965
|
+
let useOptimizedPath = false;
|
|
48966
|
+
if (!isDisjoint && varNames.length >= 2 && (0, NumericConstraintOptimizer_1.areAllNumericSets)(quantifiedSets)) {
|
|
48967
|
+
// Try to detect and optimize numeric comparison patterns
|
|
48968
|
+
const pattern = (0, NumericConstraintOptimizer_1.detectNumericComparisonPattern)(barExpr, varNames);
|
|
48969
|
+
if (pattern && pattern.type !== 'none') {
|
|
48970
|
+
// Use optimized combination generation
|
|
48971
|
+
product = (0, NumericConstraintOptimizer_1.generateOptimizedNumericCombinations)(varNames, quantifiedSets, pattern);
|
|
48972
|
+
useOptimizedPath = true;
|
|
48973
|
+
}
|
|
48974
|
+
else {
|
|
48975
|
+
// Fall back to standard cartesian product
|
|
48976
|
+
product = getCombinations(quantifiedSets);
|
|
48977
|
+
}
|
|
48978
|
+
}
|
|
48979
|
+
else {
|
|
48980
|
+
// Fall back to standard cartesian product
|
|
48981
|
+
product = getCombinations(quantifiedSets);
|
|
48982
|
+
}
|
|
48963
48983
|
const result = [];
|
|
48964
48984
|
let foundTrue = false;
|
|
48965
48985
|
let foundFalse = false;
|
|
@@ -48990,10 +49010,19 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
48990
49010
|
for (let j = 0; j < varNames.length; j++) {
|
|
48991
49011
|
quantDeclEnv.env[varNames[j]] = tuple[j];
|
|
48992
49012
|
}
|
|
48993
|
-
//
|
|
48994
|
-
|
|
48995
|
-
|
|
48996
|
-
|
|
49013
|
+
// If we used the optimized path, we can skip constraint evaluation
|
|
49014
|
+
// since the combinations already satisfy the constraint
|
|
49015
|
+
let barExprValue;
|
|
49016
|
+
if (useOptimizedPath) {
|
|
49017
|
+
barExprValue = true;
|
|
49018
|
+
}
|
|
49019
|
+
else {
|
|
49020
|
+
// now, we want to evaluate the barExpr
|
|
49021
|
+
const evalResult = this.visit(barExpr);
|
|
49022
|
+
if (!isBoolean(evalResult)) {
|
|
49023
|
+
throw new Error("Expected the expression after the bar to be a boolean!");
|
|
49024
|
+
}
|
|
49025
|
+
barExprValue = evalResult;
|
|
48997
49026
|
}
|
|
48998
49027
|
if (barExprValue) {
|
|
48999
49028
|
result.push(tuple);
|
|
@@ -49973,7 +50002,28 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49973
50002
|
varNames.push(varName);
|
|
49974
50003
|
quantifiedSets.push(varQuantifiedSets[varName]);
|
|
49975
50004
|
}
|
|
49976
|
-
|
|
50005
|
+
// Try to optimize numeric comparisons (same optimization as in quantifiers)
|
|
50006
|
+
// Note: Set comprehensions don't support the 'disj' keyword, so we don't check for it here.
|
|
50007
|
+
// Quantifiers do support 'disj', so they check !isDisjoint before optimizing.
|
|
50008
|
+
let product;
|
|
50009
|
+
let useOptimizedPath = false;
|
|
50010
|
+
if (varNames.length >= 2 && (0, NumericConstraintOptimizer_1.areAllNumericSets)(quantifiedSets)) {
|
|
50011
|
+
// Try to detect and optimize numeric comparison patterns
|
|
50012
|
+
const pattern = (0, NumericConstraintOptimizer_1.detectNumericComparisonPattern)(barExpr, varNames);
|
|
50013
|
+
if (pattern && pattern.type !== 'none') {
|
|
50014
|
+
// Use optimized combination generation
|
|
50015
|
+
product = (0, NumericConstraintOptimizer_1.generateOptimizedNumericCombinations)(varNames, quantifiedSets, pattern);
|
|
50016
|
+
useOptimizedPath = true;
|
|
50017
|
+
}
|
|
50018
|
+
else {
|
|
50019
|
+
// Fall back to standard cartesian product
|
|
50020
|
+
product = getCombinations(quantifiedSets);
|
|
50021
|
+
}
|
|
50022
|
+
}
|
|
50023
|
+
else {
|
|
50024
|
+
// Fall back to standard cartesian product
|
|
50025
|
+
product = getCombinations(quantifiedSets);
|
|
50026
|
+
}
|
|
49977
50027
|
const result = [];
|
|
49978
50028
|
// Optimize: create environment once and reuse it
|
|
49979
50029
|
const quantDeclEnv = {
|
|
@@ -49987,10 +50037,19 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
|
|
|
49987
50037
|
for (let j = 0; j < varNames.length; j++) {
|
|
49988
50038
|
quantDeclEnv.env[varNames[j]] = tuple[j];
|
|
49989
50039
|
}
|
|
49990
|
-
//
|
|
49991
|
-
|
|
49992
|
-
|
|
49993
|
-
|
|
50040
|
+
// If we used the optimized path, we can skip constraint evaluation
|
|
50041
|
+
// since the combinations already satisfy the constraint
|
|
50042
|
+
let barExprValue;
|
|
50043
|
+
if (useOptimizedPath) {
|
|
50044
|
+
barExprValue = true;
|
|
50045
|
+
}
|
|
50046
|
+
else {
|
|
50047
|
+
// now, we want to evaluate the barExpr
|
|
50048
|
+
const evalResult = this.visit(barExpr);
|
|
50049
|
+
if (!isBoolean(evalResult)) {
|
|
50050
|
+
throw new Error("Expected the expression after the bar to be a boolean value!");
|
|
50051
|
+
}
|
|
50052
|
+
barExprValue = evalResult;
|
|
49994
50053
|
}
|
|
49995
50054
|
if (barExprValue) {
|
|
49996
50055
|
// will error if not boolean val, which we want
|
|
@@ -50657,6 +50716,200 @@ class ForgeExprFreeVariableFinder extends AbstractParseTreeVisitor_1.AbstractPar
|
|
|
50657
50716
|
exports.ForgeExprFreeVariableFinder = ForgeExprFreeVariableFinder;
|
|
50658
50717
|
|
|
50659
50718
|
|
|
50719
|
+
/***/ }),
|
|
50720
|
+
|
|
50721
|
+
/***/ "./src/NumericConstraintOptimizer.ts":
|
|
50722
|
+
/*!*******************************************!*\
|
|
50723
|
+
!*** ./src/NumericConstraintOptimizer.ts ***!
|
|
50724
|
+
\*******************************************/
|
|
50725
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
50726
|
+
|
|
50727
|
+
"use strict";
|
|
50728
|
+
|
|
50729
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
50730
|
+
exports.detectNumericComparisonPattern = detectNumericComparisonPattern;
|
|
50731
|
+
exports.areAllNumericSets = areAllNumericSets;
|
|
50732
|
+
exports.generateOptimizedNumericCombinations = generateOptimizedNumericCombinations;
|
|
50733
|
+
/**
|
|
50734
|
+
* Detects if a constraint expression is a simple numeric comparison between two variables.
|
|
50735
|
+
* Currently detects patterns like: a < b, a > b, a <= b, a >= b, a != b
|
|
50736
|
+
*
|
|
50737
|
+
* Note: Uses string matching on expression text which is simple but may miss expressions
|
|
50738
|
+
* with extra whitespace or parentheses. This is intentionally conservative - we prefer
|
|
50739
|
+
* to fall back to the standard approach rather than risk incorrect optimization.
|
|
50740
|
+
*
|
|
50741
|
+
* @param constraintExpr The constraint expression to analyze
|
|
50742
|
+
* @param varNames The variable names in scope
|
|
50743
|
+
* @returns Pattern info if detected, null otherwise
|
|
50744
|
+
*/
|
|
50745
|
+
function detectNumericComparisonPattern(constraintExpr, varNames) {
|
|
50746
|
+
// Parse the expression text to look for simple comparison patterns
|
|
50747
|
+
const exprText = constraintExpr.text;
|
|
50748
|
+
// Check if this is a simple binary comparison between two variables
|
|
50749
|
+
// Pattern: <varName1><compareOp><varName2> (no whitespace in text representation)
|
|
50750
|
+
// Note: The AST text property concatenates tokens without whitespace
|
|
50751
|
+
// Try to extract comparison operator and operands
|
|
50752
|
+
for (const leftVar of varNames) {
|
|
50753
|
+
for (const rightVar of varNames) {
|
|
50754
|
+
if (leftVar === rightVar)
|
|
50755
|
+
continue;
|
|
50756
|
+
// Check for different comparison operators (whitespace already removed in text)
|
|
50757
|
+
if (exprText === `${leftVar}<${rightVar}`) {
|
|
50758
|
+
return { type: 'less_than', leftVar, rightVar };
|
|
50759
|
+
}
|
|
50760
|
+
if (exprText === `${leftVar}>${rightVar}`) {
|
|
50761
|
+
return { type: 'greater_than', leftVar, rightVar };
|
|
50762
|
+
}
|
|
50763
|
+
if (exprText === `${leftVar}<=${rightVar}`) {
|
|
50764
|
+
return { type: 'less_equal', leftVar, rightVar };
|
|
50765
|
+
}
|
|
50766
|
+
if (exprText === `${leftVar}>=${rightVar}`) {
|
|
50767
|
+
return { type: 'greater_equal', leftVar, rightVar };
|
|
50768
|
+
}
|
|
50769
|
+
if (exprText === `${leftVar}!=${rightVar}`) {
|
|
50770
|
+
return { type: 'not_equal', leftVar, rightVar };
|
|
50771
|
+
}
|
|
50772
|
+
// Also check for negated equality: not a = b
|
|
50773
|
+
if (exprText === `not${leftVar}=${rightVar}`) {
|
|
50774
|
+
return { type: 'not_equal', leftVar, rightVar };
|
|
50775
|
+
}
|
|
50776
|
+
}
|
|
50777
|
+
}
|
|
50778
|
+
return null;
|
|
50779
|
+
}
|
|
50780
|
+
/**
|
|
50781
|
+
* Checks if all values in the sets are numbers (or tuples containing single numbers).
|
|
50782
|
+
* This is required for numeric comparison optimization to be applicable.
|
|
50783
|
+
*/
|
|
50784
|
+
function areAllNumericSets(sets) {
|
|
50785
|
+
for (const set of sets) {
|
|
50786
|
+
for (const tuple of set) {
|
|
50787
|
+
if (tuple.length !== 1)
|
|
50788
|
+
return false;
|
|
50789
|
+
if (typeof tuple[0] !== 'number')
|
|
50790
|
+
return false;
|
|
50791
|
+
}
|
|
50792
|
+
}
|
|
50793
|
+
return true;
|
|
50794
|
+
}
|
|
50795
|
+
/**
|
|
50796
|
+
* Extracts numbers from tuples that contain single numbers.
|
|
50797
|
+
* Precondition: This should only be called after areAllNumericSets validation.
|
|
50798
|
+
*/
|
|
50799
|
+
function extractNumbers(tuples) {
|
|
50800
|
+
return tuples.map(t => t[0]);
|
|
50801
|
+
}
|
|
50802
|
+
/**
|
|
50803
|
+
* Generates optimized combinations for numeric comparison constraints.
|
|
50804
|
+
* Instead of generating all combinations and filtering, generates only valid ones.
|
|
50805
|
+
*
|
|
50806
|
+
* @param varNames Variable names in order
|
|
50807
|
+
* @param quantifiedSets The sets each variable ranges over
|
|
50808
|
+
* @param pattern The detected numeric comparison pattern
|
|
50809
|
+
* @returns Only the tuples that satisfy the constraint
|
|
50810
|
+
*/
|
|
50811
|
+
function generateOptimizedNumericCombinations(varNames, quantifiedSets, pattern) {
|
|
50812
|
+
// Map variable names to their indices and value sets
|
|
50813
|
+
const varIndexMap = new Map();
|
|
50814
|
+
varNames.forEach((name, idx) => {
|
|
50815
|
+
varIndexMap.set(name, idx);
|
|
50816
|
+
});
|
|
50817
|
+
const leftIdx = varIndexMap.get(pattern.leftVar);
|
|
50818
|
+
const rightIdx = varIndexMap.get(pattern.rightVar);
|
|
50819
|
+
if (leftIdx === undefined || rightIdx === undefined) {
|
|
50820
|
+
// Pattern variables don't match our variable set - this shouldn't happen
|
|
50821
|
+
// if detectNumericComparisonPattern is working correctly, but throw an error
|
|
50822
|
+
// to prevent incorrect results (caller should have validated pattern matches variables)
|
|
50823
|
+
throw new Error(`Internal error: Pattern variables ${pattern.leftVar}, ${pattern.rightVar} not found in variable list`);
|
|
50824
|
+
}
|
|
50825
|
+
// Extract numeric values
|
|
50826
|
+
const leftNumbers = extractNumbers(quantifiedSets[leftIdx]);
|
|
50827
|
+
const rightNumbers = extractNumbers(quantifiedSets[rightIdx]);
|
|
50828
|
+
// For other variables, we still need all combinations
|
|
50829
|
+
const otherVars = [];
|
|
50830
|
+
const otherSets = [];
|
|
50831
|
+
for (let i = 0; i < varNames.length; i++) {
|
|
50832
|
+
if (i !== leftIdx && i !== rightIdx) {
|
|
50833
|
+
otherVars.push(i);
|
|
50834
|
+
otherSets.push(extractNumbers(quantifiedSets[i]));
|
|
50835
|
+
}
|
|
50836
|
+
}
|
|
50837
|
+
const result = [];
|
|
50838
|
+
// Generate combinations based on the comparison type
|
|
50839
|
+
const generatePairs = (compareFunc) => {
|
|
50840
|
+
for (const leftVal of leftNumbers) {
|
|
50841
|
+
for (const rightVal of rightNumbers) {
|
|
50842
|
+
if (compareFunc(leftVal, rightVal)) {
|
|
50843
|
+
// This pair satisfies the constraint
|
|
50844
|
+
// Now combine with all other variables if any
|
|
50845
|
+
if (otherVars.length === 0) {
|
|
50846
|
+
// Simple case: only two variables
|
|
50847
|
+
const tuple = new Array(varNames.length);
|
|
50848
|
+
tuple[leftIdx] = leftVal;
|
|
50849
|
+
tuple[rightIdx] = rightVal;
|
|
50850
|
+
result.push(tuple);
|
|
50851
|
+
}
|
|
50852
|
+
else {
|
|
50853
|
+
// Need to generate cartesian product with other variables
|
|
50854
|
+
const otherCombos = cartesianProduct(otherSets);
|
|
50855
|
+
for (const otherCombo of otherCombos) {
|
|
50856
|
+
const tuple = new Array(varNames.length);
|
|
50857
|
+
tuple[leftIdx] = leftVal;
|
|
50858
|
+
tuple[rightIdx] = rightVal;
|
|
50859
|
+
for (let i = 0; i < otherVars.length; i++) {
|
|
50860
|
+
tuple[otherVars[i]] = otherCombo[i];
|
|
50861
|
+
}
|
|
50862
|
+
result.push(tuple);
|
|
50863
|
+
}
|
|
50864
|
+
}
|
|
50865
|
+
}
|
|
50866
|
+
}
|
|
50867
|
+
}
|
|
50868
|
+
};
|
|
50869
|
+
switch (pattern.type) {
|
|
50870
|
+
case 'less_than':
|
|
50871
|
+
generatePairs((a, b) => a < b);
|
|
50872
|
+
break;
|
|
50873
|
+
case 'greater_than':
|
|
50874
|
+
generatePairs((a, b) => a > b);
|
|
50875
|
+
break;
|
|
50876
|
+
case 'less_equal':
|
|
50877
|
+
generatePairs((a, b) => a <= b);
|
|
50878
|
+
break;
|
|
50879
|
+
case 'greater_equal':
|
|
50880
|
+
generatePairs((a, b) => a >= b);
|
|
50881
|
+
break;
|
|
50882
|
+
case 'not_equal':
|
|
50883
|
+
generatePairs((a, b) => a !== b);
|
|
50884
|
+
break;
|
|
50885
|
+
default:
|
|
50886
|
+
// Unknown pattern, return empty (will fall back to standard approach)
|
|
50887
|
+
return [];
|
|
50888
|
+
}
|
|
50889
|
+
return result;
|
|
50890
|
+
}
|
|
50891
|
+
/**
|
|
50892
|
+
* Helper to generate cartesian product of number arrays
|
|
50893
|
+
*/
|
|
50894
|
+
function cartesianProduct(arrays) {
|
|
50895
|
+
if (arrays.length === 0)
|
|
50896
|
+
return [[]];
|
|
50897
|
+
if (arrays.some(arr => arr.length === 0))
|
|
50898
|
+
return [];
|
|
50899
|
+
let result = [[]];
|
|
50900
|
+
for (const arr of arrays) {
|
|
50901
|
+
const newResult = [];
|
|
50902
|
+
for (const existing of result) {
|
|
50903
|
+
for (const value of arr) {
|
|
50904
|
+
newResult.push([...existing, value]);
|
|
50905
|
+
}
|
|
50906
|
+
}
|
|
50907
|
+
result = newResult;
|
|
50908
|
+
}
|
|
50909
|
+
return result;
|
|
50910
|
+
}
|
|
50911
|
+
|
|
50912
|
+
|
|
50660
50913
|
/***/ }),
|
|
50661
50914
|
|
|
50662
50915
|
/***/ "./src/errorListener.ts":
|
package/package.json
CHANGED