@lowgular/code-graph 0.1.1 → 0.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/bin/cli.js +2458 -5
- package/lib.js +2430 -0
- package/package.json +3 -2
- package/code-graph-v2/code.graph.js +0 -37
- package/code-graph-v2/config-from-query.js +0 -131
- package/code-graph-v2/extractors/extractor.js +0 -27
- package/code-graph-v2/extractors/index.js +0 -1
- package/code-graph-v2/graph-builder/code-graph.builder.js +0 -49
- package/code-graph-v2/graph-builder/index.js +0 -1
- package/code-graph-v2/graph-builder/node.processor.js +0 -22
- package/code-graph-v2/graph-builder/relationship.processor.js +0 -55
- package/code-graph-v2/graph-builder/type.processor.js +0 -21
- package/code-graph-v2/index.js +0 -4
- package/code-graph-v2/tools/build-code-graph.tool.js +0 -19
- package/code-graph-v2/utils.js +0 -34
- package/codegular/index.js +0 -5
- package/codegular/node.js +0 -71
- package/codegular/program.js +0 -100
- package/codegular/string.js +0 -121
- package/codegular/type-checker.js +0 -133
- package/codegular/type.js +0 -356
- package/codegular/utils.js +0 -335
- package/cypher/index.js +0 -1
- package/cypher/lib/executor/condition-evaluator.js +0 -135
- package/cypher/lib/executor/executor.js +0 -60
- package/cypher/lib/executor/graph.js +0 -0
- package/cypher/lib/executor/match-engine.js +0 -130
- package/cypher/lib/executor/pattern-matcher.js +0 -86
- package/cypher/lib/executor/relationship-navigator.js +0 -41
- package/cypher/lib/executor/result-formatter.js +0 -149
- package/cypher/lib/executor/traverse-engine.js +0 -141
- package/cypher/lib/executor/utils.js +0 -14
- package/cypher/lib/graph.stub.js +0 -38
- package/cypher/lib/index.js +0 -32
- package/cypher/lib/lexer.js +0 -376
- package/cypher/lib/parser.js +0 -586
- package/cypher/lib/validator/query-validator.js +0 -75
- package/cypher/lib/validator/supported-features.config.js +0 -83
- package/cypher/lib/validator/unsupported-features.config.js +0 -124
- package/cypher-cli.js +0 -41
- package/infra/code-graph.js +0 -147
- package/main.js +0 -0
- package/resources-cli.js +0 -75
- package/run-cli.js +0 -43
package/codegular/utils.js
DELETED
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
import * as path from "path";
|
|
2
|
-
import * as ts from "typescript";
|
|
3
|
-
import { getNameByIdentifier } from "./string.js";
|
|
4
|
-
const { forEachChild, SyntaxKind } = ts;
|
|
5
|
-
function escapeValue(value) {
|
|
6
|
-
return value.replace(/^['"`]+/i, "").replace(/['"`]+$/i, "");
|
|
7
|
-
}
|
|
8
|
-
const parseObjectLiteralString = (objectLiteralString) => {
|
|
9
|
-
const result = {};
|
|
10
|
-
if (!objectLiteralString || typeof objectLiteralString !== "string") {
|
|
11
|
-
return result;
|
|
12
|
-
}
|
|
13
|
-
const code = `const _ = ${objectLiteralString}`;
|
|
14
|
-
const sourceFile = ts.createSourceFile(
|
|
15
|
-
"temp.ts",
|
|
16
|
-
code,
|
|
17
|
-
ts.ScriptTarget.Latest,
|
|
18
|
-
true
|
|
19
|
-
);
|
|
20
|
-
const findObjectLiteral = (node) => {
|
|
21
|
-
if (ts.isObjectLiteralExpression(node)) {
|
|
22
|
-
return node;
|
|
23
|
-
}
|
|
24
|
-
return ts.forEachChild(node, findObjectLiteral);
|
|
25
|
-
};
|
|
26
|
-
const objectLiteral = findObjectLiteral(sourceFile);
|
|
27
|
-
if (!objectLiteral) {
|
|
28
|
-
return result;
|
|
29
|
-
}
|
|
30
|
-
for (const property of objectLiteral.properties) {
|
|
31
|
-
if (ts.isPropertyAssignment(property)) {
|
|
32
|
-
let key;
|
|
33
|
-
if (ts.isIdentifier(property.name)) {
|
|
34
|
-
key = property.name.text;
|
|
35
|
-
} else if (ts.isStringLiteral(property.name)) {
|
|
36
|
-
key = property.name.text;
|
|
37
|
-
} else if (ts.isComputedPropertyName(property.name)) {
|
|
38
|
-
key = property.name.getText(sourceFile);
|
|
39
|
-
} else {
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
const value = property.initializer.getText(sourceFile);
|
|
43
|
-
result[key] = value;
|
|
44
|
-
} else if (ts.isShorthandPropertyAssignment(property)) {
|
|
45
|
-
const key = property.name.text;
|
|
46
|
-
result[key] = key;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return result;
|
|
50
|
-
};
|
|
51
|
-
function findFirstChildByKind(node, kind) {
|
|
52
|
-
let element;
|
|
53
|
-
forEachChild(node, (child) => {
|
|
54
|
-
if (child.kind !== kind || !!element) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
element = child;
|
|
58
|
-
});
|
|
59
|
-
return element;
|
|
60
|
-
}
|
|
61
|
-
function peelCallTarget(expr) {
|
|
62
|
-
const chain = [];
|
|
63
|
-
let hasOptional = false;
|
|
64
|
-
let e = expr;
|
|
65
|
-
let baseAccess;
|
|
66
|
-
while (true) {
|
|
67
|
-
if (ts.isPropertyAccessExpression(e)) {
|
|
68
|
-
chain.unshift(e.name.text);
|
|
69
|
-
hasOptional = hasOptional || !!e.questionDotToken;
|
|
70
|
-
baseAccess = e;
|
|
71
|
-
e = e.expression;
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (ts.isElementAccessExpression(e)) {
|
|
75
|
-
const arg = e.argumentExpression;
|
|
76
|
-
const key = ts.isStringLiteral(arg) || ts.isIdentifier(arg) ? arg.text : "<computed>";
|
|
77
|
-
chain.unshift(key);
|
|
78
|
-
hasOptional = hasOptional || !!e.questionDotToken;
|
|
79
|
-
baseAccess = e;
|
|
80
|
-
e = e.expression;
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
root: e,
|
|
87
|
-
basePropAccess: baseAccess,
|
|
88
|
-
chain,
|
|
89
|
-
invoked: chain.length ? chain[chain.length - 1] : void 0,
|
|
90
|
-
hasOptional
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
function getAncestorsBy(node, predicate) {
|
|
94
|
-
let currentNode = node;
|
|
95
|
-
const nodes = [];
|
|
96
|
-
while (currentNode) {
|
|
97
|
-
if (predicate(currentNode)) {
|
|
98
|
-
nodes.push(currentNode);
|
|
99
|
-
}
|
|
100
|
-
const parent = currentNode?.parent;
|
|
101
|
-
if (!parent) {
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
currentNode = parent;
|
|
105
|
-
}
|
|
106
|
-
return nodes;
|
|
107
|
-
}
|
|
108
|
-
function getAncestorsByKind(node, kind) {
|
|
109
|
-
return getAncestorsBy(node, (node2) => node2.kind === kind);
|
|
110
|
-
}
|
|
111
|
-
function getAncestorByKind(node, kinds) {
|
|
112
|
-
let currentNode = node;
|
|
113
|
-
while (currentNode) {
|
|
114
|
-
if (kinds.includes(currentNode.kind)) {
|
|
115
|
-
return currentNode;
|
|
116
|
-
}
|
|
117
|
-
currentNode = currentNode.parent;
|
|
118
|
-
}
|
|
119
|
-
return void 0;
|
|
120
|
-
}
|
|
121
|
-
function getAllExpressions(node) {
|
|
122
|
-
const expressions = [];
|
|
123
|
-
function visit(currentNode) {
|
|
124
|
-
if (!currentNode)
|
|
125
|
-
return;
|
|
126
|
-
if (ts.isExpression(currentNode)) {
|
|
127
|
-
expressions.push(currentNode);
|
|
128
|
-
}
|
|
129
|
-
ts.forEachChild(currentNode, visit);
|
|
130
|
-
}
|
|
131
|
-
visit(node);
|
|
132
|
-
return expressions;
|
|
133
|
-
}
|
|
134
|
-
function getDescendantsBy(node, predicate) {
|
|
135
|
-
let foundElements = [];
|
|
136
|
-
let queue = [node];
|
|
137
|
-
while (queue.length > 0) {
|
|
138
|
-
const currentNode = queue.shift();
|
|
139
|
-
const children = Array.from(
|
|
140
|
-
currentNode?.getChildren(node.getSourceFile()) || []
|
|
141
|
-
);
|
|
142
|
-
foundElements = [
|
|
143
|
-
...foundElements,
|
|
144
|
-
...children.filter((node2) => predicate(node2))
|
|
145
|
-
];
|
|
146
|
-
queue = [...queue, ...children];
|
|
147
|
-
}
|
|
148
|
-
return foundElements;
|
|
149
|
-
}
|
|
150
|
-
function getDescendantsByKind(node, kind) {
|
|
151
|
-
return getDescendantsBy(node, (node2) => node2.kind === kind);
|
|
152
|
-
}
|
|
153
|
-
function getDescendantsByKinds(node, kinds) {
|
|
154
|
-
return getDescendantsBy(node, (node2) => kinds.includes(node2.kind));
|
|
155
|
-
}
|
|
156
|
-
function getDescendantByName(node, name) {
|
|
157
|
-
return getDescendantsBy(
|
|
158
|
-
node,
|
|
159
|
-
(node2) => getNameByIdentifier(node2) === name
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
function findFirstChildByKinds(node, kinds) {
|
|
163
|
-
let element;
|
|
164
|
-
forEachChild(node, (child) => {
|
|
165
|
-
if (!kinds.includes(child.kind) || !!element) {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
element = child;
|
|
169
|
-
});
|
|
170
|
-
return element;
|
|
171
|
-
}
|
|
172
|
-
function findChildrenByKinds(node, kinds) {
|
|
173
|
-
const elements = [];
|
|
174
|
-
forEachChild(node, (child) => {
|
|
175
|
-
if (!kinds.includes(child.kind)) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
elements.push(child);
|
|
179
|
-
});
|
|
180
|
-
return elements;
|
|
181
|
-
}
|
|
182
|
-
function findChildrenByKind(node, kind) {
|
|
183
|
-
return findChildrenByKinds(node, [kind]);
|
|
184
|
-
}
|
|
185
|
-
function findChildrenByName(sourceFile, node, name) {
|
|
186
|
-
const elements = [];
|
|
187
|
-
forEachChild(node, (child) => {
|
|
188
|
-
const [nameNode] = findChildrenByKind(child, SyntaxKind.Identifier);
|
|
189
|
-
if (nameNode?.getText(sourceFile) !== name) {
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
elements.push(child);
|
|
193
|
-
});
|
|
194
|
-
return elements;
|
|
195
|
-
}
|
|
196
|
-
function findChildByName(sourceFile, node, name) {
|
|
197
|
-
let element;
|
|
198
|
-
forEachChild(node, (child) => {
|
|
199
|
-
const [nameNode] = findChildrenByKind(child, SyntaxKind.Identifier);
|
|
200
|
-
if (nameNode?.getText(sourceFile) !== name) {
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
element = child;
|
|
204
|
-
});
|
|
205
|
-
return element;
|
|
206
|
-
}
|
|
207
|
-
function findChildByIndex(node, index) {
|
|
208
|
-
let element;
|
|
209
|
-
let i = 0;
|
|
210
|
-
forEachChild(node, (child) => {
|
|
211
|
-
if (i !== index) {
|
|
212
|
-
i++;
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
element = child;
|
|
216
|
-
});
|
|
217
|
-
return element;
|
|
218
|
-
}
|
|
219
|
-
function consoleAllChildren(sourceFile, node) {
|
|
220
|
-
forEachChild(node, (child) => {
|
|
221
|
-
console.log(child.kind, child.getText(sourceFile));
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
function parseArrayLiteralExpression(sourceFile, node) {
|
|
225
|
-
return findChildrenByKind(node, SyntaxKind.ObjectLiteralExpression).map(
|
|
226
|
-
(obj) => {
|
|
227
|
-
return parseObjectLiteralExpression(sourceFile, obj);
|
|
228
|
-
}
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
function parseObjectLiteralExpression(sourceFile, node) {
|
|
232
|
-
return findChildrenByKind(node, SyntaxKind.PropertyAssignment).reduce(
|
|
233
|
-
(state, currentNode) => {
|
|
234
|
-
const [property, _, value] = Array.from(
|
|
235
|
-
currentNode?.getChildren(sourceFile) || []
|
|
236
|
-
);
|
|
237
|
-
if (value.kind === SyntaxKind.ObjectLiteralExpression) {
|
|
238
|
-
const obj = parseObjectLiteralExpression(sourceFile, value);
|
|
239
|
-
return {
|
|
240
|
-
...state,
|
|
241
|
-
...Object.keys(obj).reduce((s, k) => {
|
|
242
|
-
return {
|
|
243
|
-
...s,
|
|
244
|
-
[property.getText(sourceFile) + "." + k]: escapeValue(obj[k])
|
|
245
|
-
};
|
|
246
|
-
}, {})
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
return {
|
|
250
|
-
...state,
|
|
251
|
-
[property.getText(sourceFile)]: escapeValue(value.getText(sourceFile))
|
|
252
|
-
};
|
|
253
|
-
},
|
|
254
|
-
{}
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
const isComment = (content) => content.match(/^\s*\/\/.*/) !== null;
|
|
258
|
-
const extractCommentContent = (content) => content.replace(/^\s*\/\/\s*/, "");
|
|
259
|
-
const getComments = (nodeContent) => {
|
|
260
|
-
const blockLines = nodeContent.split("\r\n");
|
|
261
|
-
const comments = blockLines.filter(isComment).map(extractCommentContent);
|
|
262
|
-
return comments;
|
|
263
|
-
};
|
|
264
|
-
function findTypeDeclarationByName(sourceFile, typeName) {
|
|
265
|
-
let result;
|
|
266
|
-
function visit(node) {
|
|
267
|
-
if (ts.isInterfaceDeclaration(node) || ts.isClassDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
|
|
268
|
-
const name = node.name?.getText(sourceFile);
|
|
269
|
-
if (name === typeName) {
|
|
270
|
-
result = node;
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
ts.forEachChild(node, visit);
|
|
275
|
-
}
|
|
276
|
-
visit(sourceFile);
|
|
277
|
-
return result;
|
|
278
|
-
}
|
|
279
|
-
const getSolutionSourceFiles = (program) => {
|
|
280
|
-
const allSourceFiles = program.getSourceFiles();
|
|
281
|
-
const sourceFiles = allSourceFiles.filter((sourceFile) => {
|
|
282
|
-
const fileName = sourceFile.fileName;
|
|
283
|
-
if (fileName.includes("node_modules")) {
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
286
|
-
if (fileName.endsWith(".d.ts")) {
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
return true;
|
|
290
|
-
});
|
|
291
|
-
return sourceFiles;
|
|
292
|
-
};
|
|
293
|
-
function readSourceFiles(tsConfigPath) {
|
|
294
|
-
const configFile = ts.readConfigFile(tsConfigPath, ts.sys.readFile);
|
|
295
|
-
if (configFile.error) {
|
|
296
|
-
throw new Error("Error reading tsconfig at " + tsConfigPath);
|
|
297
|
-
}
|
|
298
|
-
const parsedConfig = ts.parseJsonConfigFileContent(
|
|
299
|
-
configFile.config,
|
|
300
|
-
ts.sys,
|
|
301
|
-
path.dirname(tsConfigPath)
|
|
302
|
-
);
|
|
303
|
-
const program = ts.createProgram(
|
|
304
|
-
parsedConfig.fileNames,
|
|
305
|
-
parsedConfig.options
|
|
306
|
-
);
|
|
307
|
-
return { sourceFiles: getSolutionSourceFiles(program), program };
|
|
308
|
-
}
|
|
309
|
-
export {
|
|
310
|
-
consoleAllChildren,
|
|
311
|
-
escapeValue,
|
|
312
|
-
findChildByIndex,
|
|
313
|
-
findChildByName,
|
|
314
|
-
findChildrenByKind,
|
|
315
|
-
findChildrenByKinds,
|
|
316
|
-
findChildrenByName,
|
|
317
|
-
findFirstChildByKind,
|
|
318
|
-
findFirstChildByKinds,
|
|
319
|
-
findTypeDeclarationByName,
|
|
320
|
-
getAllExpressions,
|
|
321
|
-
getAncestorByKind,
|
|
322
|
-
getAncestorsBy,
|
|
323
|
-
getAncestorsByKind,
|
|
324
|
-
getComments,
|
|
325
|
-
getDescendantByName,
|
|
326
|
-
getDescendantsBy,
|
|
327
|
-
getDescendantsByKind,
|
|
328
|
-
getDescendantsByKinds,
|
|
329
|
-
getSolutionSourceFiles,
|
|
330
|
-
parseArrayLiteralExpression,
|
|
331
|
-
parseObjectLiteralExpression,
|
|
332
|
-
parseObjectLiteralString,
|
|
333
|
-
peelCallTarget,
|
|
334
|
-
readSourceFiles
|
|
335
|
-
};
|
package/cypher/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./lib/index.js";
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
class ConditionEvaluator {
|
|
2
|
-
// @TODO: apply where at match level
|
|
3
|
-
/**
|
|
4
|
-
* Apply WHERE clause filtering to matches
|
|
5
|
-
*/
|
|
6
|
-
applyWhereClauseIfNeeded(matches, where) {
|
|
7
|
-
return matches.filter((match) => {
|
|
8
|
-
const conditionResults = where.conditions.map(
|
|
9
|
-
(condition) => this.evaluateCondition(match, condition)
|
|
10
|
-
);
|
|
11
|
-
const logic = where.logic || "AND";
|
|
12
|
-
if (logic === "AND") {
|
|
13
|
-
return conditionResults.every((result) => result === true);
|
|
14
|
-
} else {
|
|
15
|
-
return conditionResults.some((result) => result === true);
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
// @fixme move to utils
|
|
20
|
-
/**
|
|
21
|
-
* Escape special regex characters in a string
|
|
22
|
-
*/
|
|
23
|
-
escapeRegex(str) {
|
|
24
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Evaluate a single WHERE condition
|
|
28
|
-
* Supports flat properties only (no nesting, like Neo4j Cypher)
|
|
29
|
-
* Handles null values from optional matches (null values fail the condition)
|
|
30
|
-
*/
|
|
31
|
-
evaluateCondition(match, condition) {
|
|
32
|
-
const value = match[condition.variable];
|
|
33
|
-
if ((condition.operator === "IS NULL" || condition.operator === "IS NOT NULL") && !condition.property) {
|
|
34
|
-
const isNull = value === null || value === void 0;
|
|
35
|
-
return condition.operator === "IS NULL" ? isNull : !isNull;
|
|
36
|
-
}
|
|
37
|
-
if (!value || value === null) {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
let baseObject;
|
|
41
|
-
if ("id" in value) {
|
|
42
|
-
baseObject = value;
|
|
43
|
-
} else if ("type" in value && "source" in value) {
|
|
44
|
-
baseObject = value;
|
|
45
|
-
} else {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
let actualValue;
|
|
49
|
-
if (!condition.property) {
|
|
50
|
-
actualValue = baseObject;
|
|
51
|
-
} else {
|
|
52
|
-
actualValue = baseObject[condition.property];
|
|
53
|
-
if (actualValue === void 0) {
|
|
54
|
-
if (condition.operator === "IS NULL" || condition.operator === "IS NOT NULL") {
|
|
55
|
-
actualValue = void 0;
|
|
56
|
-
} else {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
const conditionValue = condition.value;
|
|
62
|
-
switch (condition.operator) {
|
|
63
|
-
case "=":
|
|
64
|
-
return actualValue === conditionValue;
|
|
65
|
-
case "!=":
|
|
66
|
-
return actualValue !== conditionValue;
|
|
67
|
-
case ">":
|
|
68
|
-
return actualValue > conditionValue;
|
|
69
|
-
case "<":
|
|
70
|
-
return actualValue < conditionValue;
|
|
71
|
-
case ">=":
|
|
72
|
-
return actualValue >= conditionValue;
|
|
73
|
-
case "<=":
|
|
74
|
-
return actualValue <= conditionValue;
|
|
75
|
-
case "IS NULL":
|
|
76
|
-
return actualValue === null || actualValue === void 0;
|
|
77
|
-
case "IS NOT NULL":
|
|
78
|
-
return actualValue !== null && actualValue !== void 0;
|
|
79
|
-
case "IN":
|
|
80
|
-
if (Array.isArray(conditionValue)) {
|
|
81
|
-
if (conditionValue.length > 0 && typeof conditionValue[0] === "object" && conditionValue[0] !== null) {
|
|
82
|
-
const extractedValues = conditionValue.map((item) => item?.name).filter((name) => name !== void 0);
|
|
83
|
-
return extractedValues.includes(actualValue);
|
|
84
|
-
}
|
|
85
|
-
return conditionValue.includes(actualValue);
|
|
86
|
-
}
|
|
87
|
-
return false;
|
|
88
|
-
case "NOT IN":
|
|
89
|
-
if (Array.isArray(conditionValue)) {
|
|
90
|
-
if (conditionValue.length > 0 && typeof conditionValue[0] === "object" && conditionValue[0] !== null) {
|
|
91
|
-
const extractedValues = conditionValue.map((item) => item?.name).filter((name) => name !== void 0);
|
|
92
|
-
return !extractedValues.includes(actualValue);
|
|
93
|
-
}
|
|
94
|
-
return !conditionValue.includes(actualValue);
|
|
95
|
-
}
|
|
96
|
-
return true;
|
|
97
|
-
case "STARTS WITH":
|
|
98
|
-
return this.testRegex(
|
|
99
|
-
new RegExp(`^${this.escapeRegex(conditionValue)}`),
|
|
100
|
-
actualValue
|
|
101
|
-
);
|
|
102
|
-
case "ENDS WITH":
|
|
103
|
-
return this.testRegex(
|
|
104
|
-
new RegExp(`${this.escapeRegex(conditionValue)}$`),
|
|
105
|
-
actualValue
|
|
106
|
-
);
|
|
107
|
-
case "CONTAINS":
|
|
108
|
-
return this.testRegex(
|
|
109
|
-
new RegExp(this.escapeRegex(conditionValue)),
|
|
110
|
-
actualValue
|
|
111
|
-
);
|
|
112
|
-
case "=~":
|
|
113
|
-
return this.testRegex(new RegExp(conditionValue), actualValue);
|
|
114
|
-
default:
|
|
115
|
-
throw new Error(`Unsupported operator: ${condition.operator}`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
testRegex(regexp, actualValue) {
|
|
119
|
-
if (actualValue === null || actualValue === void 0) {
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
122
|
-
if (typeof actualValue !== "string" && typeof actualValue !== "number" && typeof actualValue !== "boolean") {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
const regexStr = typeof actualValue === "string" ? actualValue : String(actualValue);
|
|
126
|
-
try {
|
|
127
|
-
return regexp.test(regexStr);
|
|
128
|
-
} catch {
|
|
129
|
-
return false;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
export {
|
|
134
|
-
ConditionEvaluator
|
|
135
|
-
};
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { ConditionEvaluator } from "./condition-evaluator.js";
|
|
2
|
-
import { MatchEngine } from "./match-engine.js";
|
|
3
|
-
import { PatternMatcher } from "./pattern-matcher.js";
|
|
4
|
-
import { RelationshipNavigator } from "./relationship-navigator.js";
|
|
5
|
-
import { ResultFormatter } from "./result-formatter.js";
|
|
6
|
-
import { TraverseEngine } from "./traverse-engine.js";
|
|
7
|
-
class CypherExecutor {
|
|
8
|
-
constructor(graph) {
|
|
9
|
-
this.graph = graph;
|
|
10
|
-
this.conditionEvaluator = new ConditionEvaluator();
|
|
11
|
-
const patternMatcher = new PatternMatcher();
|
|
12
|
-
const relationshipNavigator = new RelationshipNavigator(patternMatcher);
|
|
13
|
-
const traverseEngine = new TraverseEngine(
|
|
14
|
-
patternMatcher,
|
|
15
|
-
relationshipNavigator
|
|
16
|
-
);
|
|
17
|
-
this.matchEngine = new MatchEngine(patternMatcher, traverseEngine);
|
|
18
|
-
this.resultFormatter = new ResultFormatter();
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Execute a parsed Cypher query
|
|
22
|
-
* @param extractData - If true (default), returns node.data. If false, returns full GraphNode objects.
|
|
23
|
-
* For edges, always returns the full GraphEdge object (edges have no data property).
|
|
24
|
-
* Returns results where keys are variable names and values are either node data or edge objects
|
|
25
|
-
*/
|
|
26
|
-
execute(query) {
|
|
27
|
-
if (query.statements.length === 0) {
|
|
28
|
-
throw new Error("Query must have at least one statement");
|
|
29
|
-
}
|
|
30
|
-
if (query.statements[query.statements.length - 1].type !== "ReturnStatement") {
|
|
31
|
-
throw new Error("MATCH Query must have a RETURN statement");
|
|
32
|
-
}
|
|
33
|
-
let results = [];
|
|
34
|
-
for (const statement of query.statements) {
|
|
35
|
-
if (statement.type === "MatchStatement") {
|
|
36
|
-
results = this.matchEngine.processMatchStatement(
|
|
37
|
-
this.graph,
|
|
38
|
-
statement,
|
|
39
|
-
results
|
|
40
|
-
);
|
|
41
|
-
} else if (statement.type === "WhereStatement") {
|
|
42
|
-
results = this.conditionEvaluator.applyWhereClauseIfNeeded(
|
|
43
|
-
results,
|
|
44
|
-
statement
|
|
45
|
-
);
|
|
46
|
-
} else if (statement.type === "OrderByStatement") {
|
|
47
|
-
results = this.resultFormatter.sortResults(results, statement);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
const returnStatement = query.statements[query.statements.length - 1];
|
|
51
|
-
let formattedResults = this.resultFormatter.formatResults(
|
|
52
|
-
results,
|
|
53
|
-
returnStatement
|
|
54
|
-
);
|
|
55
|
-
return formattedResults;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
export {
|
|
59
|
-
CypherExecutor
|
|
60
|
-
};
|
|
File without changes
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { Annonymizer } from "./utils.js";
|
|
2
|
-
class MatchEngine {
|
|
3
|
-
constructor(patternMatcher, traverseEngine) {
|
|
4
|
-
this.patternMatcher = patternMatcher;
|
|
5
|
-
this.traverseEngine = traverseEngine;
|
|
6
|
-
this.annonymizer = new Annonymizer();
|
|
7
|
-
}
|
|
8
|
-
processMatchStatement(graph, match, existingResults = []) {
|
|
9
|
-
if (match.patterns.length === 0)
|
|
10
|
-
return existingResults;
|
|
11
|
-
const starting = match.patterns[0];
|
|
12
|
-
const allResults = [];
|
|
13
|
-
this.patternMatcher.filterMatchingNodes(graph, starting).forEach((node) => {
|
|
14
|
-
const matches = this.traverseEngine.traverseRelationships(
|
|
15
|
-
graph,
|
|
16
|
-
node,
|
|
17
|
-
starting.variable || this.annonymizer.generate(node),
|
|
18
|
-
match.patterns.slice(1),
|
|
19
|
-
match.relationships
|
|
20
|
-
);
|
|
21
|
-
allResults.push(...matches);
|
|
22
|
-
});
|
|
23
|
-
if (existingResults.length > 0) {
|
|
24
|
-
if (match.optional) {
|
|
25
|
-
return this.mergeOptionalResults(existingResults, allResults, match);
|
|
26
|
-
} else {
|
|
27
|
-
return this.mergeResults(existingResults, allResults);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return allResults;
|
|
31
|
-
}
|
|
32
|
-
mergeResults(existingResults, newResults) {
|
|
33
|
-
if (existingResults.length === 0) {
|
|
34
|
-
return newResults;
|
|
35
|
-
}
|
|
36
|
-
const mergedResults = [];
|
|
37
|
-
for (const existingResult of existingResults) {
|
|
38
|
-
for (const newResult of newResults) {
|
|
39
|
-
if (this.canMergeResult(existingResult, newResult)) {
|
|
40
|
-
mergedResults.push({ ...existingResult, ...newResult });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return mergedResults;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Merge optional match results with existing results (left outer join)
|
|
48
|
-
* - Keeps all existing results
|
|
49
|
-
* - Merges optional results where variables overlap
|
|
50
|
-
* - Adds nulls for optional variables when no match is found
|
|
51
|
-
*/
|
|
52
|
-
mergeOptionalResults(existingResults, optionalResults, match) {
|
|
53
|
-
if (existingResults.length === 0) {
|
|
54
|
-
return optionalResults;
|
|
55
|
-
}
|
|
56
|
-
const optionalVarNames = /* @__PURE__ */ new Set();
|
|
57
|
-
for (const pattern of match.patterns) {
|
|
58
|
-
if (pattern.variable) {
|
|
59
|
-
optionalVarNames.add(pattern.variable);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (match.relationships) {
|
|
63
|
-
for (const rel of match.relationships) {
|
|
64
|
-
if (rel.variable) {
|
|
65
|
-
optionalVarNames.add(rel.variable);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
if (optionalResults.length === 0) {
|
|
70
|
-
return existingResults.map((result) => {
|
|
71
|
-
const resultWithNulls = { ...result };
|
|
72
|
-
for (const varName of optionalVarNames) {
|
|
73
|
-
if (!(varName in resultWithNulls)) {
|
|
74
|
-
resultWithNulls[varName] = null;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return resultWithNulls;
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
for (const optionalResult of optionalResults) {
|
|
81
|
-
for (const key in optionalResult) {
|
|
82
|
-
optionalVarNames.add(key);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
const mergedResults = [];
|
|
86
|
-
for (const existingResult of existingResults) {
|
|
87
|
-
let hasMatch = false;
|
|
88
|
-
for (const optionalResult of optionalResults) {
|
|
89
|
-
if (this.canMergeResult(existingResult, optionalResult)) {
|
|
90
|
-
mergedResults.push({ ...existingResult, ...optionalResult });
|
|
91
|
-
hasMatch = true;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
if (!hasMatch) {
|
|
95
|
-
const resultWithNulls = { ...existingResult };
|
|
96
|
-
for (const varName of optionalVarNames) {
|
|
97
|
-
if (!(varName in resultWithNulls)) {
|
|
98
|
-
resultWithNulls[varName] = null;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
mergedResults.push(resultWithNulls);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return mergedResults;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Check if additionalResult can be merged with existingResult
|
|
108
|
-
* Returns true if shared variables match (same node/edge) or if there are no conflicts
|
|
109
|
-
* Does NOT mutate any parameters
|
|
110
|
-
*/
|
|
111
|
-
canMergeResult(additionalResult, existingResult) {
|
|
112
|
-
for (const key in additionalResult) {
|
|
113
|
-
if (key in existingResult) {
|
|
114
|
-
const existing = existingResult[key];
|
|
115
|
-
const additional = additionalResult[key];
|
|
116
|
-
if (existing !== null && additional !== null) {
|
|
117
|
-
if ("id" in existing && "id" in additional && existing.id !== additional.id) {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
} else if (existing !== additional) {
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
export {
|
|
129
|
-
MatchEngine
|
|
130
|
-
};
|