@nicia-ai/typegraph 0.11.1 → 0.13.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.
- package/dist/index.cjs +2506 -2052
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2506 -2053
- package/dist/index.js.map +1 -1
- package/dist/interchange/index.d.cts +1 -1
- package/dist/interchange/index.d.ts +1 -1
- package/dist/profiler/index.d.cts +1 -1
- package/dist/profiler/index.d.ts +1 -1
- package/dist/{store-Cy6gSNqg.d.ts → store-6-vH0ZIj.d.ts} +169 -8
- package/dist/{store-C01_WsOg.d.cts → store-Bmdt_dS6.d.cts} +169 -8
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -313,72 +313,6 @@ var core = {
|
|
|
313
313
|
implies
|
|
314
314
|
};
|
|
315
315
|
|
|
316
|
-
// src/query/compiler/passes/recursive.ts
|
|
317
|
-
function runRecursiveTraversalSelectionPass(ast) {
|
|
318
|
-
const variableLengthTraversal = ast.traversals.find(
|
|
319
|
-
(traversal) => traversal.variableLength !== void 0
|
|
320
|
-
);
|
|
321
|
-
if (variableLengthTraversal === void 0) {
|
|
322
|
-
throw new CompilerInvariantError("No variable-length traversal found");
|
|
323
|
-
}
|
|
324
|
-
if (ast.traversals.length > 1) {
|
|
325
|
-
throw new UnsupportedPredicateError(
|
|
326
|
-
"Variable-length traversals with multiple traversals are not yet supported. Please use a single variable-length traversal."
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
return variableLengthTraversal;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// src/query/compiler/passes/runner.ts
|
|
333
|
-
function runCompilerPass(state, pass) {
|
|
334
|
-
const output = pass.execute(state);
|
|
335
|
-
return {
|
|
336
|
-
state: pass.update(state, output)
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
function compileTemporalFilter(options) {
|
|
340
|
-
const { mode, asOf, tableAlias, currentTimestamp } = options;
|
|
341
|
-
const prefix = tableAlias ? sql.raw(`${tableAlias}.`) : sql.raw("");
|
|
342
|
-
const deletedAt = sql`${prefix}deleted_at`;
|
|
343
|
-
const validFrom = sql`${prefix}valid_from`;
|
|
344
|
-
const validTo = sql`${prefix}valid_to`;
|
|
345
|
-
switch (mode) {
|
|
346
|
-
case "current": {
|
|
347
|
-
const now = currentTimestamp ?? sql`CURRENT_TIMESTAMP`;
|
|
348
|
-
return sql`${deletedAt} IS NULL AND (${validFrom} IS NULL OR ${validFrom} <= ${now}) AND (${validTo} IS NULL OR ${validTo} > ${now})`;
|
|
349
|
-
}
|
|
350
|
-
case "asOf": {
|
|
351
|
-
const timestamp = asOf;
|
|
352
|
-
return sql`${deletedAt} IS NULL AND (${validFrom} IS NULL OR ${validFrom} <= ${timestamp}) AND (${validTo} IS NULL OR ${validTo} > ${timestamp})`;
|
|
353
|
-
}
|
|
354
|
-
case "includeEnded": {
|
|
355
|
-
return sql`${deletedAt} IS NULL`;
|
|
356
|
-
}
|
|
357
|
-
case "includeTombstones": {
|
|
358
|
-
return sql.raw("1=1");
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
function extractTemporalOptions(ast, tableAlias) {
|
|
363
|
-
return {
|
|
364
|
-
mode: ast.temporalMode.mode,
|
|
365
|
-
asOf: ast.temporalMode.asOf,
|
|
366
|
-
tableAlias
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// src/query/compiler/passes/temporal.ts
|
|
371
|
-
function createTemporalFilterPass(ast, currentTimestamp) {
|
|
372
|
-
return {
|
|
373
|
-
forAlias(tableAlias) {
|
|
374
|
-
return compileTemporalFilter({
|
|
375
|
-
...extractTemporalOptions(ast, tableAlias),
|
|
376
|
-
currentTimestamp
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
|
|
382
316
|
// src/query/subquery-utils.ts
|
|
383
317
|
function normalizeValueType(valueType) {
|
|
384
318
|
if (valueType === void 0 || valueType === "unknown") {
|
|
@@ -1465,1878 +1399,2477 @@ function extractVectorSimilarityPredicates(predicates) {
|
|
|
1465
1399
|
return results;
|
|
1466
1400
|
}
|
|
1467
1401
|
|
|
1468
|
-
// src/query/compiler/
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1402
|
+
// src/query/compiler/predicate-utils.ts
|
|
1403
|
+
var EMPTY_PREDICATES = [];
|
|
1404
|
+
function buildPredicateIndexKey(alias, targetType) {
|
|
1405
|
+
return `${alias}\0${targetType}`;
|
|
1406
|
+
}
|
|
1407
|
+
function resolvePredicateTargetType(predicate2) {
|
|
1408
|
+
return predicate2.targetType === "edge" ? "edge" : "node";
|
|
1409
|
+
}
|
|
1410
|
+
function buildPredicateIndex(ast) {
|
|
1411
|
+
const byAliasAndType = /* @__PURE__ */ new Map();
|
|
1412
|
+
for (const predicate2 of ast.predicates) {
|
|
1413
|
+
const key = buildPredicateIndexKey(
|
|
1414
|
+
predicate2.targetAlias,
|
|
1415
|
+
resolvePredicateTargetType(predicate2)
|
|
1474
1416
|
);
|
|
1417
|
+
const existing = byAliasAndType.get(key);
|
|
1418
|
+
if (existing === void 0) {
|
|
1419
|
+
byAliasAndType.set(key, [predicate2]);
|
|
1420
|
+
} else {
|
|
1421
|
+
existing.push(predicate2);
|
|
1422
|
+
}
|
|
1475
1423
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1424
|
+
return { byAliasAndType };
|
|
1425
|
+
}
|
|
1426
|
+
function getPredicatesForAlias(predicateIndex, alias, targetType) {
|
|
1427
|
+
return predicateIndex.byAliasAndType.get(
|
|
1428
|
+
buildPredicateIndexKey(alias, targetType)
|
|
1429
|
+
) ?? EMPTY_PREDICATES;
|
|
1430
|
+
}
|
|
1431
|
+
function compilePredicateClauses(predicates, predicateContext) {
|
|
1432
|
+
return predicates.map(
|
|
1433
|
+
(predicate2) => compilePredicateExpression(predicate2.expression, predicateContext)
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1436
|
+
function compileKindFilter(column, kinds) {
|
|
1437
|
+
if (kinds.length === 0) {
|
|
1438
|
+
return sql`1 = 0`;
|
|
1479
1439
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
throw new UnsupportedPredicateError(
|
|
1483
|
-
`Vector similarity predicates are not supported for dialect "${dialect.name}"`
|
|
1484
|
-
);
|
|
1440
|
+
if (kinds.length === 1) {
|
|
1441
|
+
return sql`${column} = ${kinds[0]}`;
|
|
1485
1442
|
}
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1443
|
+
return sql`${column} IN (${sql.join(
|
|
1444
|
+
kinds.map((kind) => sql`${kind}`),
|
|
1445
|
+
sql`, `
|
|
1446
|
+
)})`;
|
|
1447
|
+
}
|
|
1448
|
+
function getNodeKindsForAlias(ast, alias) {
|
|
1449
|
+
if (alias === ast.start.alias) {
|
|
1450
|
+
return ast.start.kinds;
|
|
1490
1451
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1452
|
+
for (const traversal of ast.traversals) {
|
|
1453
|
+
if (traversal.nodeAlias === alias) {
|
|
1454
|
+
return traversal.nodeKinds;
|
|
1455
|
+
}
|
|
1495
1456
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1457
|
+
throw new CompilerInvariantError(`Unknown traversal source alias: ${alias}`);
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// src/query/compiler/emitter/plan-inspector.ts
|
|
1461
|
+
function collectPlanOperations(node, ops) {
|
|
1462
|
+
ops.add(node.op);
|
|
1463
|
+
switch (node.op) {
|
|
1464
|
+
case "aggregate":
|
|
1465
|
+
case "filter":
|
|
1466
|
+
case "join":
|
|
1467
|
+
case "limit_offset":
|
|
1468
|
+
case "project":
|
|
1469
|
+
case "recursive_expand":
|
|
1470
|
+
case "sort":
|
|
1471
|
+
case "vector_knn": {
|
|
1472
|
+
collectPlanOperations(node.input, ops);
|
|
1473
|
+
return;
|
|
1502
1474
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1475
|
+
case "set_op": {
|
|
1476
|
+
collectPlanOperations(node.left, ops);
|
|
1477
|
+
collectPlanOperations(node.right, ops);
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
case "scan": {
|
|
1481
|
+
return;
|
|
1507
1482
|
}
|
|
1508
1483
|
}
|
|
1509
|
-
return { vectorPredicate };
|
|
1510
1484
|
}
|
|
1511
|
-
function
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1485
|
+
function findUnaryNodeInProjectChain(rootNode, op) {
|
|
1486
|
+
let currentNode = rootNode.input;
|
|
1487
|
+
for (; ; ) {
|
|
1488
|
+
if (currentNode.op === op) {
|
|
1489
|
+
return currentNode;
|
|
1490
|
+
}
|
|
1491
|
+
switch (currentNode.op) {
|
|
1492
|
+
case "aggregate":
|
|
1493
|
+
case "filter":
|
|
1494
|
+
case "join":
|
|
1495
|
+
case "limit_offset":
|
|
1496
|
+
case "recursive_expand":
|
|
1497
|
+
case "sort":
|
|
1498
|
+
case "vector_knn": {
|
|
1499
|
+
currentNode = currentNode.input;
|
|
1500
|
+
continue;
|
|
1501
|
+
}
|
|
1502
|
+
case "project":
|
|
1503
|
+
case "scan":
|
|
1504
|
+
case "set_op": {
|
|
1505
|
+
return void 0;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1517
1508
|
}
|
|
1518
|
-
return Math.min(astLimit, vectorPredicate.limit);
|
|
1519
1509
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1510
|
+
function inspectProjectPlan(logicalPlan) {
|
|
1511
|
+
if (logicalPlan.root.op !== "project") {
|
|
1512
|
+
throw new CompilerInvariantError(
|
|
1513
|
+
`SQL emitter expected logical plan root to be "project", got "${logicalPlan.root.op}"`,
|
|
1514
|
+
{ component: "plan-inspector" }
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1517
|
+
const operations = /* @__PURE__ */ new Set();
|
|
1518
|
+
collectPlanOperations(logicalPlan.root, operations);
|
|
1519
|
+
const limitOffsetNode = findUnaryNodeInProjectChain(
|
|
1520
|
+
logicalPlan.root,
|
|
1521
|
+
"limit_offset"
|
|
1522
|
+
);
|
|
1523
|
+
const sortNode = findUnaryNodeInProjectChain(
|
|
1524
|
+
logicalPlan.root,
|
|
1525
|
+
"sort"
|
|
1526
|
+
);
|
|
1527
|
+
return {
|
|
1528
|
+
hasAggregate: operations.has("aggregate"),
|
|
1529
|
+
hasLimitOffset: operations.has("limit_offset"),
|
|
1530
|
+
hasRecursiveExpand: operations.has("recursive_expand"),
|
|
1531
|
+
hasSetOperation: operations.has("set_op"),
|
|
1532
|
+
hasSort: operations.has("sort"),
|
|
1533
|
+
hasVectorKnn: operations.has("vector_knn"),
|
|
1534
|
+
limitOffsetNode,
|
|
1535
|
+
rootProjectNode: logicalPlan.root,
|
|
1536
|
+
sortNode
|
|
1527
1537
|
};
|
|
1528
1538
|
}
|
|
1529
|
-
function
|
|
1530
|
-
const
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1539
|
+
function inspectStandardProjectPlan(logicalPlan) {
|
|
1540
|
+
const shape = inspectProjectPlan(logicalPlan);
|
|
1541
|
+
if (shape.hasSetOperation) {
|
|
1542
|
+
throw new CompilerInvariantError(
|
|
1543
|
+
'Standard SQL emitter does not support plans containing "set_op" nodes',
|
|
1544
|
+
{ component: "plan-inspector" }
|
|
1545
|
+
);
|
|
1535
1546
|
}
|
|
1536
|
-
|
|
1547
|
+
if (shape.hasRecursiveExpand) {
|
|
1548
|
+
throw new CompilerInvariantError(
|
|
1549
|
+
'Standard SQL emitter does not support plans containing "recursive_expand" nodes',
|
|
1550
|
+
{ component: "plan-inspector" }
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
return shape;
|
|
1537
1554
|
}
|
|
1538
|
-
function
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1555
|
+
function inspectRecursiveProjectPlan(logicalPlan) {
|
|
1556
|
+
const shape = inspectProjectPlan(logicalPlan);
|
|
1557
|
+
if (!shape.hasRecursiveExpand) {
|
|
1558
|
+
throw new CompilerInvariantError(
|
|
1559
|
+
'Recursive SQL emitter expected logical plan to contain a "recursive_expand" node',
|
|
1560
|
+
{ component: "plan-inspector" }
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1563
|
+
if (shape.hasSetOperation) {
|
|
1564
|
+
throw new CompilerInvariantError(
|
|
1565
|
+
'Recursive SQL emitter does not support plans containing "set_op" nodes',
|
|
1566
|
+
{ component: "plan-inspector" }
|
|
1567
|
+
);
|
|
1568
|
+
}
|
|
1569
|
+
return shape;
|
|
1543
1570
|
}
|
|
1544
|
-
function
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1571
|
+
function findTopLevelLimitOffsetNode(rootNode) {
|
|
1572
|
+
let currentNode = rootNode;
|
|
1573
|
+
for (; ; ) {
|
|
1574
|
+
if (currentNode.op === "limit_offset") {
|
|
1575
|
+
return currentNode;
|
|
1576
|
+
}
|
|
1577
|
+
switch (currentNode.op) {
|
|
1578
|
+
case "aggregate":
|
|
1579
|
+
case "filter":
|
|
1580
|
+
case "join":
|
|
1581
|
+
case "project":
|
|
1582
|
+
case "recursive_expand":
|
|
1583
|
+
case "sort":
|
|
1584
|
+
case "vector_knn": {
|
|
1585
|
+
currentNode = currentNode.input;
|
|
1586
|
+
continue;
|
|
1587
|
+
}
|
|
1588
|
+
case "scan":
|
|
1589
|
+
case "set_op": {
|
|
1590
|
+
return void 0;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
function findTopLevelSortNode(rootNode) {
|
|
1596
|
+
let currentNode = rootNode;
|
|
1597
|
+
for (; ; ) {
|
|
1598
|
+
if (currentNode.op === "sort") {
|
|
1599
|
+
return currentNode;
|
|
1600
|
+
}
|
|
1601
|
+
switch (currentNode.op) {
|
|
1602
|
+
case "aggregate":
|
|
1603
|
+
case "filter":
|
|
1604
|
+
case "join":
|
|
1605
|
+
case "limit_offset":
|
|
1606
|
+
case "project":
|
|
1607
|
+
case "recursive_expand":
|
|
1608
|
+
case "vector_knn": {
|
|
1609
|
+
currentNode = currentNode.input;
|
|
1610
|
+
continue;
|
|
1611
|
+
}
|
|
1612
|
+
case "scan":
|
|
1613
|
+
case "set_op": {
|
|
1614
|
+
return void 0;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
function inspectSetOperationPlan(logicalPlan) {
|
|
1620
|
+
const operations = /* @__PURE__ */ new Set();
|
|
1621
|
+
collectPlanOperations(logicalPlan.root, operations);
|
|
1622
|
+
if (!operations.has("set_op")) {
|
|
1623
|
+
throw new CompilerInvariantError(
|
|
1624
|
+
'Set-operation SQL emitter expected logical plan to contain a "set_op" node',
|
|
1625
|
+
{ component: "plan-inspector" }
|
|
1626
|
+
);
|
|
1548
1627
|
}
|
|
1628
|
+
const limitOffsetNode = findTopLevelLimitOffsetNode(logicalPlan.root);
|
|
1629
|
+
const sortNode = findTopLevelSortNode(logicalPlan.root);
|
|
1549
1630
|
return {
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
predicates: aliasPredicates.map((predicate2) => predicate2.expression)
|
|
1631
|
+
hasLimitOffset: limitOffsetNode !== void 0,
|
|
1632
|
+
hasSetOperation: true,
|
|
1633
|
+
hasSort: sortNode !== void 0,
|
|
1634
|
+
limitOffsetNode,
|
|
1635
|
+
sortNode
|
|
1556
1636
|
};
|
|
1557
1637
|
}
|
|
1558
|
-
function
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
id: nextPlanNodeId(),
|
|
1566
|
-
input: node,
|
|
1567
|
-
op: "aggregate"
|
|
1568
|
-
};
|
|
1569
|
-
node = ast.having === void 0 ? aggregateNode : { ...aggregateNode, having: ast.having };
|
|
1638
|
+
function assertRecursiveEmitterClauseAlignment(logicalPlan, input) {
|
|
1639
|
+
const planShape = inspectRecursiveProjectPlan(logicalPlan);
|
|
1640
|
+
if (planShape.hasSort && input.orderBy === void 0) {
|
|
1641
|
+
throw new CompilerInvariantError(
|
|
1642
|
+
"Recursive SQL emitter expected ORDER BY clause for plan containing a sort node",
|
|
1643
|
+
{ component: "recursive-emitter" }
|
|
1644
|
+
);
|
|
1570
1645
|
}
|
|
1571
|
-
if (
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
orderBy: ast.orderBy
|
|
1577
|
-
};
|
|
1646
|
+
if (!planShape.hasSort && input.orderBy !== void 0) {
|
|
1647
|
+
throw new CompilerInvariantError(
|
|
1648
|
+
"Recursive SQL emitter received ORDER BY clause for a plan without sort nodes",
|
|
1649
|
+
{ component: "recursive-emitter" }
|
|
1650
|
+
);
|
|
1578
1651
|
}
|
|
1579
|
-
if (
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1652
|
+
if (planShape.hasLimitOffset && input.limitOffset === void 0) {
|
|
1653
|
+
throw new CompilerInvariantError(
|
|
1654
|
+
"Recursive SQL emitter expected LIMIT/OFFSET clause for plan containing a limit_offset node",
|
|
1655
|
+
{ component: "recursive-emitter" }
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
if (!planShape.hasLimitOffset && input.limitOffset !== void 0) {
|
|
1659
|
+
throw new CompilerInvariantError(
|
|
1660
|
+
"Recursive SQL emitter received LIMIT/OFFSET clause for a plan without limit_offset nodes",
|
|
1661
|
+
{ component: "recursive-emitter" }
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
function emitRecursiveQuerySql(input) {
|
|
1666
|
+
assertRecursiveEmitterClauseAlignment(input.logicalPlan, input);
|
|
1667
|
+
const parts = [
|
|
1668
|
+
sql`WITH RECURSIVE`,
|
|
1669
|
+
input.recursiveCte,
|
|
1670
|
+
sql`SELECT ${input.projection}`,
|
|
1671
|
+
sql`FROM recursive_cte`,
|
|
1672
|
+
input.depthFilter
|
|
1673
|
+
];
|
|
1674
|
+
if (input.orderBy !== void 0) {
|
|
1675
|
+
parts.push(input.orderBy);
|
|
1676
|
+
}
|
|
1677
|
+
if (input.limitOffset !== void 0) {
|
|
1678
|
+
parts.push(input.limitOffset);
|
|
1679
|
+
}
|
|
1680
|
+
return sql.join(parts, sql` `);
|
|
1681
|
+
}
|
|
1682
|
+
function assertSetOperationEmitterClauseAlignment(logicalPlan, suffixClauses) {
|
|
1683
|
+
const shape = inspectSetOperationPlan(logicalPlan);
|
|
1684
|
+
const hasSuffixClauses = suffixClauses !== void 0 && suffixClauses.length > 0;
|
|
1685
|
+
if (!shape.hasSort && !shape.hasLimitOffset && hasSuffixClauses) {
|
|
1686
|
+
throw new CompilerInvariantError(
|
|
1687
|
+
"Set-operation SQL emitter received suffix clauses for a plan without top-level sort or limit_offset nodes",
|
|
1688
|
+
{ component: "set-operation-emitter" }
|
|
1689
|
+
);
|
|
1690
|
+
}
|
|
1691
|
+
if (!hasSuffixClauses) {
|
|
1692
|
+
if (shape.hasSort || shape.hasLimitOffset) {
|
|
1598
1693
|
throw new CompilerInvariantError(
|
|
1599
|
-
"
|
|
1694
|
+
"Set-operation SQL emitter expected suffix clauses for plan containing top-level sort or limit_offset nodes",
|
|
1695
|
+
{ component: "set-operation-emitter" }
|
|
1600
1696
|
);
|
|
1601
1697
|
}
|
|
1698
|
+
return;
|
|
1602
1699
|
}
|
|
1603
|
-
const
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
return collapsedTraversalCteAlias === void 0 ? projectNodeBase : {
|
|
1610
|
-
...projectNodeBase,
|
|
1611
|
-
collapsedTraversalAlias: collapsedTraversalCteAlias
|
|
1612
|
-
};
|
|
1613
|
-
}
|
|
1614
|
-
function lowerStandardQueryToLogicalPlanNode(input) {
|
|
1615
|
-
const { ast, nextPlanNodeId } = input;
|
|
1616
|
-
let currentNode = {
|
|
1617
|
-
alias: ast.start.alias,
|
|
1618
|
-
graphId: input.graphId,
|
|
1619
|
-
id: nextPlanNodeId(),
|
|
1620
|
-
kinds: ast.start.kinds,
|
|
1621
|
-
op: "scan",
|
|
1622
|
-
source: "nodes"
|
|
1623
|
-
};
|
|
1624
|
-
currentNode = wrapWithAliasFilterNode(
|
|
1625
|
-
currentNode,
|
|
1626
|
-
ast,
|
|
1627
|
-
ast.start.alias,
|
|
1628
|
-
"node",
|
|
1629
|
-
nextPlanNodeId
|
|
1630
|
-
);
|
|
1631
|
-
for (const traversal of ast.traversals) {
|
|
1632
|
-
currentNode = {
|
|
1633
|
-
direction: traversal.direction,
|
|
1634
|
-
edgeAlias: traversal.edgeAlias,
|
|
1635
|
-
edgeKinds: traversal.edgeKinds,
|
|
1636
|
-
id: nextPlanNodeId(),
|
|
1637
|
-
input: currentNode,
|
|
1638
|
-
inverseEdgeKinds: traversal.inverseEdgeKinds ?? [],
|
|
1639
|
-
joinFromAlias: traversal.joinFromAlias,
|
|
1640
|
-
joinType: traversal.optional ? "left" : "inner",
|
|
1641
|
-
nodeAlias: traversal.nodeAlias,
|
|
1642
|
-
nodeKinds: traversal.nodeKinds,
|
|
1643
|
-
op: "join"
|
|
1644
|
-
};
|
|
1645
|
-
currentNode = wrapWithAliasFilterNode(
|
|
1646
|
-
currentNode,
|
|
1647
|
-
ast,
|
|
1648
|
-
traversal.edgeAlias,
|
|
1649
|
-
"edge",
|
|
1650
|
-
nextPlanNodeId
|
|
1651
|
-
);
|
|
1652
|
-
currentNode = wrapWithAliasFilterNode(
|
|
1653
|
-
currentNode,
|
|
1654
|
-
ast,
|
|
1655
|
-
traversal.nodeAlias,
|
|
1656
|
-
"node",
|
|
1657
|
-
nextPlanNodeId
|
|
1700
|
+
const limitOffsetClauseCount = shape.limitOffsetNode === void 0 ? 0 : (shape.limitOffsetNode.limit === void 0 ? 0 : 1) + (shape.limitOffsetNode.offset === void 0 ? 0 : 1);
|
|
1701
|
+
const expectedClauseCount = (shape.sortNode === void 0 ? 0 : 1) + limitOffsetClauseCount;
|
|
1702
|
+
if (suffixClauses.length !== expectedClauseCount) {
|
|
1703
|
+
throw new CompilerInvariantError(
|
|
1704
|
+
`Set-operation SQL emitter expected ${String(expectedClauseCount)} top-level suffix clause(s) from logical plan, got ${String(suffixClauses.length)}`,
|
|
1705
|
+
{ component: "set-operation-emitter" }
|
|
1658
1706
|
);
|
|
1659
1707
|
}
|
|
1660
|
-
if (input.vectorPredicate !== void 0) {
|
|
1661
|
-
currentNode = {
|
|
1662
|
-
id: nextPlanNodeId(),
|
|
1663
|
-
input: currentNode,
|
|
1664
|
-
op: "vector_knn",
|
|
1665
|
-
predicate: input.vectorPredicate
|
|
1666
|
-
};
|
|
1667
|
-
}
|
|
1668
|
-
return appendAggregateSortLimitAndProjectNodes(
|
|
1669
|
-
currentNode,
|
|
1670
|
-
ast,
|
|
1671
|
-
nextPlanNodeId,
|
|
1672
|
-
input.effectiveLimit,
|
|
1673
|
-
input.collapsedTraversalCteAlias
|
|
1674
|
-
);
|
|
1675
1708
|
}
|
|
1676
|
-
function
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
alias: ast.start.alias,
|
|
1681
|
-
graphId: input.graphId,
|
|
1682
|
-
id: nextPlanNodeId(),
|
|
1683
|
-
kinds: ast.start.kinds,
|
|
1684
|
-
op: "scan",
|
|
1685
|
-
source: "nodes"
|
|
1686
|
-
};
|
|
1687
|
-
currentNode = wrapWithAliasFilterNode(
|
|
1688
|
-
currentNode,
|
|
1689
|
-
ast,
|
|
1690
|
-
ast.start.alias,
|
|
1691
|
-
"node",
|
|
1692
|
-
nextPlanNodeId
|
|
1693
|
-
);
|
|
1694
|
-
currentNode = {
|
|
1695
|
-
edgeAlias: traversal.edgeAlias,
|
|
1696
|
-
edgeKinds: traversal.edgeKinds,
|
|
1697
|
-
id: nextPlanNodeId(),
|
|
1698
|
-
input: currentNode,
|
|
1699
|
-
inverseEdgeKinds: traversal.inverseEdgeKinds ?? [],
|
|
1700
|
-
nodeAlias: traversal.nodeAlias,
|
|
1701
|
-
nodeKinds: traversal.nodeKinds,
|
|
1702
|
-
op: "recursive_expand",
|
|
1703
|
-
traversal: traversal.variableLength
|
|
1704
|
-
};
|
|
1705
|
-
currentNode = wrapWithAliasFilterNode(
|
|
1706
|
-
currentNode,
|
|
1707
|
-
ast,
|
|
1708
|
-
traversal.edgeAlias,
|
|
1709
|
-
"edge",
|
|
1710
|
-
nextPlanNodeId
|
|
1711
|
-
);
|
|
1712
|
-
currentNode = wrapWithAliasFilterNode(
|
|
1713
|
-
currentNode,
|
|
1714
|
-
ast,
|
|
1715
|
-
traversal.nodeAlias,
|
|
1716
|
-
"node",
|
|
1717
|
-
nextPlanNodeId
|
|
1718
|
-
);
|
|
1719
|
-
return appendAggregateSortLimitAndProjectNodes(
|
|
1720
|
-
currentNode,
|
|
1721
|
-
ast,
|
|
1722
|
-
nextPlanNodeId,
|
|
1723
|
-
ast.limit
|
|
1709
|
+
function emitSetOperationQuerySql(input) {
|
|
1710
|
+
assertSetOperationEmitterClauseAlignment(
|
|
1711
|
+
input.logicalPlan,
|
|
1712
|
+
input.suffixClauses
|
|
1724
1713
|
);
|
|
1714
|
+
const parts = [];
|
|
1715
|
+
if (input.ctes !== void 0 && input.ctes.length > 0) {
|
|
1716
|
+
parts.push(sql`WITH ${sql.join([...input.ctes], sql`, `)}`);
|
|
1717
|
+
}
|
|
1718
|
+
parts.push(input.baseQuery);
|
|
1719
|
+
if (input.suffixClauses !== void 0 && input.suffixClauses.length > 0) {
|
|
1720
|
+
parts.push(...input.suffixClauses);
|
|
1721
|
+
}
|
|
1722
|
+
return sql.join(parts, sql` `);
|
|
1725
1723
|
}
|
|
1726
|
-
function
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
nextPlanNodeId
|
|
1724
|
+
function assertStandardEmitterClauseAlignment(logicalPlan, input) {
|
|
1725
|
+
const planShape = inspectStandardProjectPlan(logicalPlan);
|
|
1726
|
+
if (input.groupBy !== void 0 && !planShape.hasAggregate) {
|
|
1727
|
+
throw new CompilerInvariantError(
|
|
1728
|
+
"Standard SQL emitter received GROUP BY clause for a plan without aggregate nodes",
|
|
1729
|
+
{ component: "standard-emitter" }
|
|
1733
1730
|
);
|
|
1734
1731
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
ast: query,
|
|
1741
|
-
graphId,
|
|
1742
|
-
nextPlanNodeId
|
|
1743
|
-
});
|
|
1732
|
+
if (input.having !== void 0 && !planShape.hasAggregate) {
|
|
1733
|
+
throw new CompilerInvariantError(
|
|
1734
|
+
"Standard SQL emitter received HAVING clause for a plan without aggregate nodes",
|
|
1735
|
+
{ component: "standard-emitter" }
|
|
1736
|
+
);
|
|
1744
1737
|
}
|
|
1745
|
-
const
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
ast: query,
|
|
1752
|
-
graphId,
|
|
1753
|
-
nextPlanNodeId,
|
|
1754
|
-
...effectiveLimit === void 0 ? {} : { effectiveLimit },
|
|
1755
|
-
...vectorPredicate === void 0 ? {} : { vectorPredicate }
|
|
1756
|
-
};
|
|
1757
|
-
return lowerStandardQueryToLogicalPlanNode(loweringInput);
|
|
1758
|
-
}
|
|
1759
|
-
function lowerSetOperationToLogicalPlanNode(op, graphId, dialect, nextPlanNodeId) {
|
|
1760
|
-
let currentNode = {
|
|
1761
|
-
id: nextPlanNodeId(),
|
|
1762
|
-
left: lowerComposableQueryToLogicalPlanNode(
|
|
1763
|
-
op.left,
|
|
1764
|
-
dialect,
|
|
1765
|
-
graphId,
|
|
1766
|
-
nextPlanNodeId
|
|
1767
|
-
),
|
|
1768
|
-
op: "set_op",
|
|
1769
|
-
operator: op.operator,
|
|
1770
|
-
right: lowerComposableQueryToLogicalPlanNode(
|
|
1771
|
-
op.right,
|
|
1772
|
-
dialect,
|
|
1773
|
-
graphId,
|
|
1774
|
-
nextPlanNodeId
|
|
1775
|
-
)
|
|
1776
|
-
};
|
|
1777
|
-
if (op.orderBy !== void 0 && op.orderBy.length > 0) {
|
|
1778
|
-
currentNode = {
|
|
1779
|
-
id: nextPlanNodeId(),
|
|
1780
|
-
input: currentNode,
|
|
1781
|
-
op: "sort",
|
|
1782
|
-
orderBy: op.orderBy
|
|
1783
|
-
};
|
|
1738
|
+
const expectsOrderBy = planShape.hasSort || planShape.hasVectorKnn;
|
|
1739
|
+
if (expectsOrderBy && input.orderBy === void 0) {
|
|
1740
|
+
throw new CompilerInvariantError(
|
|
1741
|
+
"Standard SQL emitter expected ORDER BY clause for plan containing a sort or vector_knn node",
|
|
1742
|
+
{ component: "standard-emitter" }
|
|
1743
|
+
);
|
|
1784
1744
|
}
|
|
1785
|
-
if (
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1745
|
+
if (!expectsOrderBy && input.orderBy !== void 0) {
|
|
1746
|
+
throw new CompilerInvariantError(
|
|
1747
|
+
"Standard SQL emitter received ORDER BY clause for a plan without sort or vector_knn nodes",
|
|
1748
|
+
{ component: "standard-emitter" }
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1751
|
+
if (planShape.hasLimitOffset && input.limitOffset === void 0) {
|
|
1752
|
+
throw new CompilerInvariantError(
|
|
1753
|
+
"Standard SQL emitter expected LIMIT/OFFSET clause for plan containing a limit_offset node",
|
|
1754
|
+
{ component: "standard-emitter" }
|
|
1755
|
+
);
|
|
1756
|
+
}
|
|
1757
|
+
if (!planShape.hasLimitOffset && input.limitOffset !== void 0) {
|
|
1758
|
+
throw new CompilerInvariantError(
|
|
1759
|
+
"Standard SQL emitter received LIMIT/OFFSET clause for a plan without limit_offset nodes",
|
|
1760
|
+
{ component: "standard-emitter" }
|
|
1761
|
+
);
|
|
1798
1762
|
}
|
|
1799
|
-
return currentNode;
|
|
1800
|
-
}
|
|
1801
|
-
function lowerStandardQueryToLogicalPlan(input) {
|
|
1802
|
-
const nextPlanNodeId = createPlanNodeIdFactory();
|
|
1803
|
-
return {
|
|
1804
|
-
metadata: {
|
|
1805
|
-
dialect: input.dialect,
|
|
1806
|
-
graphId: input.graphId
|
|
1807
|
-
},
|
|
1808
|
-
root: lowerStandardQueryToLogicalPlanNode({
|
|
1809
|
-
...input,
|
|
1810
|
-
nextPlanNodeId
|
|
1811
|
-
})
|
|
1812
|
-
};
|
|
1813
|
-
}
|
|
1814
|
-
function lowerRecursiveQueryToLogicalPlan(input) {
|
|
1815
|
-
const nextPlanNodeId = createPlanNodeIdFactory();
|
|
1816
|
-
return {
|
|
1817
|
-
metadata: {
|
|
1818
|
-
dialect: input.dialect,
|
|
1819
|
-
graphId: input.graphId
|
|
1820
|
-
},
|
|
1821
|
-
root: lowerRecursiveQueryToLogicalPlanNode({
|
|
1822
|
-
...input,
|
|
1823
|
-
nextPlanNodeId
|
|
1824
|
-
})
|
|
1825
|
-
};
|
|
1826
1763
|
}
|
|
1827
|
-
function
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1764
|
+
function emitStandardQuerySql(input) {
|
|
1765
|
+
assertStandardEmitterClauseAlignment(input.logicalPlan, input);
|
|
1766
|
+
const parts = [];
|
|
1767
|
+
if (input.ctes.length > 0) {
|
|
1768
|
+
parts.push(sql`WITH ${sql.join([...input.ctes], sql`, `)}`);
|
|
1769
|
+
}
|
|
1770
|
+
parts.push(sql`SELECT ${input.projection}`, input.fromClause);
|
|
1771
|
+
if (input.groupBy !== void 0) {
|
|
1772
|
+
parts.push(input.groupBy);
|
|
1773
|
+
}
|
|
1774
|
+
if (input.having !== void 0) {
|
|
1775
|
+
parts.push(input.having);
|
|
1776
|
+
}
|
|
1777
|
+
if (input.orderBy !== void 0) {
|
|
1778
|
+
parts.push(input.orderBy);
|
|
1779
|
+
}
|
|
1780
|
+
if (input.limitOffset !== void 0) {
|
|
1781
|
+
parts.push(input.limitOffset);
|
|
1782
|
+
}
|
|
1783
|
+
return sql.join(parts, sql` `);
|
|
1841
1784
|
}
|
|
1842
1785
|
|
|
1843
|
-
// src/query/compiler/
|
|
1844
|
-
function
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
case "
|
|
1849
|
-
|
|
1850
|
-
case "limit_offset":
|
|
1851
|
-
case "project":
|
|
1852
|
-
case "recursive_expand":
|
|
1853
|
-
case "sort":
|
|
1854
|
-
case "vector_knn": {
|
|
1855
|
-
collectPlanOperations(node.input, ops);
|
|
1856
|
-
return;
|
|
1786
|
+
// src/query/compiler/typed-json-extract.ts
|
|
1787
|
+
function compileTypedJsonExtract(input) {
|
|
1788
|
+
const { column, dialect, pointer, valueType } = input;
|
|
1789
|
+
const fallback = input.fallback ?? "json";
|
|
1790
|
+
switch (valueType) {
|
|
1791
|
+
case "string": {
|
|
1792
|
+
return dialect.jsonExtractText(column, pointer);
|
|
1857
1793
|
}
|
|
1858
|
-
case "
|
|
1859
|
-
|
|
1860
|
-
collectPlanOperations(node.right, ops);
|
|
1861
|
-
return;
|
|
1794
|
+
case "number": {
|
|
1795
|
+
return dialect.jsonExtractNumber(column, pointer);
|
|
1862
1796
|
}
|
|
1863
|
-
case "
|
|
1864
|
-
return;
|
|
1797
|
+
case "boolean": {
|
|
1798
|
+
return dialect.jsonExtractBoolean(column, pointer);
|
|
1865
1799
|
}
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
function findUnaryNodeInProjectChain(rootNode, op) {
|
|
1869
|
-
let currentNode = rootNode.input;
|
|
1870
|
-
for (; ; ) {
|
|
1871
|
-
if (currentNode.op === op) {
|
|
1872
|
-
return currentNode;
|
|
1800
|
+
case "date": {
|
|
1801
|
+
return dialect.jsonExtractDate(column, pointer);
|
|
1873
1802
|
}
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
continue;
|
|
1884
|
-
}
|
|
1885
|
-
case "project":
|
|
1886
|
-
case "scan":
|
|
1887
|
-
case "set_op": {
|
|
1888
|
-
return void 0;
|
|
1889
|
-
}
|
|
1803
|
+
case "array":
|
|
1804
|
+
case "object":
|
|
1805
|
+
case "embedding":
|
|
1806
|
+
case "unknown":
|
|
1807
|
+
case void 0: {
|
|
1808
|
+
return fallback === "text" ? dialect.jsonExtractText(column, pointer) : dialect.jsonExtract(column, pointer);
|
|
1809
|
+
}
|
|
1810
|
+
default: {
|
|
1811
|
+
return fallback === "text" ? dialect.jsonExtractText(column, pointer) : dialect.jsonExtract(column, pointer);
|
|
1890
1812
|
}
|
|
1891
1813
|
}
|
|
1892
1814
|
}
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
sortNode
|
|
1920
|
-
};
|
|
1815
|
+
var NODE_COLUMNS = [
|
|
1816
|
+
"id",
|
|
1817
|
+
"kind",
|
|
1818
|
+
"props",
|
|
1819
|
+
"version",
|
|
1820
|
+
"valid_from",
|
|
1821
|
+
"valid_to",
|
|
1822
|
+
"created_at",
|
|
1823
|
+
"updated_at",
|
|
1824
|
+
"deleted_at"
|
|
1825
|
+
];
|
|
1826
|
+
var EDGE_COLUMNS = [
|
|
1827
|
+
"id",
|
|
1828
|
+
"kind",
|
|
1829
|
+
"from_id",
|
|
1830
|
+
"to_id",
|
|
1831
|
+
"props",
|
|
1832
|
+
"valid_from",
|
|
1833
|
+
"valid_to",
|
|
1834
|
+
"created_at",
|
|
1835
|
+
"updated_at",
|
|
1836
|
+
"deleted_at"
|
|
1837
|
+
];
|
|
1838
|
+
var EMPTY_REQUIRED_COLUMNS = /* @__PURE__ */ new Set();
|
|
1839
|
+
function quoteIdentifier(identifier) {
|
|
1840
|
+
return sql.raw(`"${identifier.replaceAll('"', '""')}"`);
|
|
1921
1841
|
}
|
|
1922
|
-
function
|
|
1923
|
-
|
|
1924
|
-
if (
|
|
1925
|
-
|
|
1926
|
-
'Standard SQL emitter does not support plans containing "set_op" nodes',
|
|
1927
|
-
{ component: "plan-inspector" }
|
|
1928
|
-
);
|
|
1929
|
-
}
|
|
1930
|
-
if (shape.hasRecursiveExpand) {
|
|
1931
|
-
throw new CompilerInvariantError(
|
|
1932
|
-
'Standard SQL emitter does not support plans containing "recursive_expand" nodes',
|
|
1933
|
-
{ component: "plan-inspector" }
|
|
1934
|
-
);
|
|
1935
|
-
}
|
|
1936
|
-
return shape;
|
|
1842
|
+
function shouldProjectColumn(requiredColumns, column, alwaysRequiredColumns) {
|
|
1843
|
+
if (alwaysRequiredColumns?.has(column)) return true;
|
|
1844
|
+
if (requiredColumns === void 0) return true;
|
|
1845
|
+
return requiredColumns.has(column);
|
|
1937
1846
|
}
|
|
1938
|
-
function
|
|
1939
|
-
const
|
|
1940
|
-
if (
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
{ component: "plan-inspector" }
|
|
1944
|
-
);
|
|
1945
|
-
}
|
|
1946
|
-
if (shape.hasSetOperation) {
|
|
1947
|
-
throw new CompilerInvariantError(
|
|
1948
|
-
'Recursive SQL emitter does not support plans containing "set_op" nodes',
|
|
1949
|
-
{ component: "plan-inspector" }
|
|
1950
|
-
);
|
|
1847
|
+
function addRequiredColumn(requiredColumnsByAlias, alias, column) {
|
|
1848
|
+
const existing = requiredColumnsByAlias.get(alias);
|
|
1849
|
+
if (existing) {
|
|
1850
|
+
existing.add(column);
|
|
1851
|
+
return;
|
|
1951
1852
|
}
|
|
1952
|
-
|
|
1853
|
+
requiredColumnsByAlias.set(alias, /* @__PURE__ */ new Set([column]));
|
|
1953
1854
|
}
|
|
1954
|
-
function
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
return currentNode;
|
|
1959
|
-
}
|
|
1960
|
-
switch (currentNode.op) {
|
|
1961
|
-
case "aggregate":
|
|
1962
|
-
case "filter":
|
|
1963
|
-
case "join":
|
|
1964
|
-
case "project":
|
|
1965
|
-
case "recursive_expand":
|
|
1966
|
-
case "sort":
|
|
1967
|
-
case "vector_knn": {
|
|
1968
|
-
currentNode = currentNode.input;
|
|
1969
|
-
continue;
|
|
1970
|
-
}
|
|
1971
|
-
case "scan":
|
|
1972
|
-
case "set_op": {
|
|
1973
|
-
return void 0;
|
|
1974
|
-
}
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1855
|
+
function markFieldRefAsRequired(requiredColumnsByAlias, field2) {
|
|
1856
|
+
const column = field2.path[0];
|
|
1857
|
+
if (column === void 0) return;
|
|
1858
|
+
addRequiredColumn(requiredColumnsByAlias, field2.alias, column);
|
|
1977
1859
|
}
|
|
1978
|
-
function
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
}
|
|
1984
|
-
switch (currentNode.op) {
|
|
1985
|
-
case "aggregate":
|
|
1986
|
-
case "filter":
|
|
1987
|
-
case "join":
|
|
1988
|
-
case "limit_offset":
|
|
1989
|
-
case "project":
|
|
1990
|
-
case "recursive_expand":
|
|
1991
|
-
case "vector_knn": {
|
|
1992
|
-
currentNode = currentNode.input;
|
|
1993
|
-
continue;
|
|
1994
|
-
}
|
|
1995
|
-
case "scan":
|
|
1996
|
-
case "set_op": {
|
|
1997
|
-
return void 0;
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
1860
|
+
function mapSelectiveSystemFieldToColumn(field2) {
|
|
1861
|
+
if (field2 === "fromId") return "from_id";
|
|
1862
|
+
if (field2 === "toId") return "to_id";
|
|
1863
|
+
if (field2.startsWith("meta.")) {
|
|
1864
|
+
return field2.slice(5).replaceAll(/([A-Z])/g, "_$1").toLowerCase();
|
|
2000
1865
|
}
|
|
1866
|
+
return field2;
|
|
2001
1867
|
}
|
|
2002
|
-
function
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
{ component: "plan-inspector" }
|
|
1868
|
+
function markSelectiveFieldAsRequired(requiredColumnsByAlias, field2) {
|
|
1869
|
+
if (field2.isSystemField) {
|
|
1870
|
+
addRequiredColumn(
|
|
1871
|
+
requiredColumnsByAlias,
|
|
1872
|
+
field2.alias,
|
|
1873
|
+
mapSelectiveSystemFieldToColumn(field2.field)
|
|
2009
1874
|
);
|
|
1875
|
+
return;
|
|
2010
1876
|
}
|
|
2011
|
-
|
|
2012
|
-
const sortNode = findTopLevelSortNode(logicalPlan.root);
|
|
2013
|
-
return {
|
|
2014
|
-
hasLimitOffset: limitOffsetNode !== void 0,
|
|
2015
|
-
hasSetOperation: true,
|
|
2016
|
-
hasSort: sortNode !== void 0,
|
|
2017
|
-
limitOffsetNode,
|
|
2018
|
-
sortNode
|
|
2019
|
-
};
|
|
1877
|
+
addRequiredColumn(requiredColumnsByAlias, field2.alias, "props");
|
|
2020
1878
|
}
|
|
2021
|
-
function
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
{ component: "recursive-emitter" }
|
|
2033
|
-
);
|
|
2034
|
-
}
|
|
2035
|
-
if (planShape.hasLimitOffset && input.limitOffset === void 0) {
|
|
2036
|
-
throw new CompilerInvariantError(
|
|
2037
|
-
"Recursive SQL emitter expected LIMIT/OFFSET clause for plan containing a limit_offset node",
|
|
2038
|
-
{ component: "recursive-emitter" }
|
|
2039
|
-
);
|
|
2040
|
-
}
|
|
2041
|
-
if (!planShape.hasLimitOffset && input.limitOffset !== void 0) {
|
|
2042
|
-
throw new CompilerInvariantError(
|
|
2043
|
-
"Recursive SQL emitter received LIMIT/OFFSET clause for a plan without limit_offset nodes",
|
|
2044
|
-
{ component: "recursive-emitter" }
|
|
2045
|
-
);
|
|
1879
|
+
function isIdFieldRef(field2) {
|
|
1880
|
+
return field2.path.length === 1 && field2.path[0] === "id" && field2.jsonPointer === void 0;
|
|
1881
|
+
}
|
|
1882
|
+
function isAggregateExpr(source) {
|
|
1883
|
+
return "__type" in source && source.__type === "aggregate";
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
// src/query/compiler/emitter/standard-builders.ts
|
|
1887
|
+
function compileColumnReference(tableAlias, column) {
|
|
1888
|
+
if (tableAlias === void 0) {
|
|
1889
|
+
return sql.raw(column);
|
|
2046
1890
|
}
|
|
1891
|
+
return sql`${sql.raw(tableAlias)}.${sql.raw(column)}`;
|
|
2047
1892
|
}
|
|
2048
|
-
function
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
1893
|
+
function compileNodeSelectColumns(tableAlias, alias, requiredColumns) {
|
|
1894
|
+
return NODE_COLUMNS.filter(
|
|
1895
|
+
(column) => column === "id" || column === "kind" || shouldProjectColumn(requiredColumns, column)
|
|
1896
|
+
).map(
|
|
1897
|
+
(column) => sql`${compileColumnReference(tableAlias, column)} AS ${sql.raw(`${alias}_${column}`)}`
|
|
1898
|
+
);
|
|
1899
|
+
}
|
|
1900
|
+
function compileEdgeSelectColumns(tableAlias, alias, requiredColumns) {
|
|
1901
|
+
return EDGE_COLUMNS.filter(
|
|
1902
|
+
(column) => shouldProjectColumn(requiredColumns, column)
|
|
1903
|
+
).map(
|
|
1904
|
+
(column) => sql`${compileColumnReference(tableAlias, column)} AS ${sql.raw(`${alias}_${column}`)}`
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
function buildStandardStartCte(input) {
|
|
1908
|
+
const { ast, ctx, graphId, predicateIndex, requiredColumnsByAlias } = input;
|
|
1909
|
+
const alias = ast.start.alias;
|
|
1910
|
+
const kinds = ast.start.kinds;
|
|
1911
|
+
const kindFilter = compileKindFilter(sql.raw("kind"), kinds);
|
|
1912
|
+
const temporalFilter = input.temporalFilterPass.forAlias();
|
|
1913
|
+
const cteContext = { ...ctx, cteColumnPrefix: "" };
|
|
1914
|
+
const predicateClauses = compilePredicateClauses(
|
|
1915
|
+
getPredicatesForAlias(predicateIndex, alias, "node"),
|
|
1916
|
+
cteContext
|
|
1917
|
+
);
|
|
1918
|
+
const whereClauses = [
|
|
1919
|
+
sql`graph_id = ${graphId}`,
|
|
1920
|
+
kindFilter,
|
|
1921
|
+
temporalFilter,
|
|
1922
|
+
...predicateClauses
|
|
1923
|
+
];
|
|
1924
|
+
const effectiveRequiredColumns = requiredColumnsByAlias ? requiredColumnsByAlias.get(alias) ?? EMPTY_REQUIRED_COLUMNS : void 0;
|
|
1925
|
+
return sql`
|
|
1926
|
+
cte_${sql.raw(alias)} AS (
|
|
1927
|
+
SELECT ${sql.join(
|
|
1928
|
+
compileNodeSelectColumns(void 0, alias, effectiveRequiredColumns),
|
|
1929
|
+
sql`, `
|
|
1930
|
+
)}
|
|
1931
|
+
FROM ${ctx.schema.nodesTable}
|
|
1932
|
+
WHERE ${sql.join(whereClauses, sql` AND `)}
|
|
1933
|
+
)
|
|
1934
|
+
`;
|
|
1935
|
+
}
|
|
1936
|
+
function buildStandardTraversalCte(input) {
|
|
1937
|
+
const {
|
|
1938
|
+
ast,
|
|
1939
|
+
carryForwardPreviousColumns,
|
|
1940
|
+
ctx,
|
|
1941
|
+
graphId,
|
|
1942
|
+
materializeCte,
|
|
1943
|
+
predicateIndex,
|
|
1944
|
+
requiredColumnsByAlias,
|
|
1945
|
+
temporalFilterPass,
|
|
1946
|
+
traversalIndex,
|
|
1947
|
+
traversalLimit
|
|
1948
|
+
} = input;
|
|
1949
|
+
const traversal = ast.traversals[traversalIndex];
|
|
1950
|
+
const traversalLimitValue = traversalIndex === ast.traversals.length - 1 ? traversalLimit : void 0;
|
|
1951
|
+
const previousNodeKinds = getNodeKindsForAlias(ast, traversal.joinFromAlias);
|
|
1952
|
+
const directEdgeKinds = [...new Set(traversal.edgeKinds)];
|
|
1953
|
+
const inverseEdgeKinds = traversal.inverseEdgeKinds === void 0 ? [] : [...new Set(traversal.inverseEdgeKinds)];
|
|
1954
|
+
const nodeKinds = traversal.nodeKinds;
|
|
1955
|
+
const nodeKindFilter = compileKindFilter(sql.raw("n.kind"), nodeKinds);
|
|
1956
|
+
const edgeTemporalFilter = temporalFilterPass.forAlias("e");
|
|
1957
|
+
const nodeTemporalFilter = temporalFilterPass.forAlias("n");
|
|
1958
|
+
const nodeCteContext = {
|
|
1959
|
+
...ctx,
|
|
1960
|
+
cteColumnPrefix: "n"
|
|
1961
|
+
};
|
|
1962
|
+
const nodePredicateClauses = compilePredicateClauses(
|
|
1963
|
+
getPredicatesForAlias(predicateIndex, traversal.nodeAlias, "node"),
|
|
1964
|
+
nodeCteContext
|
|
1965
|
+
);
|
|
1966
|
+
const edgeCteContext = {
|
|
1967
|
+
...ctx,
|
|
1968
|
+
cteColumnPrefix: "e"
|
|
1969
|
+
};
|
|
1970
|
+
const edgePredicateClauses = compilePredicateClauses(
|
|
1971
|
+
getPredicatesForAlias(predicateIndex, traversal.edgeAlias, "edge"),
|
|
1972
|
+
edgeCteContext
|
|
1973
|
+
);
|
|
1974
|
+
const baseWhereClauses = [
|
|
1975
|
+
sql`e.graph_id = ${graphId}`,
|
|
1976
|
+
nodeKindFilter,
|
|
1977
|
+
edgeTemporalFilter,
|
|
1978
|
+
nodeTemporalFilter,
|
|
1979
|
+
...nodePredicateClauses,
|
|
1980
|
+
...edgePredicateClauses
|
|
1981
|
+
];
|
|
1982
|
+
const previousAlias = traversal.joinFromAlias;
|
|
1983
|
+
const edgeAlias = traversal.edgeAlias;
|
|
1984
|
+
const nodeAlias = traversal.nodeAlias;
|
|
1985
|
+
const requiredNodeColumns = requiredColumnsByAlias ? requiredColumnsByAlias.get(nodeAlias) ?? EMPTY_REQUIRED_COLUMNS : void 0;
|
|
1986
|
+
const requiredEdgeColumns = requiredColumnsByAlias ? requiredColumnsByAlias.get(edgeAlias) ?? EMPTY_REQUIRED_COLUMNS : void 0;
|
|
1987
|
+
const previousRowColumns = carryForwardPreviousColumns ? [sql`cte_${sql.raw(previousAlias)}.*`] : [
|
|
1988
|
+
sql`cte_${sql.raw(previousAlias)}.${sql.raw(previousAlias)}_id AS ${sql.raw(previousAlias)}_id`,
|
|
1989
|
+
sql`cte_${sql.raw(previousAlias)}.${sql.raw(previousAlias)}_kind AS ${sql.raw(previousAlias)}_kind`
|
|
1990
|
+
];
|
|
1991
|
+
const selectColumns = [
|
|
1992
|
+
...previousRowColumns,
|
|
1993
|
+
...compileEdgeSelectColumns("e", edgeAlias, requiredEdgeColumns),
|
|
1994
|
+
...compileNodeSelectColumns("n", nodeAlias, requiredNodeColumns)
|
|
1995
|
+
];
|
|
1996
|
+
const cteMaterialization = materializeCte ? sql`MATERIALIZED ` : sql``;
|
|
1997
|
+
function compileTraversalBranch(branch) {
|
|
1998
|
+
const whereClauses = [
|
|
1999
|
+
...baseWhereClauses,
|
|
2000
|
+
compileKindFilter(sql.raw("e.kind"), branch.edgeKinds),
|
|
2001
|
+
compileKindFilter(
|
|
2002
|
+
sql.raw(`e.${branch.joinKindField}`),
|
|
2003
|
+
previousNodeKinds
|
|
2004
|
+
),
|
|
2005
|
+
compileKindFilter(sql.raw(`e.${branch.targetKindField}`), nodeKinds)
|
|
2006
|
+
];
|
|
2007
|
+
if (branch.duplicateGuard !== void 0) {
|
|
2008
|
+
whereClauses.push(branch.duplicateGuard);
|
|
2009
|
+
}
|
|
2010
|
+
return sql`
|
|
2011
|
+
SELECT ${sql.join(selectColumns, sql`, `)}
|
|
2012
|
+
FROM cte_${sql.raw(previousAlias)}
|
|
2013
|
+
JOIN ${ctx.schema.edgesTable} e ON cte_${sql.raw(previousAlias)}.${sql.raw(previousAlias)}_id = e.${sql.raw(branch.joinField)}
|
|
2014
|
+
AND cte_${sql.raw(previousAlias)}.${sql.raw(previousAlias)}_kind = e.${sql.raw(branch.joinKindField)}
|
|
2015
|
+
JOIN ${ctx.schema.nodesTable} n ON n.graph_id = e.graph_id
|
|
2016
|
+
AND n.id = e.${sql.raw(branch.targetField)}
|
|
2017
|
+
AND n.kind = e.${sql.raw(branch.targetKindField)}
|
|
2018
|
+
WHERE ${sql.join(whereClauses, sql` AND `)}
|
|
2019
|
+
`;
|
|
2020
|
+
}
|
|
2021
|
+
const directJoinField = traversal.direction === "out" ? "from_id" : "to_id";
|
|
2022
|
+
const directTargetField = traversal.direction === "out" ? "to_id" : "from_id";
|
|
2023
|
+
const directJoinKindField = traversal.direction === "out" ? "from_kind" : "to_kind";
|
|
2024
|
+
const directTargetKindField = traversal.direction === "out" ? "to_kind" : "from_kind";
|
|
2025
|
+
const directBranch = compileTraversalBranch({
|
|
2026
|
+
edgeKinds: directEdgeKinds,
|
|
2027
|
+
joinField: directJoinField,
|
|
2028
|
+
joinKindField: directJoinKindField,
|
|
2029
|
+
targetField: directTargetField,
|
|
2030
|
+
targetKindField: directTargetKindField
|
|
2031
|
+
});
|
|
2032
|
+
if (inverseEdgeKinds.length === 0) {
|
|
2033
|
+
if (traversalLimitValue !== void 0) {
|
|
2034
|
+
return sql`
|
|
2035
|
+
cte_${sql.raw(nodeAlias)} AS ${cteMaterialization}(
|
|
2036
|
+
SELECT * FROM (
|
|
2037
|
+
${directBranch}
|
|
2038
|
+
) AS traversal_rows
|
|
2039
|
+
LIMIT ${traversalLimitValue}
|
|
2040
|
+
)
|
|
2041
|
+
`;
|
|
2042
|
+
}
|
|
2043
|
+
return sql`
|
|
2044
|
+
cte_${sql.raw(nodeAlias)} AS ${cteMaterialization}(
|
|
2045
|
+
${directBranch}
|
|
2046
|
+
)
|
|
2047
|
+
`;
|
|
2048
|
+
}
|
|
2049
|
+
const inverseJoinField = traversal.direction === "out" ? "to_id" : "from_id";
|
|
2050
|
+
const inverseTargetField = traversal.direction === "out" ? "from_id" : "to_id";
|
|
2051
|
+
const inverseJoinKindField = traversal.direction === "out" ? "to_kind" : "from_kind";
|
|
2052
|
+
const inverseTargetKindField = traversal.direction === "out" ? "from_kind" : "to_kind";
|
|
2053
|
+
const overlappingKinds = inverseEdgeKinds.filter(
|
|
2054
|
+
(kind) => directEdgeKinds.includes(kind)
|
|
2055
|
+
);
|
|
2056
|
+
const duplicateGuard = overlappingKinds.length > 0 ? sql`NOT (e.from_id = e.to_id AND ${compileKindFilter(
|
|
2057
|
+
sql.raw("e.kind"),
|
|
2058
|
+
overlappingKinds
|
|
2059
|
+
)})` : void 0;
|
|
2060
|
+
const inverseBranch = compileTraversalBranch({
|
|
2061
|
+
duplicateGuard,
|
|
2062
|
+
edgeKinds: inverseEdgeKinds,
|
|
2063
|
+
joinField: inverseJoinField,
|
|
2064
|
+
joinKindField: inverseJoinKindField,
|
|
2065
|
+
targetField: inverseTargetField,
|
|
2066
|
+
targetKindField: inverseTargetKindField
|
|
2067
|
+
});
|
|
2068
|
+
if (traversalLimitValue !== void 0) {
|
|
2069
|
+
return sql`
|
|
2070
|
+
cte_${sql.raw(nodeAlias)} AS ${cteMaterialization}(
|
|
2071
|
+
SELECT * FROM (
|
|
2072
|
+
${directBranch}
|
|
2073
|
+
UNION ALL
|
|
2074
|
+
${inverseBranch}
|
|
2075
|
+
) AS traversal_rows
|
|
2076
|
+
LIMIT ${traversalLimitValue}
|
|
2077
|
+
)
|
|
2078
|
+
`;
|
|
2079
|
+
}
|
|
2080
|
+
return sql`
|
|
2081
|
+
cte_${sql.raw(nodeAlias)} AS ${cteMaterialization}(
|
|
2082
|
+
${directBranch}
|
|
2083
|
+
UNION ALL
|
|
2084
|
+
${inverseBranch}
|
|
2085
|
+
)
|
|
2086
|
+
`;
|
|
2087
|
+
}
|
|
2088
|
+
function compileAggregateExprFromSource(expr, dialect) {
|
|
2089
|
+
const { field: field2 } = expr;
|
|
2090
|
+
const fn = expr.function;
|
|
2091
|
+
switch (fn) {
|
|
2092
|
+
case "count":
|
|
2093
|
+
case "countDistinct":
|
|
2094
|
+
case "sum":
|
|
2095
|
+
case "avg":
|
|
2096
|
+
case "min":
|
|
2097
|
+
case "max": {
|
|
2098
|
+
const cteAlias = `cte_${field2.alias}`;
|
|
2099
|
+
const column = compileFieldValue(
|
|
2100
|
+
field2,
|
|
2101
|
+
dialect,
|
|
2102
|
+
field2.valueType,
|
|
2103
|
+
cteAlias
|
|
2104
|
+
);
|
|
2105
|
+
if (fn === "countDistinct") {
|
|
2106
|
+
return sql`COUNT(DISTINCT ${column})`;
|
|
2107
|
+
}
|
|
2108
|
+
return sql`${sql.raw(fn.toUpperCase())}(${column})`;
|
|
2109
|
+
}
|
|
2110
|
+
default: {
|
|
2111
|
+
throw new UnsupportedPredicateError(
|
|
2112
|
+
`Unknown aggregate function: ${String(fn)}`
|
|
2113
|
+
);
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
function compileProjectedSource(field2, dialect) {
|
|
2118
|
+
if (isAggregateExpr(field2.source)) {
|
|
2119
|
+
return compileAggregateExprFromSource(field2.source, dialect);
|
|
2120
|
+
}
|
|
2121
|
+
const cteAlias = field2.cteAlias ?? `cte_${field2.source.alias}`;
|
|
2122
|
+
return compileFieldValue(
|
|
2123
|
+
field2.source,
|
|
2124
|
+
dialect,
|
|
2125
|
+
field2.source.valueType,
|
|
2126
|
+
cteAlias
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
function buildStandardProjection(input) {
|
|
2130
|
+
const { ast, collapsedTraversalCteAlias, dialect } = input;
|
|
2131
|
+
if (ast.selectiveFields && ast.selectiveFields.length > 0) {
|
|
2132
|
+
return compileSelectiveProjection(
|
|
2133
|
+
ast.selectiveFields,
|
|
2134
|
+
dialect,
|
|
2135
|
+
ast,
|
|
2136
|
+
collapsedTraversalCteAlias
|
|
2137
|
+
);
|
|
2138
|
+
}
|
|
2139
|
+
const fields = ast.projection.fields;
|
|
2140
|
+
if (fields.length === 0) {
|
|
2141
|
+
return sql.raw("*");
|
|
2142
|
+
}
|
|
2143
|
+
const projectedFields = fields.map((field2) => {
|
|
2144
|
+
const source = compileProjectedSource(field2, dialect);
|
|
2145
|
+
return sql`${source} AS ${quoteIdentifier(field2.outputName)}`;
|
|
2146
|
+
});
|
|
2147
|
+
return sql.join(projectedFields, sql`, `);
|
|
2148
|
+
}
|
|
2149
|
+
function compileSelectiveProjection(fields, dialect, ast, collapsedTraversalCteAlias) {
|
|
2150
|
+
const aliasToCte = /* @__PURE__ */ new Map([
|
|
2151
|
+
[ast.start.alias, `cte_${ast.start.alias}`]
|
|
2152
|
+
]);
|
|
2153
|
+
for (const traversal of ast.traversals) {
|
|
2154
|
+
aliasToCte.set(traversal.nodeAlias, `cte_${traversal.nodeAlias}`);
|
|
2155
|
+
aliasToCte.set(traversal.edgeAlias, `cte_${traversal.nodeAlias}`);
|
|
2156
|
+
}
|
|
2157
|
+
const columns = fields.map((field2) => {
|
|
2158
|
+
const cteAlias = collapsedTraversalCteAlias ?? aliasToCte.get(field2.alias) ?? `cte_${field2.alias}`;
|
|
2159
|
+
if (field2.isSystemField) {
|
|
2160
|
+
const dbColumn = mapSelectiveSystemFieldToColumn(field2.field);
|
|
2161
|
+
return sql`${sql.raw(cteAlias)}.${sql.raw(`${field2.alias}_${dbColumn}`)} AS ${quoteIdentifier(field2.outputName)}`;
|
|
2162
|
+
}
|
|
2163
|
+
const propsColumn = `${field2.alias}_props`;
|
|
2164
|
+
const column = sql`${sql.raw(cteAlias)}.${sql.raw(propsColumn)}`;
|
|
2165
|
+
const pointer = jsonPointer([field2.field]);
|
|
2166
|
+
const extracted = compileTypedJsonExtract({
|
|
2167
|
+
column,
|
|
2168
|
+
dialect,
|
|
2169
|
+
pointer,
|
|
2170
|
+
valueType: field2.valueType
|
|
2171
|
+
});
|
|
2172
|
+
return sql`${extracted} AS ${quoteIdentifier(field2.outputName)}`;
|
|
2173
|
+
});
|
|
2174
|
+
return sql.join(columns, sql`, `);
|
|
2175
|
+
}
|
|
2176
|
+
function buildStandardFromClause(input) {
|
|
2177
|
+
const { ast, collapsedTraversalCteAlias, vectorPredicate } = input;
|
|
2178
|
+
if (collapsedTraversalCteAlias !== void 0) {
|
|
2179
|
+
return sql`FROM ${sql.raw(collapsedTraversalCteAlias)}`;
|
|
2180
|
+
}
|
|
2181
|
+
const startAlias = ast.start.alias;
|
|
2182
|
+
const fromClause = sql`FROM cte_${sql.raw(startAlias)}`;
|
|
2183
|
+
const joins = [];
|
|
2184
|
+
for (const traversal of ast.traversals) {
|
|
2185
|
+
const cteAlias = `cte_${traversal.nodeAlias}`;
|
|
2186
|
+
const previousAlias = traversal.joinFromAlias;
|
|
2187
|
+
const joinType = traversal.optional ? "LEFT JOIN" : "INNER JOIN";
|
|
2188
|
+
joins.push(
|
|
2189
|
+
sql`${sql.raw(joinType)} ${sql.raw(cteAlias)} ON ${sql.raw(cteAlias)}.${sql.raw(previousAlias)}_id = cte_${sql.raw(previousAlias)}.${sql.raw(previousAlias)}_id AND ${sql.raw(cteAlias)}.${sql.raw(previousAlias)}_kind = cte_${sql.raw(previousAlias)}.${sql.raw(previousAlias)}_kind`
|
|
2190
|
+
);
|
|
2191
|
+
}
|
|
2192
|
+
if (vectorPredicate) {
|
|
2193
|
+
const nodeAlias = vectorPredicate.field.alias;
|
|
2194
|
+
joins.push(
|
|
2195
|
+
sql`INNER JOIN cte_embeddings ON cte_embeddings.node_id = cte_${sql.raw(nodeAlias)}.${sql.raw(nodeAlias)}_id`
|
|
2196
|
+
);
|
|
2197
|
+
}
|
|
2198
|
+
return joins.length === 0 ? fromClause : sql`${fromClause} ${sql.join(joins, sql` `)}`;
|
|
2199
|
+
}
|
|
2200
|
+
function buildStandardOrderBy(input) {
|
|
2201
|
+
const { ast, collapsedTraversalCteAlias, dialect } = input;
|
|
2202
|
+
if (!ast.orderBy || ast.orderBy.length === 0) {
|
|
2203
|
+
return void 0;
|
|
2204
|
+
}
|
|
2205
|
+
const parts = [];
|
|
2206
|
+
for (const orderSpec of ast.orderBy) {
|
|
2207
|
+
const valueType = orderSpec.field.valueType;
|
|
2208
|
+
if (valueType === "array" || valueType === "object") {
|
|
2209
|
+
throw new UnsupportedPredicateError(
|
|
2210
|
+
"Ordering by JSON arrays or objects is not supported"
|
|
2211
|
+
);
|
|
2212
|
+
}
|
|
2213
|
+
const cteAlias = collapsedTraversalCteAlias ?? `cte_${orderSpec.field.alias}`;
|
|
2214
|
+
const field2 = compileFieldValue(
|
|
2215
|
+
orderSpec.field,
|
|
2216
|
+
dialect,
|
|
2217
|
+
valueType,
|
|
2218
|
+
cteAlias
|
|
2219
|
+
);
|
|
2220
|
+
const direction = sql.raw(orderSpec.direction.toUpperCase());
|
|
2221
|
+
const nulls = orderSpec.nulls ?? (orderSpec.direction === "asc" ? "last" : "first");
|
|
2222
|
+
const nullsDirection = sql.raw(nulls === "first" ? "DESC" : "ASC");
|
|
2223
|
+
parts.push(
|
|
2224
|
+
sql`(${field2} IS NULL) ${nullsDirection}`,
|
|
2225
|
+
sql`${field2} ${direction}`
|
|
2226
|
+
);
|
|
2227
|
+
}
|
|
2228
|
+
return sql`ORDER BY ${sql.join(parts, sql`, `)}`;
|
|
2229
|
+
}
|
|
2230
|
+
function fieldRefKey(field2) {
|
|
2231
|
+
const pointer = field2.jsonPointer ?? "";
|
|
2232
|
+
return `${field2.alias}:${field2.path.join(".")}:${pointer}`;
|
|
2233
|
+
}
|
|
2234
|
+
function buildStandardGroupBy(input) {
|
|
2235
|
+
const { ast, dialect } = input;
|
|
2236
|
+
if (!ast.groupBy || ast.groupBy.fields.length === 0) {
|
|
2237
|
+
return void 0;
|
|
2238
|
+
}
|
|
2239
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
2240
|
+
const allFields = [];
|
|
2241
|
+
for (const projectedField of ast.projection.fields) {
|
|
2242
|
+
if (projectedField.source.__type === "field_ref") {
|
|
2243
|
+
const key = fieldRefKey(projectedField.source);
|
|
2244
|
+
if (!seenKeys.has(key)) {
|
|
2245
|
+
seenKeys.add(key);
|
|
2246
|
+
allFields.push(projectedField.source);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
for (const field2 of ast.groupBy.fields) {
|
|
2251
|
+
const key = fieldRefKey(field2);
|
|
2252
|
+
if (!seenKeys.has(key)) {
|
|
2253
|
+
seenKeys.add(key);
|
|
2254
|
+
allFields.push(field2);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
if (allFields.length === 0) {
|
|
2258
|
+
return void 0;
|
|
2259
|
+
}
|
|
2260
|
+
const parts = allFields.map(
|
|
2261
|
+
(field2) => compileFieldValue(field2, dialect, field2.valueType, `cte_${field2.alias}`)
|
|
2262
|
+
);
|
|
2263
|
+
return sql`GROUP BY ${sql.join(parts, sql`, `)}`;
|
|
2264
|
+
}
|
|
2265
|
+
function buildStandardHaving(input) {
|
|
2266
|
+
const { ast, ctx } = input;
|
|
2267
|
+
if (!ast.having) {
|
|
2268
|
+
return void 0;
|
|
2269
|
+
}
|
|
2270
|
+
const condition = compilePredicateExpression(ast.having, ctx);
|
|
2271
|
+
return sql`HAVING ${condition}`;
|
|
2272
|
+
}
|
|
2273
|
+
function buildStandardEmbeddingsCte(input) {
|
|
2274
|
+
const { ctx, graphId, vectorPredicate } = input;
|
|
2275
|
+
const { dialect } = ctx;
|
|
2276
|
+
const { field: field2, metric, minScore, queryEmbedding } = vectorPredicate;
|
|
2277
|
+
const fieldPath = field2.jsonPointer ? field2.jsonPointer : field2.path.length > 1 && field2.path[0] === "props" ? `/${field2.path.slice(1).join("/")}` : `/${field2.path.join("/")}`;
|
|
2278
|
+
const distanceExpr = dialect.vectorDistance(
|
|
2279
|
+
sql.raw("embedding"),
|
|
2280
|
+
queryEmbedding,
|
|
2281
|
+
metric
|
|
2282
|
+
);
|
|
2283
|
+
const conditions = [
|
|
2284
|
+
sql`graph_id = ${graphId}`,
|
|
2285
|
+
sql`field_path = ${fieldPath}`
|
|
2056
2286
|
];
|
|
2057
|
-
if (
|
|
2058
|
-
|
|
2287
|
+
if (minScore !== void 0) {
|
|
2288
|
+
conditions.push(
|
|
2289
|
+
compileVectorMinScoreCondition(distanceExpr, metric, minScore)
|
|
2290
|
+
);
|
|
2291
|
+
}
|
|
2292
|
+
const scoreExpr = compileVectorScoreExpression(distanceExpr, metric);
|
|
2293
|
+
return sql`
|
|
2294
|
+
cte_embeddings AS (
|
|
2295
|
+
SELECT
|
|
2296
|
+
node_id,
|
|
2297
|
+
${distanceExpr} AS distance,
|
|
2298
|
+
${scoreExpr} AS score
|
|
2299
|
+
FROM ${ctx.schema.embeddingsTable}
|
|
2300
|
+
WHERE ${sql.join(conditions, sql` AND `)}
|
|
2301
|
+
ORDER BY ${distanceExpr} ASC
|
|
2302
|
+
)
|
|
2303
|
+
`;
|
|
2304
|
+
}
|
|
2305
|
+
function compileVectorScoreExpression(distanceExpr, metric) {
|
|
2306
|
+
switch (metric) {
|
|
2307
|
+
case "cosine": {
|
|
2308
|
+
return sql`(1.0 - ${distanceExpr})`;
|
|
2309
|
+
}
|
|
2310
|
+
case "l2":
|
|
2311
|
+
case "inner_product": {
|
|
2312
|
+
return distanceExpr;
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
function compileVectorMinScoreCondition(distanceExpr, metric, minScore) {
|
|
2317
|
+
switch (metric) {
|
|
2318
|
+
case "cosine": {
|
|
2319
|
+
const threshold = 1 - minScore;
|
|
2320
|
+
return sql`${distanceExpr} <= ${threshold}`;
|
|
2321
|
+
}
|
|
2322
|
+
case "l2": {
|
|
2323
|
+
return sql`${distanceExpr} <= ${minScore}`;
|
|
2324
|
+
}
|
|
2325
|
+
case "inner_product": {
|
|
2326
|
+
const negativeThreshold = -minScore;
|
|
2327
|
+
return sql`${distanceExpr} <= ${negativeThreshold}`;
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
function buildStandardVectorOrderBy(input) {
|
|
2332
|
+
const { ast, dialect } = input;
|
|
2333
|
+
const distanceOrder = sql`cte_embeddings.distance ASC`;
|
|
2334
|
+
const additionalOrders = [];
|
|
2335
|
+
if (ast.orderBy && ast.orderBy.length > 0) {
|
|
2336
|
+
for (const orderSpec of ast.orderBy) {
|
|
2337
|
+
const valueType = orderSpec.field.valueType;
|
|
2338
|
+
if (valueType === "array" || valueType === "object") {
|
|
2339
|
+
throw new UnsupportedPredicateError(
|
|
2340
|
+
"Ordering by JSON arrays or objects is not supported"
|
|
2341
|
+
);
|
|
2342
|
+
}
|
|
2343
|
+
const cteAlias = `cte_${orderSpec.field.alias}`;
|
|
2344
|
+
const field2 = compileFieldValue(
|
|
2345
|
+
orderSpec.field,
|
|
2346
|
+
dialect,
|
|
2347
|
+
valueType,
|
|
2348
|
+
cteAlias
|
|
2349
|
+
);
|
|
2350
|
+
const direction = sql.raw(orderSpec.direction.toUpperCase());
|
|
2351
|
+
const nulls = orderSpec.nulls ?? (orderSpec.direction === "asc" ? "last" : "first");
|
|
2352
|
+
const nullsDirection = sql.raw(nulls === "first" ? "DESC" : "ASC");
|
|
2353
|
+
additionalOrders.push(
|
|
2354
|
+
sql`(${field2} IS NULL) ${nullsDirection}`,
|
|
2355
|
+
sql`${field2} ${direction}`
|
|
2356
|
+
);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
const allOrders = [distanceOrder, ...additionalOrders];
|
|
2360
|
+
return sql`ORDER BY ${sql.join(allOrders, sql`, `)}`;
|
|
2361
|
+
}
|
|
2362
|
+
function buildLimitOffsetClause(input) {
|
|
2363
|
+
const { limit, offset } = input;
|
|
2364
|
+
const parts = [];
|
|
2365
|
+
if (limit !== void 0) {
|
|
2366
|
+
parts.push(sql`LIMIT ${limit}`);
|
|
2367
|
+
}
|
|
2368
|
+
if (offset !== void 0) {
|
|
2369
|
+
parts.push(sql`OFFSET ${offset}`);
|
|
2370
|
+
}
|
|
2371
|
+
return parts.length > 0 ? sql.join(parts, sql` `) : void 0;
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
// src/query/compiler/passes/recursive.ts
|
|
2375
|
+
function runRecursiveTraversalSelectionPass(ast) {
|
|
2376
|
+
const variableLengthTraversal = ast.traversals.find(
|
|
2377
|
+
(traversal) => traversal.variableLength !== void 0
|
|
2378
|
+
);
|
|
2379
|
+
if (variableLengthTraversal === void 0) {
|
|
2380
|
+
throw new CompilerInvariantError("No variable-length traversal found");
|
|
2381
|
+
}
|
|
2382
|
+
if (ast.traversals.length > 1) {
|
|
2383
|
+
throw new UnsupportedPredicateError(
|
|
2384
|
+
"Variable-length traversals with multiple traversals are not yet supported. Please use a single variable-length traversal."
|
|
2385
|
+
);
|
|
2386
|
+
}
|
|
2387
|
+
return variableLengthTraversal;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
// src/query/compiler/passes/runner.ts
|
|
2391
|
+
function runCompilerPass(state, pass) {
|
|
2392
|
+
const output = pass.execute(state);
|
|
2393
|
+
return {
|
|
2394
|
+
state: pass.update(state, output)
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
function compileTemporalFilter(options) {
|
|
2398
|
+
const { mode, asOf, tableAlias, currentTimestamp } = options;
|
|
2399
|
+
const prefix = tableAlias ? sql.raw(`${tableAlias}.`) : sql.raw("");
|
|
2400
|
+
const deletedAt = sql`${prefix}deleted_at`;
|
|
2401
|
+
const validFrom = sql`${prefix}valid_from`;
|
|
2402
|
+
const validTo = sql`${prefix}valid_to`;
|
|
2403
|
+
switch (mode) {
|
|
2404
|
+
case "current": {
|
|
2405
|
+
const now = currentTimestamp ?? sql`CURRENT_TIMESTAMP`;
|
|
2406
|
+
return sql`${deletedAt} IS NULL AND (${validFrom} IS NULL OR ${validFrom} <= ${now}) AND (${validTo} IS NULL OR ${validTo} > ${now})`;
|
|
2407
|
+
}
|
|
2408
|
+
case "asOf": {
|
|
2409
|
+
const timestamp = asOf;
|
|
2410
|
+
return sql`${deletedAt} IS NULL AND (${validFrom} IS NULL OR ${validFrom} <= ${timestamp}) AND (${validTo} IS NULL OR ${validTo} > ${timestamp})`;
|
|
2411
|
+
}
|
|
2412
|
+
case "includeEnded": {
|
|
2413
|
+
return sql`${deletedAt} IS NULL`;
|
|
2414
|
+
}
|
|
2415
|
+
case "includeTombstones": {
|
|
2416
|
+
return sql.raw("1=1");
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
function extractTemporalOptions(ast, tableAlias) {
|
|
2421
|
+
return {
|
|
2422
|
+
mode: ast.temporalMode.mode,
|
|
2423
|
+
asOf: ast.temporalMode.asOf,
|
|
2424
|
+
tableAlias
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
// src/query/compiler/passes/temporal.ts
|
|
2429
|
+
function createTemporalFilterPass(ast, currentTimestamp) {
|
|
2430
|
+
return {
|
|
2431
|
+
forAlias(tableAlias) {
|
|
2432
|
+
return compileTemporalFilter({
|
|
2433
|
+
...extractTemporalOptions(ast, tableAlias),
|
|
2434
|
+
currentTimestamp
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
// src/query/compiler/passes/vector.ts
|
|
2441
|
+
function runVectorPredicatePass(ast, dialect) {
|
|
2442
|
+
const vectorPredicates = extractVectorSimilarityPredicates(ast.predicates);
|
|
2443
|
+
if (vectorPredicates.length > 1) {
|
|
2444
|
+
throw new UnsupportedPredicateError(
|
|
2445
|
+
"Multiple vector similarity predicates in a single query are not supported"
|
|
2446
|
+
);
|
|
2447
|
+
}
|
|
2448
|
+
const vectorPredicate = vectorPredicates[0];
|
|
2449
|
+
if (vectorPredicate === void 0) {
|
|
2450
|
+
return { vectorPredicate: void 0 };
|
|
2451
|
+
}
|
|
2452
|
+
const vectorStrategy = dialect.capabilities.vectorPredicateStrategy;
|
|
2453
|
+
if (vectorStrategy === "unsupported" || !dialect.supportsVectors) {
|
|
2454
|
+
throw new UnsupportedPredicateError(
|
|
2455
|
+
`Vector similarity predicates are not supported for dialect "${dialect.name}"`
|
|
2456
|
+
);
|
|
2457
|
+
}
|
|
2458
|
+
if (!dialect.capabilities.vectorMetrics.includes(vectorPredicate.metric)) {
|
|
2459
|
+
throw new UnsupportedPredicateError(
|
|
2460
|
+
`Vector metric "${vectorPredicate.metric}" is not supported for dialect "${dialect.name}"`
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
if (!Number.isFinite(vectorPredicate.limit) || vectorPredicate.limit <= 0) {
|
|
2464
|
+
throw new UnsupportedPredicateError(
|
|
2465
|
+
`Vector predicate limit must be a positive finite number, got ${String(vectorPredicate.limit)}`
|
|
2466
|
+
);
|
|
2467
|
+
}
|
|
2468
|
+
const { minScore } = vectorPredicate;
|
|
2469
|
+
if (minScore !== void 0) {
|
|
2470
|
+
if (!Number.isFinite(minScore)) {
|
|
2471
|
+
throw new UnsupportedPredicateError(
|
|
2472
|
+
`Vector minScore must be a finite number, got ${String(minScore)}`
|
|
2473
|
+
);
|
|
2474
|
+
}
|
|
2475
|
+
if (vectorPredicate.metric === "cosine" && (minScore < -1 || minScore > 1)) {
|
|
2476
|
+
throw new UnsupportedPredicateError(
|
|
2477
|
+
`Cosine minScore must be between -1 and 1, got ${String(minScore)}`
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
return { vectorPredicate };
|
|
2482
|
+
}
|
|
2483
|
+
function resolveVectorAwareLimit(astLimit, vectorPredicate) {
|
|
2484
|
+
if (vectorPredicate === void 0) {
|
|
2485
|
+
return astLimit;
|
|
2486
|
+
}
|
|
2487
|
+
if (astLimit === void 0) {
|
|
2488
|
+
return vectorPredicate.limit;
|
|
2489
|
+
}
|
|
2490
|
+
return Math.min(astLimit, vectorPredicate.limit);
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
// src/query/compiler/plan/lowering.ts
|
|
2494
|
+
function createPlanNodeIdFactory() {
|
|
2495
|
+
let current = 0;
|
|
2496
|
+
return function nextPlanNodeId() {
|
|
2497
|
+
current += 1;
|
|
2498
|
+
return `plan_${current.toString(36)}`;
|
|
2499
|
+
};
|
|
2500
|
+
}
|
|
2501
|
+
function extractAggregateExpressions(ast) {
|
|
2502
|
+
const aggregates = [];
|
|
2503
|
+
for (const field2 of ast.projection.fields) {
|
|
2504
|
+
if ("__type" in field2.source && field2.source.__type === "aggregate") {
|
|
2505
|
+
aggregates.push(field2.source);
|
|
2506
|
+
}
|
|
2059
2507
|
}
|
|
2060
|
-
|
|
2061
|
-
|
|
2508
|
+
return aggregates;
|
|
2509
|
+
}
|
|
2510
|
+
function getAliasPredicates(ast, alias, predicateTargetType) {
|
|
2511
|
+
return ast.predicates.filter((predicate2) => {
|
|
2512
|
+
const targetType = predicate2.targetType ?? "node";
|
|
2513
|
+
return predicate2.targetAlias === alias && targetType === predicateTargetType;
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2516
|
+
function wrapWithAliasFilterNode(currentNode, ast, alias, predicateTargetType, nextPlanNodeId) {
|
|
2517
|
+
const aliasPredicates = getAliasPredicates(ast, alias, predicateTargetType);
|
|
2518
|
+
if (aliasPredicates.length === 0) {
|
|
2519
|
+
return currentNode;
|
|
2062
2520
|
}
|
|
2063
|
-
return
|
|
2521
|
+
return {
|
|
2522
|
+
alias,
|
|
2523
|
+
id: nextPlanNodeId(),
|
|
2524
|
+
input: currentNode,
|
|
2525
|
+
op: "filter",
|
|
2526
|
+
predicateTargetType,
|
|
2527
|
+
predicates: aliasPredicates.map((predicate2) => predicate2.expression)
|
|
2528
|
+
};
|
|
2064
2529
|
}
|
|
2065
|
-
function
|
|
2066
|
-
|
|
2067
|
-
const
|
|
2068
|
-
if (
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2530
|
+
function appendAggregateSortLimitAndProjectNodes(currentNode, ast, nextPlanNodeId, limit, collapsedTraversalCteAlias) {
|
|
2531
|
+
let node = currentNode;
|
|
2532
|
+
const aggregateExpressions = extractAggregateExpressions(ast);
|
|
2533
|
+
if (aggregateExpressions.length > 0 || ast.groupBy !== void 0 || ast.having !== void 0) {
|
|
2534
|
+
const aggregateNode = {
|
|
2535
|
+
aggregates: aggregateExpressions,
|
|
2536
|
+
groupBy: ast.groupBy?.fields ?? [],
|
|
2537
|
+
id: nextPlanNodeId(),
|
|
2538
|
+
input: node,
|
|
2539
|
+
op: "aggregate"
|
|
2540
|
+
};
|
|
2541
|
+
node = ast.having === void 0 ? aggregateNode : { ...aggregateNode, having: ast.having };
|
|
2073
2542
|
}
|
|
2074
|
-
if (
|
|
2075
|
-
|
|
2543
|
+
if (ast.orderBy !== void 0 && ast.orderBy.length > 0) {
|
|
2544
|
+
node = {
|
|
2545
|
+
id: nextPlanNodeId(),
|
|
2546
|
+
input: node,
|
|
2547
|
+
op: "sort",
|
|
2548
|
+
orderBy: ast.orderBy
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
if (limit !== void 0 || ast.offset !== void 0) {
|
|
2552
|
+
const limitOffsetNodeBase = {
|
|
2553
|
+
id: nextPlanNodeId(),
|
|
2554
|
+
input: node,
|
|
2555
|
+
op: "limit_offset"
|
|
2556
|
+
};
|
|
2557
|
+
const hasLimit = limit !== void 0;
|
|
2558
|
+
const hasOffset = ast.offset !== void 0;
|
|
2559
|
+
if (hasLimit && hasOffset) {
|
|
2560
|
+
node = {
|
|
2561
|
+
...limitOffsetNodeBase,
|
|
2562
|
+
limit,
|
|
2563
|
+
offset: ast.offset
|
|
2564
|
+
};
|
|
2565
|
+
} else if (hasLimit) {
|
|
2566
|
+
node = { ...limitOffsetNodeBase, limit };
|
|
2567
|
+
} else if (hasOffset) {
|
|
2568
|
+
node = { ...limitOffsetNodeBase, offset: ast.offset };
|
|
2569
|
+
} else {
|
|
2076
2570
|
throw new CompilerInvariantError(
|
|
2077
|
-
"
|
|
2078
|
-
{ component: "set-operation-emitter" }
|
|
2571
|
+
"limit_offset node requires limit or offset to be present"
|
|
2079
2572
|
);
|
|
2080
2573
|
}
|
|
2081
|
-
return;
|
|
2082
|
-
}
|
|
2083
|
-
const limitOffsetClauseCount = shape.limitOffsetNode === void 0 ? 0 : (shape.limitOffsetNode.limit === void 0 ? 0 : 1) + (shape.limitOffsetNode.offset === void 0 ? 0 : 1);
|
|
2084
|
-
const expectedClauseCount = (shape.sortNode === void 0 ? 0 : 1) + limitOffsetClauseCount;
|
|
2085
|
-
if (suffixClauses.length !== expectedClauseCount) {
|
|
2086
|
-
throw new CompilerInvariantError(
|
|
2087
|
-
`Set-operation SQL emitter expected ${String(expectedClauseCount)} top-level suffix clause(s) from logical plan, got ${String(suffixClauses.length)}`,
|
|
2088
|
-
{ component: "set-operation-emitter" }
|
|
2089
|
-
);
|
|
2090
2574
|
}
|
|
2575
|
+
const projectNodeBase = {
|
|
2576
|
+
fields: ast.projection.fields,
|
|
2577
|
+
id: nextPlanNodeId(),
|
|
2578
|
+
input: node,
|
|
2579
|
+
op: "project"
|
|
2580
|
+
};
|
|
2581
|
+
return collapsedTraversalCteAlias === void 0 ? projectNodeBase : {
|
|
2582
|
+
...projectNodeBase,
|
|
2583
|
+
collapsedTraversalAlias: collapsedTraversalCteAlias
|
|
2584
|
+
};
|
|
2091
2585
|
}
|
|
2092
|
-
function
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2586
|
+
function lowerStandardQueryToLogicalPlanNode(input) {
|
|
2587
|
+
const { ast, nextPlanNodeId } = input;
|
|
2588
|
+
let currentNode = {
|
|
2589
|
+
alias: ast.start.alias,
|
|
2590
|
+
graphId: input.graphId,
|
|
2591
|
+
id: nextPlanNodeId(),
|
|
2592
|
+
kinds: ast.start.kinds,
|
|
2593
|
+
op: "scan",
|
|
2594
|
+
source: "nodes"
|
|
2595
|
+
};
|
|
2596
|
+
currentNode = wrapWithAliasFilterNode(
|
|
2597
|
+
currentNode,
|
|
2598
|
+
ast,
|
|
2599
|
+
ast.start.alias,
|
|
2600
|
+
"node",
|
|
2601
|
+
nextPlanNodeId
|
|
2096
2602
|
);
|
|
2097
|
-
const
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
"Standard SQL emitter received HAVING clause for a plan without aggregate nodes",
|
|
2118
|
-
{ component: "standard-emitter" }
|
|
2603
|
+
for (const traversal of ast.traversals) {
|
|
2604
|
+
currentNode = {
|
|
2605
|
+
direction: traversal.direction,
|
|
2606
|
+
edgeAlias: traversal.edgeAlias,
|
|
2607
|
+
edgeKinds: traversal.edgeKinds,
|
|
2608
|
+
id: nextPlanNodeId(),
|
|
2609
|
+
input: currentNode,
|
|
2610
|
+
inverseEdgeKinds: traversal.inverseEdgeKinds ?? [],
|
|
2611
|
+
joinFromAlias: traversal.joinFromAlias,
|
|
2612
|
+
joinType: traversal.optional ? "left" : "inner",
|
|
2613
|
+
nodeAlias: traversal.nodeAlias,
|
|
2614
|
+
nodeKinds: traversal.nodeKinds,
|
|
2615
|
+
op: "join"
|
|
2616
|
+
};
|
|
2617
|
+
currentNode = wrapWithAliasFilterNode(
|
|
2618
|
+
currentNode,
|
|
2619
|
+
ast,
|
|
2620
|
+
traversal.edgeAlias,
|
|
2621
|
+
"edge",
|
|
2622
|
+
nextPlanNodeId
|
|
2119
2623
|
);
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
"
|
|
2125
|
-
|
|
2624
|
+
currentNode = wrapWithAliasFilterNode(
|
|
2625
|
+
currentNode,
|
|
2626
|
+
ast,
|
|
2627
|
+
traversal.nodeAlias,
|
|
2628
|
+
"node",
|
|
2629
|
+
nextPlanNodeId
|
|
2126
2630
|
);
|
|
2127
2631
|
}
|
|
2128
|
-
if (
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2632
|
+
if (input.vectorPredicate !== void 0) {
|
|
2633
|
+
currentNode = {
|
|
2634
|
+
id: nextPlanNodeId(),
|
|
2635
|
+
input: currentNode,
|
|
2636
|
+
op: "vector_knn",
|
|
2637
|
+
predicate: input.vectorPredicate
|
|
2638
|
+
};
|
|
2133
2639
|
}
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2640
|
+
return appendAggregateSortLimitAndProjectNodes(
|
|
2641
|
+
currentNode,
|
|
2642
|
+
ast,
|
|
2643
|
+
nextPlanNodeId,
|
|
2644
|
+
input.effectiveLimit,
|
|
2645
|
+
input.collapsedTraversalCteAlias
|
|
2646
|
+
);
|
|
2647
|
+
}
|
|
2648
|
+
function lowerRecursiveQueryToLogicalPlanNode(input) {
|
|
2649
|
+
const { ast, nextPlanNodeId } = input;
|
|
2650
|
+
const traversal = input.traversal ?? runRecursiveTraversalSelectionPass(input.ast);
|
|
2651
|
+
let currentNode = {
|
|
2652
|
+
alias: ast.start.alias,
|
|
2653
|
+
graphId: input.graphId,
|
|
2654
|
+
id: nextPlanNodeId(),
|
|
2655
|
+
kinds: ast.start.kinds,
|
|
2656
|
+
op: "scan",
|
|
2657
|
+
source: "nodes"
|
|
2658
|
+
};
|
|
2659
|
+
currentNode = wrapWithAliasFilterNode(
|
|
2660
|
+
currentNode,
|
|
2661
|
+
ast,
|
|
2662
|
+
ast.start.alias,
|
|
2663
|
+
"node",
|
|
2664
|
+
nextPlanNodeId
|
|
2665
|
+
);
|
|
2666
|
+
currentNode = {
|
|
2667
|
+
edgeAlias: traversal.edgeAlias,
|
|
2668
|
+
edgeKinds: traversal.edgeKinds,
|
|
2669
|
+
id: nextPlanNodeId(),
|
|
2670
|
+
input: currentNode,
|
|
2671
|
+
inverseEdgeKinds: traversal.inverseEdgeKinds ?? [],
|
|
2672
|
+
nodeAlias: traversal.nodeAlias,
|
|
2673
|
+
nodeKinds: traversal.nodeKinds,
|
|
2674
|
+
op: "recursive_expand",
|
|
2675
|
+
traversal: traversal.variableLength
|
|
2676
|
+
};
|
|
2677
|
+
currentNode = wrapWithAliasFilterNode(
|
|
2678
|
+
currentNode,
|
|
2679
|
+
ast,
|
|
2680
|
+
traversal.edgeAlias,
|
|
2681
|
+
"edge",
|
|
2682
|
+
nextPlanNodeId
|
|
2683
|
+
);
|
|
2684
|
+
currentNode = wrapWithAliasFilterNode(
|
|
2685
|
+
currentNode,
|
|
2686
|
+
ast,
|
|
2687
|
+
traversal.nodeAlias,
|
|
2688
|
+
"node",
|
|
2689
|
+
nextPlanNodeId
|
|
2690
|
+
);
|
|
2691
|
+
return appendAggregateSortLimitAndProjectNodes(
|
|
2692
|
+
currentNode,
|
|
2693
|
+
ast,
|
|
2694
|
+
nextPlanNodeId,
|
|
2695
|
+
ast.limit
|
|
2696
|
+
);
|
|
2697
|
+
}
|
|
2698
|
+
function lowerComposableQueryToLogicalPlanNode(query, dialect, graphId, nextPlanNodeId) {
|
|
2699
|
+
if ("__type" in query) {
|
|
2700
|
+
return lowerSetOperationToLogicalPlanNode(
|
|
2701
|
+
query,
|
|
2702
|
+
graphId,
|
|
2703
|
+
dialect,
|
|
2704
|
+
nextPlanNodeId
|
|
2138
2705
|
);
|
|
2139
2706
|
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2707
|
+
const hasVariableLengthTraversal2 = query.traversals.some(
|
|
2708
|
+
(traversal) => traversal.variableLength !== void 0
|
|
2709
|
+
);
|
|
2710
|
+
if (hasVariableLengthTraversal2) {
|
|
2711
|
+
return lowerRecursiveQueryToLogicalPlanNode({
|
|
2712
|
+
ast: query,
|
|
2713
|
+
graphId,
|
|
2714
|
+
nextPlanNodeId
|
|
2715
|
+
});
|
|
2145
2716
|
}
|
|
2717
|
+
const vectorPredicate = runVectorPredicatePass(
|
|
2718
|
+
query,
|
|
2719
|
+
getDialect(dialect)
|
|
2720
|
+
).vectorPredicate;
|
|
2721
|
+
const effectiveLimit = resolveVectorAwareLimit(query.limit, vectorPredicate);
|
|
2722
|
+
const loweringInput = {
|
|
2723
|
+
ast: query,
|
|
2724
|
+
graphId,
|
|
2725
|
+
nextPlanNodeId,
|
|
2726
|
+
...effectiveLimit === void 0 ? {} : { effectiveLimit },
|
|
2727
|
+
...vectorPredicate === void 0 ? {} : { vectorPredicate }
|
|
2728
|
+
};
|
|
2729
|
+
return lowerStandardQueryToLogicalPlanNode(loweringInput);
|
|
2146
2730
|
}
|
|
2147
|
-
function
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2731
|
+
function lowerSetOperationToLogicalPlanNode(op, graphId, dialect, nextPlanNodeId) {
|
|
2732
|
+
let currentNode = {
|
|
2733
|
+
id: nextPlanNodeId(),
|
|
2734
|
+
left: lowerComposableQueryToLogicalPlanNode(
|
|
2735
|
+
op.left,
|
|
2736
|
+
dialect,
|
|
2737
|
+
graphId,
|
|
2738
|
+
nextPlanNodeId
|
|
2739
|
+
),
|
|
2740
|
+
op: "set_op",
|
|
2741
|
+
operator: op.operator,
|
|
2742
|
+
right: lowerComposableQueryToLogicalPlanNode(
|
|
2743
|
+
op.right,
|
|
2744
|
+
dialect,
|
|
2745
|
+
graphId,
|
|
2746
|
+
nextPlanNodeId
|
|
2747
|
+
)
|
|
2748
|
+
};
|
|
2749
|
+
if (op.orderBy !== void 0 && op.orderBy.length > 0) {
|
|
2750
|
+
currentNode = {
|
|
2751
|
+
id: nextPlanNodeId(),
|
|
2752
|
+
input: currentNode,
|
|
2753
|
+
op: "sort",
|
|
2754
|
+
orderBy: op.orderBy
|
|
2755
|
+
};
|
|
2165
2756
|
}
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
}
|
|
2175
|
-
|
|
2176
|
-
const byAliasAndType = /* @__PURE__ */ new Map();
|
|
2177
|
-
for (const predicate2 of ast.predicates) {
|
|
2178
|
-
const key = buildPredicateIndexKey(
|
|
2179
|
-
predicate2.targetAlias,
|
|
2180
|
-
resolvePredicateTargetType(predicate2)
|
|
2181
|
-
);
|
|
2182
|
-
const existing = byAliasAndType.get(key);
|
|
2183
|
-
if (existing === void 0) {
|
|
2184
|
-
byAliasAndType.set(key, [predicate2]);
|
|
2757
|
+
if (op.limit !== void 0 || op.offset !== void 0) {
|
|
2758
|
+
const limitOffsetBase = {
|
|
2759
|
+
id: nextPlanNodeId(),
|
|
2760
|
+
input: currentNode,
|
|
2761
|
+
op: "limit_offset"
|
|
2762
|
+
};
|
|
2763
|
+
if (op.limit !== void 0 && op.offset !== void 0) {
|
|
2764
|
+
currentNode = { ...limitOffsetBase, limit: op.limit, offset: op.offset };
|
|
2765
|
+
} else if (op.limit === void 0) {
|
|
2766
|
+
currentNode = { ...limitOffsetBase, offset: op.offset };
|
|
2185
2767
|
} else {
|
|
2186
|
-
|
|
2768
|
+
currentNode = { ...limitOffsetBase, limit: op.limit };
|
|
2187
2769
|
}
|
|
2188
2770
|
}
|
|
2189
|
-
return
|
|
2190
|
-
}
|
|
2191
|
-
function getPredicatesForAlias(predicateIndex, alias, targetType) {
|
|
2192
|
-
return predicateIndex.byAliasAndType.get(
|
|
2193
|
-
buildPredicateIndexKey(alias, targetType)
|
|
2194
|
-
) ?? EMPTY_PREDICATES;
|
|
2771
|
+
return currentNode;
|
|
2195
2772
|
}
|
|
2196
|
-
function
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2773
|
+
function lowerStandardQueryToLogicalPlan(input) {
|
|
2774
|
+
const nextPlanNodeId = createPlanNodeIdFactory();
|
|
2775
|
+
return {
|
|
2776
|
+
metadata: {
|
|
2777
|
+
dialect: input.dialect,
|
|
2778
|
+
graphId: input.graphId
|
|
2779
|
+
},
|
|
2780
|
+
root: lowerStandardQueryToLogicalPlanNode({
|
|
2781
|
+
...input,
|
|
2782
|
+
nextPlanNodeId
|
|
2783
|
+
})
|
|
2784
|
+
};
|
|
2200
2785
|
}
|
|
2201
|
-
function
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2786
|
+
function lowerRecursiveQueryToLogicalPlan(input) {
|
|
2787
|
+
const nextPlanNodeId = createPlanNodeIdFactory();
|
|
2788
|
+
return {
|
|
2789
|
+
metadata: {
|
|
2790
|
+
dialect: input.dialect,
|
|
2791
|
+
graphId: input.graphId
|
|
2792
|
+
},
|
|
2793
|
+
root: lowerRecursiveQueryToLogicalPlanNode({
|
|
2794
|
+
...input,
|
|
2795
|
+
nextPlanNodeId
|
|
2796
|
+
})
|
|
2797
|
+
};
|
|
2212
2798
|
}
|
|
2213
|
-
function
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2799
|
+
function lowerSetOperationToLogicalPlan(input) {
|
|
2800
|
+
const nextPlanNodeId = createPlanNodeIdFactory();
|
|
2801
|
+
return {
|
|
2802
|
+
metadata: {
|
|
2803
|
+
dialect: input.dialect,
|
|
2804
|
+
graphId: input.graphId
|
|
2805
|
+
},
|
|
2806
|
+
root: lowerSetOperationToLogicalPlanNode(
|
|
2807
|
+
input.op,
|
|
2808
|
+
input.graphId,
|
|
2809
|
+
input.dialect,
|
|
2810
|
+
nextPlanNodeId
|
|
2811
|
+
)
|
|
2812
|
+
};
|
|
2223
2813
|
}
|
|
2224
2814
|
|
|
2225
|
-
// src/query/compiler/
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2815
|
+
// src/query/compiler/recursive.ts
|
|
2816
|
+
var MAX_RECURSIVE_DEPTH = 100;
|
|
2817
|
+
var MAX_EXPLICIT_RECURSIVE_DEPTH = 1e3;
|
|
2818
|
+
var NO_ALWAYS_REQUIRED_COLUMNS = /* @__PURE__ */ new Set();
|
|
2819
|
+
function runRecursiveQueryPassPipeline(ast, graphId, ctx) {
|
|
2820
|
+
let state = {
|
|
2821
|
+
ast,
|
|
2822
|
+
ctx,
|
|
2823
|
+
graphId,
|
|
2824
|
+
logicalPlan: void 0,
|
|
2825
|
+
requiredColumnsByAlias: void 0,
|
|
2826
|
+
temporalFilterPass: void 0,
|
|
2827
|
+
traversal: void 0
|
|
2828
|
+
};
|
|
2829
|
+
const recursiveTraversalPass = runCompilerPass(state, {
|
|
2830
|
+
name: "recursive_traversal",
|
|
2831
|
+
execute(currentState) {
|
|
2832
|
+
return runRecursiveTraversalSelectionPass(currentState.ast);
|
|
2833
|
+
},
|
|
2834
|
+
update(currentState, traversal) {
|
|
2835
|
+
return {
|
|
2836
|
+
...currentState,
|
|
2837
|
+
traversal
|
|
2838
|
+
};
|
|
2238
2839
|
}
|
|
2239
|
-
|
|
2240
|
-
|
|
2840
|
+
});
|
|
2841
|
+
state = recursiveTraversalPass.state;
|
|
2842
|
+
const temporalPass = runCompilerPass(state, {
|
|
2843
|
+
name: "temporal_filters",
|
|
2844
|
+
execute(currentState) {
|
|
2845
|
+
return createTemporalFilterPass(
|
|
2846
|
+
currentState.ast,
|
|
2847
|
+
currentState.ctx.dialect.currentTimestamp()
|
|
2848
|
+
);
|
|
2849
|
+
},
|
|
2850
|
+
update(currentState, temporalFilterPass) {
|
|
2851
|
+
return {
|
|
2852
|
+
...currentState,
|
|
2853
|
+
temporalFilterPass
|
|
2854
|
+
};
|
|
2241
2855
|
}
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2856
|
+
});
|
|
2857
|
+
state = temporalPass.state;
|
|
2858
|
+
const columnPruningPass = runCompilerPass(state, {
|
|
2859
|
+
name: "column_pruning",
|
|
2860
|
+
execute(currentState) {
|
|
2861
|
+
const traversal = currentState.traversal;
|
|
2862
|
+
if (traversal === void 0) {
|
|
2863
|
+
throw new CompilerInvariantError(
|
|
2864
|
+
"Recursive traversal pass did not select traversal"
|
|
2865
|
+
);
|
|
2866
|
+
}
|
|
2867
|
+
return collectRequiredColumnsByAlias(currentState.ast, traversal);
|
|
2868
|
+
},
|
|
2869
|
+
update(currentState, requiredColumnsByAlias) {
|
|
2870
|
+
return {
|
|
2871
|
+
...currentState,
|
|
2872
|
+
requiredColumnsByAlias
|
|
2873
|
+
};
|
|
2248
2874
|
}
|
|
2249
|
-
|
|
2250
|
-
|
|
2875
|
+
});
|
|
2876
|
+
state = columnPruningPass.state;
|
|
2877
|
+
const logicalPlanPass = runCompilerPass(state, {
|
|
2878
|
+
name: "logical_plan",
|
|
2879
|
+
execute(currentState) {
|
|
2880
|
+
const loweringInput = {
|
|
2881
|
+
ast: currentState.ast,
|
|
2882
|
+
dialect: currentState.ctx.dialect.name,
|
|
2883
|
+
graphId: currentState.graphId,
|
|
2884
|
+
...currentState.traversal === void 0 ? {} : { traversal: currentState.traversal }
|
|
2885
|
+
};
|
|
2886
|
+
return lowerRecursiveQueryToLogicalPlan(loweringInput);
|
|
2887
|
+
},
|
|
2888
|
+
update(currentState, logicalPlan) {
|
|
2889
|
+
return {
|
|
2890
|
+
...currentState,
|
|
2891
|
+
logicalPlan
|
|
2892
|
+
};
|
|
2251
2893
|
}
|
|
2252
|
-
}
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
"id",
|
|
2256
|
-
"kind",
|
|
2257
|
-
"props",
|
|
2258
|
-
"version",
|
|
2259
|
-
"valid_from",
|
|
2260
|
-
"valid_to",
|
|
2261
|
-
"created_at",
|
|
2262
|
-
"updated_at",
|
|
2263
|
-
"deleted_at"
|
|
2264
|
-
];
|
|
2265
|
-
var EDGE_COLUMNS = [
|
|
2266
|
-
"id",
|
|
2267
|
-
"kind",
|
|
2268
|
-
"from_id",
|
|
2269
|
-
"to_id",
|
|
2270
|
-
"props",
|
|
2271
|
-
"valid_from",
|
|
2272
|
-
"valid_to",
|
|
2273
|
-
"created_at",
|
|
2274
|
-
"updated_at",
|
|
2275
|
-
"deleted_at"
|
|
2276
|
-
];
|
|
2277
|
-
var EMPTY_REQUIRED_COLUMNS = /* @__PURE__ */ new Set();
|
|
2278
|
-
function quoteIdentifier(identifier) {
|
|
2279
|
-
return sql.raw(`"${identifier.replaceAll('"', '""')}"`);
|
|
2280
|
-
}
|
|
2281
|
-
function shouldProjectColumn(requiredColumns, column, alwaysRequiredColumns) {
|
|
2282
|
-
if (alwaysRequiredColumns?.has(column)) return true;
|
|
2283
|
-
if (requiredColumns === void 0) return true;
|
|
2284
|
-
return requiredColumns.has(column);
|
|
2285
|
-
}
|
|
2286
|
-
function addRequiredColumn(requiredColumnsByAlias, alias, column) {
|
|
2287
|
-
const existing = requiredColumnsByAlias.get(alias);
|
|
2288
|
-
if (existing) {
|
|
2289
|
-
existing.add(column);
|
|
2290
|
-
return;
|
|
2291
|
-
}
|
|
2292
|
-
requiredColumnsByAlias.set(alias, /* @__PURE__ */ new Set([column]));
|
|
2894
|
+
});
|
|
2895
|
+
state = logicalPlanPass.state;
|
|
2896
|
+
return state;
|
|
2293
2897
|
}
|
|
2294
|
-
function
|
|
2295
|
-
const
|
|
2296
|
-
|
|
2297
|
-
|
|
2898
|
+
function compileVariableLengthQuery(ast, graphId, ctx) {
|
|
2899
|
+
const strategy = ctx.dialect.capabilities.recursiveQueryStrategy;
|
|
2900
|
+
const handler = RECURSIVE_QUERY_STRATEGY_HANDLERS[strategy];
|
|
2901
|
+
return handler(ast, graphId, ctx);
|
|
2298
2902
|
}
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2903
|
+
var RECURSIVE_QUERY_STRATEGY_HANDLERS = {
|
|
2904
|
+
recursive_cte: compileVariableLengthQueryWithRecursiveCteStrategy
|
|
2905
|
+
};
|
|
2906
|
+
function compileVariableLengthQueryWithRecursiveCteStrategy(ast, graphId, ctx) {
|
|
2907
|
+
const passState = runRecursiveQueryPassPipeline(ast, graphId, ctx);
|
|
2908
|
+
const { dialect } = ctx;
|
|
2909
|
+
const {
|
|
2910
|
+
logicalPlan,
|
|
2911
|
+
requiredColumnsByAlias,
|
|
2912
|
+
temporalFilterPass,
|
|
2913
|
+
traversal: vlTraversal
|
|
2914
|
+
} = passState;
|
|
2915
|
+
if (temporalFilterPass === void 0) {
|
|
2916
|
+
throw new CompilerInvariantError(
|
|
2917
|
+
"Temporal filter pass did not initialize temporal state"
|
|
2918
|
+
);
|
|
2304
2919
|
}
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
if (field2.isSystemField) {
|
|
2309
|
-
addRequiredColumn(
|
|
2310
|
-
requiredColumnsByAlias,
|
|
2311
|
-
field2.alias,
|
|
2312
|
-
mapSelectiveSystemFieldToColumn(field2.field)
|
|
2920
|
+
if (logicalPlan === void 0) {
|
|
2921
|
+
throw new CompilerInvariantError(
|
|
2922
|
+
"Logical plan pass did not initialize plan state"
|
|
2313
2923
|
);
|
|
2314
|
-
return;
|
|
2315
2924
|
}
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
}
|
|
2321
|
-
function isAggregateExpr(source) {
|
|
2322
|
-
return "__type" in source && source.__type === "aggregate";
|
|
2323
|
-
}
|
|
2324
|
-
|
|
2325
|
-
// src/query/compiler/emitter/standard-builders.ts
|
|
2326
|
-
function compileColumnReference(tableAlias, column) {
|
|
2327
|
-
if (tableAlias === void 0) {
|
|
2328
|
-
return sql.raw(column);
|
|
2925
|
+
if (vlTraversal === void 0) {
|
|
2926
|
+
throw new CompilerInvariantError(
|
|
2927
|
+
"Recursive traversal pass did not select traversal"
|
|
2928
|
+
);
|
|
2329
2929
|
}
|
|
2330
|
-
|
|
2331
|
-
}
|
|
2332
|
-
function compileNodeSelectColumns(tableAlias, alias, requiredColumns) {
|
|
2333
|
-
return NODE_COLUMNS.filter(
|
|
2334
|
-
(column) => column === "id" || column === "kind" || shouldProjectColumn(requiredColumns, column)
|
|
2335
|
-
).map(
|
|
2336
|
-
(column) => sql`${compileColumnReference(tableAlias, column)} AS ${sql.raw(`${alias}_${column}`)}`
|
|
2337
|
-
);
|
|
2338
|
-
}
|
|
2339
|
-
function compileEdgeSelectColumns(tableAlias, alias, requiredColumns) {
|
|
2340
|
-
return EDGE_COLUMNS.filter(
|
|
2341
|
-
(column) => shouldProjectColumn(requiredColumns, column)
|
|
2342
|
-
).map(
|
|
2343
|
-
(column) => sql`${compileColumnReference(tableAlias, column)} AS ${sql.raw(`${alias}_${column}`)}`
|
|
2344
|
-
);
|
|
2345
|
-
}
|
|
2346
|
-
function buildStandardStartCte(input) {
|
|
2347
|
-
const { ast, ctx, graphId, predicateIndex, requiredColumnsByAlias } = input;
|
|
2348
|
-
const alias = ast.start.alias;
|
|
2349
|
-
const kinds = ast.start.kinds;
|
|
2350
|
-
const kindFilter = compileKindFilter(sql.raw("kind"), kinds);
|
|
2351
|
-
const temporalFilter = input.temporalFilterPass.forAlias();
|
|
2352
|
-
const cteContext = { ...ctx, cteColumnPrefix: "" };
|
|
2353
|
-
const predicateClauses = compilePredicateClauses(
|
|
2354
|
-
getPredicatesForAlias(predicateIndex, alias, "node"),
|
|
2355
|
-
cteContext
|
|
2356
|
-
);
|
|
2357
|
-
const whereClauses = [
|
|
2358
|
-
sql`graph_id = ${graphId}`,
|
|
2359
|
-
kindFilter,
|
|
2360
|
-
temporalFilter,
|
|
2361
|
-
...predicateClauses
|
|
2362
|
-
];
|
|
2363
|
-
const effectiveRequiredColumns = requiredColumnsByAlias ? requiredColumnsByAlias.get(alias) ?? EMPTY_REQUIRED_COLUMNS : void 0;
|
|
2364
|
-
return sql`
|
|
2365
|
-
cte_${sql.raw(alias)} AS (
|
|
2366
|
-
SELECT ${sql.join(
|
|
2367
|
-
compileNodeSelectColumns(void 0, alias, effectiveRequiredColumns),
|
|
2368
|
-
sql`, `
|
|
2369
|
-
)}
|
|
2370
|
-
FROM ${ctx.schema.nodesTable}
|
|
2371
|
-
WHERE ${sql.join(whereClauses, sql` AND `)}
|
|
2372
|
-
)
|
|
2373
|
-
`;
|
|
2374
|
-
}
|
|
2375
|
-
function buildStandardTraversalCte(input) {
|
|
2376
|
-
const {
|
|
2930
|
+
const recursiveCte = compileRecursiveCte(
|
|
2377
2931
|
ast,
|
|
2378
|
-
|
|
2379
|
-
ctx,
|
|
2932
|
+
vlTraversal,
|
|
2380
2933
|
graphId,
|
|
2381
|
-
|
|
2382
|
-
predicateIndex,
|
|
2934
|
+
ctx,
|
|
2383
2935
|
requiredColumnsByAlias,
|
|
2384
|
-
temporalFilterPass
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
const
|
|
2389
|
-
const
|
|
2390
|
-
const
|
|
2936
|
+
temporalFilterPass
|
|
2937
|
+
);
|
|
2938
|
+
const projection = compileRecursiveProjection(ast, vlTraversal, dialect);
|
|
2939
|
+
const minDepth = vlTraversal.variableLength.minDepth;
|
|
2940
|
+
const depthFilter = minDepth > 0 ? sql`WHERE depth >= ${minDepth}` : sql.raw("");
|
|
2941
|
+
const orderBy = compileRecursiveOrderBy(ast, dialect);
|
|
2942
|
+
const limitOffset = compileLimitOffset(ast);
|
|
2943
|
+
return emitRecursiveQuerySql({
|
|
2944
|
+
depthFilter,
|
|
2945
|
+
...limitOffset === void 0 ? {} : { limitOffset },
|
|
2946
|
+
logicalPlan,
|
|
2947
|
+
...orderBy === void 0 ? {} : { orderBy },
|
|
2948
|
+
projection,
|
|
2949
|
+
recursiveCte
|
|
2950
|
+
});
|
|
2951
|
+
}
|
|
2952
|
+
function hasVariableLengthTraversal(ast) {
|
|
2953
|
+
return ast.traversals.some((t) => t.variableLength !== void 0);
|
|
2954
|
+
}
|
|
2955
|
+
function compileRecursiveCte(ast, traversal, graphId, ctx, requiredColumnsByAlias, temporalFilterPass) {
|
|
2956
|
+
const { dialect } = ctx;
|
|
2957
|
+
const startAlias = ast.start.alias;
|
|
2958
|
+
const startKinds = ast.start.kinds;
|
|
2959
|
+
const nodeAlias = traversal.nodeAlias;
|
|
2391
2960
|
const directEdgeKinds = [...new Set(traversal.edgeKinds)];
|
|
2392
2961
|
const inverseEdgeKinds = traversal.inverseEdgeKinds === void 0 ? [] : [...new Set(traversal.inverseEdgeKinds)];
|
|
2962
|
+
const forceWorktableOuterJoinOrder = dialect.capabilities.forceRecursiveWorktableOuterJoinOrder;
|
|
2393
2963
|
const nodeKinds = traversal.nodeKinds;
|
|
2394
|
-
const
|
|
2964
|
+
const previousNodeKinds = [.../* @__PURE__ */ new Set([...startKinds, ...nodeKinds])];
|
|
2965
|
+
const direction = traversal.direction;
|
|
2966
|
+
const vl = traversal.variableLength;
|
|
2967
|
+
const shouldEnforceCycleCheck = vl.cyclePolicy !== "allow";
|
|
2968
|
+
const shouldTrackPath = shouldEnforceCycleCheck || vl.pathAlias !== void 0;
|
|
2969
|
+
const recursiveJoinRequiredColumns = /* @__PURE__ */ new Set(["id"]);
|
|
2970
|
+
if (previousNodeKinds.length > 1) {
|
|
2971
|
+
recursiveJoinRequiredColumns.add("kind");
|
|
2972
|
+
}
|
|
2973
|
+
const requiredStartColumns = requiredColumnsByAlias ? requiredColumnsByAlias.get(startAlias) ?? EMPTY_REQUIRED_COLUMNS : void 0;
|
|
2974
|
+
const requiredNodeColumns = requiredColumnsByAlias ? requiredColumnsByAlias.get(nodeAlias) ?? EMPTY_REQUIRED_COLUMNS : void 0;
|
|
2975
|
+
const startColumnsFromBase = compileNodeSelectColumnsFromTable(
|
|
2976
|
+
"n0",
|
|
2977
|
+
startAlias,
|
|
2978
|
+
requiredStartColumns,
|
|
2979
|
+
NO_ALWAYS_REQUIRED_COLUMNS
|
|
2980
|
+
);
|
|
2981
|
+
const startColumnsFromRecursive = compileNodeSelectColumnsFromRecursiveRow(
|
|
2982
|
+
startAlias,
|
|
2983
|
+
requiredStartColumns,
|
|
2984
|
+
NO_ALWAYS_REQUIRED_COLUMNS
|
|
2985
|
+
);
|
|
2986
|
+
const nodeColumnsFromBase = compileNodeSelectColumnsFromTable(
|
|
2987
|
+
"n0",
|
|
2988
|
+
nodeAlias,
|
|
2989
|
+
requiredNodeColumns,
|
|
2990
|
+
recursiveJoinRequiredColumns
|
|
2991
|
+
);
|
|
2992
|
+
const nodeColumnsFromRecursive = compileNodeSelectColumnsFromTable(
|
|
2993
|
+
"n",
|
|
2994
|
+
nodeAlias,
|
|
2995
|
+
requiredNodeColumns,
|
|
2996
|
+
recursiveJoinRequiredColumns
|
|
2997
|
+
);
|
|
2998
|
+
const startKindFilter = compileKindFilter2(startKinds, "n0.kind");
|
|
2999
|
+
const nodeKindFilter = compileKindFilter2(nodeKinds, "n.kind");
|
|
3000
|
+
const startTemporalFilter = temporalFilterPass.forAlias("n0");
|
|
2395
3001
|
const edgeTemporalFilter = temporalFilterPass.forAlias("e");
|
|
2396
3002
|
const nodeTemporalFilter = temporalFilterPass.forAlias("n");
|
|
2397
|
-
const
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
);
|
|
2405
|
-
const
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
getPredicatesForAlias(predicateIndex, traversal.edgeAlias, "edge"),
|
|
2411
|
-
edgeCteContext
|
|
3003
|
+
const startContext = { ...ctx, cteColumnPrefix: "" };
|
|
3004
|
+
const startPredicates = compileNodePredicates(ast, startAlias, startContext);
|
|
3005
|
+
const edgeContext = { ...ctx, cteColumnPrefix: "e" };
|
|
3006
|
+
const edgePredicates = compileEdgePredicates(
|
|
3007
|
+
ast,
|
|
3008
|
+
traversal.edgeAlias,
|
|
3009
|
+
edgeContext
|
|
3010
|
+
);
|
|
3011
|
+
const targetContext = { ...ctx, cteColumnPrefix: "n" };
|
|
3012
|
+
const targetNodePredicates = compileNodePredicates(
|
|
3013
|
+
ast,
|
|
3014
|
+
nodeAlias,
|
|
3015
|
+
targetContext
|
|
2412
3016
|
);
|
|
3017
|
+
if (vl.maxDepth > MAX_EXPLICIT_RECURSIVE_DEPTH) {
|
|
3018
|
+
throw new UnsupportedPredicateError(
|
|
3019
|
+
`Recursive traversal maxHops(${vl.maxDepth}) exceeds maximum explicit depth of ${MAX_EXPLICIT_RECURSIVE_DEPTH}`
|
|
3020
|
+
);
|
|
3021
|
+
}
|
|
3022
|
+
const effectiveMaxDepth = vl.maxDepth > 0 ? vl.maxDepth : MAX_RECURSIVE_DEPTH;
|
|
3023
|
+
const maxDepthCondition = sql`r.depth < ${effectiveMaxDepth}`;
|
|
3024
|
+
const cycleCheck = shouldEnforceCycleCheck ? dialect.cycleCheck(sql.raw("n.id"), sql.raw("r.path")) : void 0;
|
|
3025
|
+
const initialPath = shouldTrackPath ? dialect.initializePath(sql.raw("n0.id")) : void 0;
|
|
3026
|
+
const pathExtension = shouldTrackPath ? dialect.extendPath(sql.raw("r.path"), sql.raw("n.id")) : void 0;
|
|
2413
3027
|
const baseWhereClauses = [
|
|
3028
|
+
sql`n0.graph_id = ${graphId}`,
|
|
3029
|
+
startKindFilter,
|
|
3030
|
+
startTemporalFilter,
|
|
3031
|
+
...startPredicates
|
|
3032
|
+
];
|
|
3033
|
+
const recursiveBaseWhereClauses = [
|
|
2414
3034
|
sql`e.graph_id = ${graphId}`,
|
|
2415
3035
|
nodeKindFilter,
|
|
2416
3036
|
edgeTemporalFilter,
|
|
2417
3037
|
nodeTemporalFilter,
|
|
2418
|
-
|
|
2419
|
-
...edgePredicateClauses
|
|
2420
|
-
];
|
|
2421
|
-
const previousAlias = traversal.joinFromAlias;
|
|
2422
|
-
const edgeAlias = traversal.edgeAlias;
|
|
2423
|
-
const nodeAlias = traversal.nodeAlias;
|
|
2424
|
-
const requiredNodeColumns = requiredColumnsByAlias ? requiredColumnsByAlias.get(nodeAlias) ?? EMPTY_REQUIRED_COLUMNS : void 0;
|
|
2425
|
-
const requiredEdgeColumns = requiredColumnsByAlias ? requiredColumnsByAlias.get(edgeAlias) ?? EMPTY_REQUIRED_COLUMNS : void 0;
|
|
2426
|
-
const previousRowColumns = carryForwardPreviousColumns ? [sql`cte_${sql.raw(previousAlias)}.*`] : [
|
|
2427
|
-
sql`cte_${sql.raw(previousAlias)}.${sql.raw(previousAlias)}_id AS ${sql.raw(previousAlias)}_id`,
|
|
2428
|
-
sql`cte_${sql.raw(previousAlias)}.${sql.raw(previousAlias)}_kind AS ${sql.raw(previousAlias)}_kind`
|
|
2429
|
-
];
|
|
2430
|
-
const selectColumns = [
|
|
2431
|
-
...previousRowColumns,
|
|
2432
|
-
...compileEdgeSelectColumns("e", edgeAlias, requiredEdgeColumns),
|
|
2433
|
-
...compileNodeSelectColumns("n", nodeAlias, requiredNodeColumns)
|
|
3038
|
+
maxDepthCondition
|
|
2434
3039
|
];
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
),
|
|
2444
|
-
|
|
3040
|
+
if (cycleCheck !== void 0) {
|
|
3041
|
+
recursiveBaseWhereClauses.push(cycleCheck);
|
|
3042
|
+
}
|
|
3043
|
+
recursiveBaseWhereClauses.push(...edgePredicates, ...targetNodePredicates);
|
|
3044
|
+
function compileRecursiveBranch2(branch) {
|
|
3045
|
+
const recursiveFilterClauses = [
|
|
3046
|
+
...recursiveBaseWhereClauses,
|
|
3047
|
+
compileKindFilter2(branch.edgeKinds, "e.kind"),
|
|
3048
|
+
compileKindFilter2(previousNodeKinds, `e.${branch.joinKindField}`),
|
|
3049
|
+
compileKindFilter2(nodeKinds, `e.${branch.targetKindField}`)
|
|
2445
3050
|
];
|
|
2446
3051
|
if (branch.duplicateGuard !== void 0) {
|
|
2447
|
-
|
|
2448
|
-
}
|
|
2449
|
-
return sql`
|
|
2450
|
-
SELECT ${sql.join(selectColumns, sql`, `)}
|
|
2451
|
-
FROM cte_${sql.raw(previousAlias)}
|
|
2452
|
-
JOIN ${ctx.schema.edgesTable} e ON cte_${sql.raw(previousAlias)}.${sql.raw(previousAlias)}_id = e.${sql.raw(branch.joinField)}
|
|
2453
|
-
AND cte_${sql.raw(previousAlias)}.${sql.raw(previousAlias)}_kind = e.${sql.raw(branch.joinKindField)}
|
|
2454
|
-
JOIN ${ctx.schema.nodesTable} n ON n.graph_id = e.graph_id
|
|
2455
|
-
AND n.id = e.${sql.raw(branch.targetField)}
|
|
2456
|
-
AND n.kind = e.${sql.raw(branch.targetKindField)}
|
|
2457
|
-
WHERE ${sql.join(whereClauses, sql` AND `)}
|
|
2458
|
-
`;
|
|
2459
|
-
}
|
|
2460
|
-
const directJoinField = traversal.direction === "out" ? "from_id" : "to_id";
|
|
2461
|
-
const directTargetField = traversal.direction === "out" ? "to_id" : "from_id";
|
|
2462
|
-
const directJoinKindField = traversal.direction === "out" ? "from_kind" : "to_kind";
|
|
2463
|
-
const directTargetKindField = traversal.direction === "out" ? "to_kind" : "from_kind";
|
|
2464
|
-
const directBranch = compileTraversalBranch({
|
|
2465
|
-
edgeKinds: directEdgeKinds,
|
|
2466
|
-
joinField: directJoinField,
|
|
2467
|
-
joinKindField: directJoinKindField,
|
|
2468
|
-
targetField: directTargetField,
|
|
2469
|
-
targetKindField: directTargetKindField
|
|
2470
|
-
});
|
|
2471
|
-
if (inverseEdgeKinds.length === 0) {
|
|
2472
|
-
if (traversalLimitValue !== void 0) {
|
|
2473
|
-
return sql`
|
|
2474
|
-
cte_${sql.raw(nodeAlias)} AS ${cteMaterialization}(
|
|
2475
|
-
SELECT * FROM (
|
|
2476
|
-
${directBranch}
|
|
2477
|
-
) AS traversal_rows
|
|
2478
|
-
LIMIT ${traversalLimitValue}
|
|
2479
|
-
)
|
|
2480
|
-
`;
|
|
3052
|
+
recursiveFilterClauses.push(branch.duplicateGuard);
|
|
2481
3053
|
}
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
const inverseTargetField = traversal.direction === "out" ? "from_id" : "to_id";
|
|
2490
|
-
const inverseJoinKindField = traversal.direction === "out" ? "to_kind" : "from_kind";
|
|
2491
|
-
const inverseTargetKindField = traversal.direction === "out" ? "from_kind" : "to_kind";
|
|
2492
|
-
const overlappingKinds = inverseEdgeKinds.filter(
|
|
2493
|
-
(kind) => directEdgeKinds.includes(kind)
|
|
2494
|
-
);
|
|
2495
|
-
const duplicateGuard = overlappingKinds.length > 0 ? sql`NOT (e.from_id = e.to_id AND ${compileKindFilter(
|
|
2496
|
-
sql.raw("e.kind"),
|
|
2497
|
-
overlappingKinds
|
|
2498
|
-
)})` : void 0;
|
|
2499
|
-
const inverseBranch = compileTraversalBranch({
|
|
2500
|
-
duplicateGuard,
|
|
2501
|
-
edgeKinds: inverseEdgeKinds,
|
|
2502
|
-
joinField: inverseJoinField,
|
|
2503
|
-
joinKindField: inverseJoinKindField,
|
|
2504
|
-
targetField: inverseTargetField,
|
|
2505
|
-
targetKindField: inverseTargetKindField
|
|
2506
|
-
});
|
|
2507
|
-
if (traversalLimitValue !== void 0) {
|
|
2508
|
-
return sql`
|
|
2509
|
-
cte_${sql.raw(nodeAlias)} AS ${cteMaterialization}(
|
|
2510
|
-
SELECT * FROM (
|
|
2511
|
-
${directBranch}
|
|
2512
|
-
UNION ALL
|
|
2513
|
-
${inverseBranch}
|
|
2514
|
-
) AS traversal_rows
|
|
2515
|
-
LIMIT ${traversalLimitValue}
|
|
2516
|
-
)
|
|
2517
|
-
`;
|
|
2518
|
-
}
|
|
2519
|
-
return sql`
|
|
2520
|
-
cte_${sql.raw(nodeAlias)} AS ${cteMaterialization}(
|
|
2521
|
-
${directBranch}
|
|
2522
|
-
UNION ALL
|
|
2523
|
-
${inverseBranch}
|
|
2524
|
-
)
|
|
2525
|
-
`;
|
|
2526
|
-
}
|
|
2527
|
-
function compileAggregateExprFromSource(expr, dialect) {
|
|
2528
|
-
const { field: field2 } = expr;
|
|
2529
|
-
const fn = expr.function;
|
|
2530
|
-
switch (fn) {
|
|
2531
|
-
case "count":
|
|
2532
|
-
case "countDistinct":
|
|
2533
|
-
case "sum":
|
|
2534
|
-
case "avg":
|
|
2535
|
-
case "min":
|
|
2536
|
-
case "max": {
|
|
2537
|
-
const cteAlias = `cte_${field2.alias}`;
|
|
2538
|
-
const column = compileFieldValue(
|
|
2539
|
-
field2,
|
|
2540
|
-
dialect,
|
|
2541
|
-
field2.valueType,
|
|
2542
|
-
cteAlias
|
|
2543
|
-
);
|
|
2544
|
-
if (fn === "countDistinct") {
|
|
2545
|
-
return sql`COUNT(DISTINCT ${column})`;
|
|
2546
|
-
}
|
|
2547
|
-
return sql`${sql.raw(fn.toUpperCase())}(${column})`;
|
|
3054
|
+
const recursiveSelectColumns = [
|
|
3055
|
+
...startColumnsFromRecursive,
|
|
3056
|
+
...nodeColumnsFromRecursive,
|
|
3057
|
+
sql`r.depth + 1 AS depth`
|
|
3058
|
+
];
|
|
3059
|
+
if (pathExtension !== void 0) {
|
|
3060
|
+
recursiveSelectColumns.push(sql`${pathExtension} AS path`);
|
|
2548
3061
|
}
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
3062
|
+
const recursiveJoinClauses = [
|
|
3063
|
+
sql`e.${sql.raw(branch.joinField)} = r.${sql.raw(nodeAlias)}_id`
|
|
3064
|
+
];
|
|
3065
|
+
if (previousNodeKinds.length > 1) {
|
|
3066
|
+
recursiveJoinClauses.push(
|
|
3067
|
+
sql`e.${sql.raw(branch.joinKindField)} = r.${sql.raw(nodeAlias)}_kind`
|
|
2552
3068
|
);
|
|
2553
3069
|
}
|
|
3070
|
+
if (forceWorktableOuterJoinOrder) {
|
|
3071
|
+
const recursiveWhereClauses = [
|
|
3072
|
+
...recursiveJoinClauses,
|
|
3073
|
+
...recursiveFilterClauses
|
|
3074
|
+
];
|
|
3075
|
+
return sql`
|
|
3076
|
+
SELECT ${sql.join(recursiveSelectColumns, sql`, `)}
|
|
3077
|
+
FROM recursive_cte r
|
|
3078
|
+
CROSS JOIN ${ctx.schema.edgesTable} e
|
|
3079
|
+
JOIN ${ctx.schema.nodesTable} n ON n.graph_id = e.graph_id
|
|
3080
|
+
AND n.id = e.${sql.raw(branch.targetField)}
|
|
3081
|
+
AND n.kind = e.${sql.raw(branch.targetKindField)}
|
|
3082
|
+
WHERE ${sql.join(recursiveWhereClauses, sql` AND `)}
|
|
3083
|
+
`;
|
|
3084
|
+
}
|
|
3085
|
+
return sql`
|
|
3086
|
+
SELECT ${sql.join(recursiveSelectColumns, sql`, `)}
|
|
3087
|
+
FROM recursive_cte r
|
|
3088
|
+
JOIN ${ctx.schema.edgesTable} e ON ${sql.join(recursiveJoinClauses, sql` AND `)}
|
|
3089
|
+
JOIN ${ctx.schema.nodesTable} n ON n.graph_id = e.graph_id
|
|
3090
|
+
AND n.id = e.${sql.raw(branch.targetField)}
|
|
3091
|
+
AND n.kind = e.${sql.raw(branch.targetKindField)}
|
|
3092
|
+
WHERE ${sql.join(recursiveFilterClauses, sql` AND `)}
|
|
3093
|
+
`;
|
|
2554
3094
|
}
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
)
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
dialect,
|
|
2574
|
-
ast,
|
|
2575
|
-
collapsedTraversalCteAlias
|
|
2576
|
-
);
|
|
2577
|
-
}
|
|
2578
|
-
const fields = ast.projection.fields;
|
|
2579
|
-
if (fields.length === 0) {
|
|
2580
|
-
return sql.raw("*");
|
|
2581
|
-
}
|
|
2582
|
-
const projectedFields = fields.map((field2) => {
|
|
2583
|
-
const source = compileProjectedSource(field2, dialect);
|
|
2584
|
-
return sql`${source} AS ${quoteIdentifier(field2.outputName)}`;
|
|
2585
|
-
});
|
|
2586
|
-
return sql.join(projectedFields, sql`, `);
|
|
2587
|
-
}
|
|
2588
|
-
function compileSelectiveProjection(fields, dialect, ast, collapsedTraversalCteAlias) {
|
|
2589
|
-
const aliasToCte = /* @__PURE__ */ new Map([
|
|
2590
|
-
[ast.start.alias, `cte_${ast.start.alias}`]
|
|
2591
|
-
]);
|
|
2592
|
-
for (const traversal of ast.traversals) {
|
|
2593
|
-
aliasToCte.set(traversal.nodeAlias, `cte_${traversal.nodeAlias}`);
|
|
2594
|
-
aliasToCte.set(traversal.edgeAlias, `cte_${traversal.nodeAlias}`);
|
|
2595
|
-
}
|
|
2596
|
-
const columns = fields.map((field2) => {
|
|
2597
|
-
const cteAlias = collapsedTraversalCteAlias ?? aliasToCte.get(field2.alias) ?? `cte_${field2.alias}`;
|
|
2598
|
-
if (field2.isSystemField) {
|
|
2599
|
-
const dbColumn = mapSelectiveSystemFieldToColumn(field2.field);
|
|
2600
|
-
return sql`${sql.raw(cteAlias)}.${sql.raw(`${field2.alias}_${dbColumn}`)} AS ${quoteIdentifier(field2.outputName)}`;
|
|
2601
|
-
}
|
|
2602
|
-
const propsColumn = `${field2.alias}_props`;
|
|
2603
|
-
const column = sql`${sql.raw(cteAlias)}.${sql.raw(propsColumn)}`;
|
|
2604
|
-
const pointer = jsonPointer([field2.field]);
|
|
2605
|
-
const extracted = compileTypedJsonExtract({
|
|
2606
|
-
column,
|
|
2607
|
-
dialect,
|
|
2608
|
-
pointer,
|
|
2609
|
-
valueType: field2.valueType
|
|
2610
|
-
});
|
|
2611
|
-
return sql`${extracted} AS ${quoteIdentifier(field2.outputName)}`;
|
|
2612
|
-
});
|
|
2613
|
-
return sql.join(columns, sql`, `);
|
|
2614
|
-
}
|
|
2615
|
-
function buildStandardFromClause(input) {
|
|
2616
|
-
const { ast, collapsedTraversalCteAlias, vectorPredicate } = input;
|
|
2617
|
-
if (collapsedTraversalCteAlias !== void 0) {
|
|
2618
|
-
return sql`FROM ${sql.raw(collapsedTraversalCteAlias)}`;
|
|
2619
|
-
}
|
|
2620
|
-
const startAlias = ast.start.alias;
|
|
2621
|
-
const fromClause = sql`FROM cte_${sql.raw(startAlias)}`;
|
|
2622
|
-
const joins = [];
|
|
2623
|
-
for (const traversal of ast.traversals) {
|
|
2624
|
-
const cteAlias = `cte_${traversal.nodeAlias}`;
|
|
2625
|
-
const previousAlias = traversal.joinFromAlias;
|
|
2626
|
-
const joinType = traversal.optional ? "LEFT JOIN" : "INNER JOIN";
|
|
2627
|
-
joins.push(
|
|
2628
|
-
sql`${sql.raw(joinType)} ${sql.raw(cteAlias)} ON ${sql.raw(cteAlias)}.${sql.raw(previousAlias)}_id = cte_${sql.raw(previousAlias)}.${sql.raw(previousAlias)}_id AND ${sql.raw(cteAlias)}.${sql.raw(previousAlias)}_kind = cte_${sql.raw(previousAlias)}.${sql.raw(previousAlias)}_kind`
|
|
3095
|
+
const directJoinField = direction === "out" ? "from_id" : "to_id";
|
|
3096
|
+
const directTargetField = direction === "out" ? "to_id" : "from_id";
|
|
3097
|
+
const directJoinKindField = direction === "out" ? "from_kind" : "to_kind";
|
|
3098
|
+
const directTargetKindField = direction === "out" ? "to_kind" : "from_kind";
|
|
3099
|
+
const directBranch = compileRecursiveBranch2({
|
|
3100
|
+
joinField: directJoinField,
|
|
3101
|
+
targetField: directTargetField,
|
|
3102
|
+
joinKindField: directJoinKindField,
|
|
3103
|
+
targetKindField: directTargetKindField,
|
|
3104
|
+
edgeKinds: directEdgeKinds
|
|
3105
|
+
});
|
|
3106
|
+
function compileInverseRecursiveBranch() {
|
|
3107
|
+
const inverseJoinField = direction === "out" ? "to_id" : "from_id";
|
|
3108
|
+
const inverseTargetField = direction === "out" ? "from_id" : "to_id";
|
|
3109
|
+
const inverseJoinKindField = direction === "out" ? "to_kind" : "from_kind";
|
|
3110
|
+
const inverseTargetKindField = direction === "out" ? "from_kind" : "to_kind";
|
|
3111
|
+
const overlappingKinds = inverseEdgeKinds.filter(
|
|
3112
|
+
(kind) => directEdgeKinds.includes(kind)
|
|
2629
3113
|
);
|
|
3114
|
+
const duplicateGuard = overlappingKinds.length > 0 ? sql`NOT (e.from_id = e.to_id AND ${compileKindFilter2(overlappingKinds, "e.kind")})` : void 0;
|
|
3115
|
+
const inverseBranch = compileRecursiveBranch2({
|
|
3116
|
+
joinField: inverseJoinField,
|
|
3117
|
+
targetField: inverseTargetField,
|
|
3118
|
+
joinKindField: inverseJoinKindField,
|
|
3119
|
+
targetKindField: inverseTargetKindField,
|
|
3120
|
+
edgeKinds: inverseEdgeKinds,
|
|
3121
|
+
duplicateGuard
|
|
3122
|
+
});
|
|
3123
|
+
return sql`
|
|
3124
|
+
${directBranch}
|
|
3125
|
+
UNION ALL
|
|
3126
|
+
${inverseBranch}
|
|
3127
|
+
`;
|
|
2630
3128
|
}
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
3129
|
+
const recursiveBranchSql = inverseEdgeKinds.length === 0 ? directBranch : compileInverseRecursiveBranch();
|
|
3130
|
+
const baseSelectColumns = [
|
|
3131
|
+
...startColumnsFromBase,
|
|
3132
|
+
...nodeColumnsFromBase,
|
|
3133
|
+
sql`0 AS depth`
|
|
3134
|
+
];
|
|
3135
|
+
if (initialPath !== void 0) {
|
|
3136
|
+
baseSelectColumns.push(sql`${initialPath} AS path`);
|
|
2636
3137
|
}
|
|
2637
|
-
return
|
|
3138
|
+
return sql`
|
|
3139
|
+
recursive_cte AS (
|
|
3140
|
+
-- Base case: starting nodes
|
|
3141
|
+
SELECT ${sql.join(baseSelectColumns, sql`, `)}
|
|
3142
|
+
FROM ${ctx.schema.nodesTable} n0
|
|
3143
|
+
WHERE ${sql.join(baseWhereClauses, sql` AND `)}
|
|
3144
|
+
|
|
3145
|
+
UNION ALL
|
|
3146
|
+
|
|
3147
|
+
-- Recursive case: follow edges
|
|
3148
|
+
${recursiveBranchSql}
|
|
3149
|
+
)
|
|
3150
|
+
`;
|
|
2638
3151
|
}
|
|
2639
|
-
function
|
|
2640
|
-
|
|
2641
|
-
if (!ast.orderBy || ast.orderBy.length === 0) {
|
|
2642
|
-
return void 0;
|
|
2643
|
-
}
|
|
2644
|
-
const parts = [];
|
|
2645
|
-
for (const orderSpec of ast.orderBy) {
|
|
2646
|
-
const valueType = orderSpec.field.valueType;
|
|
2647
|
-
if (valueType === "array" || valueType === "object") {
|
|
2648
|
-
throw new UnsupportedPredicateError(
|
|
2649
|
-
"Ordering by JSON arrays or objects is not supported"
|
|
2650
|
-
);
|
|
2651
|
-
}
|
|
2652
|
-
const cteAlias = collapsedTraversalCteAlias ?? `cte_${orderSpec.field.alias}`;
|
|
2653
|
-
const field2 = compileFieldValue(
|
|
2654
|
-
orderSpec.field,
|
|
2655
|
-
dialect,
|
|
2656
|
-
valueType,
|
|
2657
|
-
cteAlias
|
|
2658
|
-
);
|
|
2659
|
-
const direction = sql.raw(orderSpec.direction.toUpperCase());
|
|
2660
|
-
const nulls = orderSpec.nulls ?? (orderSpec.direction === "asc" ? "last" : "first");
|
|
2661
|
-
const nullsDirection = sql.raw(nulls === "first" ? "DESC" : "ASC");
|
|
2662
|
-
parts.push(
|
|
2663
|
-
sql`(${field2} IS NULL) ${nullsDirection}`,
|
|
2664
|
-
sql`${field2} ${direction}`
|
|
2665
|
-
);
|
|
2666
|
-
}
|
|
2667
|
-
return sql`ORDER BY ${sql.join(parts, sql`, `)}`;
|
|
3152
|
+
function compileKindFilter2(kinds, columnExpr) {
|
|
3153
|
+
return compileKindFilter(sql.raw(columnExpr), kinds);
|
|
2668
3154
|
}
|
|
2669
|
-
function
|
|
2670
|
-
|
|
2671
|
-
return `${field2.alias}:${field2.path.join(".")}:${pointer}`;
|
|
3155
|
+
function compileNodePredicates(ast, alias, ctx) {
|
|
3156
|
+
return ast.predicates.filter((p) => p.targetAlias === alias && p.targetType !== "edge").map((p) => compilePredicateExpression(p.expression, ctx));
|
|
2672
3157
|
}
|
|
2673
|
-
function
|
|
2674
|
-
|
|
2675
|
-
|
|
3158
|
+
function compileEdgePredicates(ast, edgeAlias, ctx) {
|
|
3159
|
+
return ast.predicates.filter((p) => p.targetAlias === edgeAlias && p.targetType === "edge").map((p) => compilePredicateExpression(p.expression, ctx));
|
|
3160
|
+
}
|
|
3161
|
+
function collectRequiredColumnsByAlias(ast, traversal) {
|
|
3162
|
+
const selectiveFields = ast.selectiveFields;
|
|
3163
|
+
if (selectiveFields === void 0 || selectiveFields.length === 0) {
|
|
2676
3164
|
return void 0;
|
|
2677
3165
|
}
|
|
2678
|
-
const
|
|
2679
|
-
const
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
allFields.push(projectedField.source);
|
|
2686
|
-
}
|
|
2687
|
-
}
|
|
3166
|
+
const requiredColumnsByAlias = /* @__PURE__ */ new Map();
|
|
3167
|
+
const previousNodeKinds = [
|
|
3168
|
+
.../* @__PURE__ */ new Set([...ast.start.kinds, ...traversal.nodeKinds])
|
|
3169
|
+
];
|
|
3170
|
+
addRequiredColumn(requiredColumnsByAlias, traversal.nodeAlias, "id");
|
|
3171
|
+
if (previousNodeKinds.length > 1) {
|
|
3172
|
+
addRequiredColumn(requiredColumnsByAlias, traversal.nodeAlias, "kind");
|
|
2688
3173
|
}
|
|
2689
|
-
for (const field2 of
|
|
2690
|
-
|
|
2691
|
-
if (!seenKeys.has(key)) {
|
|
2692
|
-
seenKeys.add(key);
|
|
2693
|
-
allFields.push(field2);
|
|
2694
|
-
}
|
|
3174
|
+
for (const field2 of selectiveFields) {
|
|
3175
|
+
markSelectiveFieldAsRequired(requiredColumnsByAlias, field2);
|
|
2695
3176
|
}
|
|
2696
|
-
if (
|
|
2697
|
-
|
|
3177
|
+
if (ast.orderBy) {
|
|
3178
|
+
for (const orderSpec of ast.orderBy) {
|
|
3179
|
+
markFieldRefAsRequired(requiredColumnsByAlias, orderSpec.field);
|
|
3180
|
+
}
|
|
2698
3181
|
}
|
|
2699
|
-
|
|
2700
|
-
|
|
3182
|
+
return requiredColumnsByAlias;
|
|
3183
|
+
}
|
|
3184
|
+
function compileNodeSelectColumnsFromTable(tableAlias, alias, requiredColumns, alwaysRequiredColumns) {
|
|
3185
|
+
return NODE_COLUMNS.filter(
|
|
3186
|
+
(column) => shouldProjectColumn(requiredColumns, column, alwaysRequiredColumns)
|
|
3187
|
+
).map(
|
|
3188
|
+
(column) => sql`${sql.raw(tableAlias)}.${sql.raw(column)} AS ${sql.raw(`${alias}_${column}`)}`
|
|
2701
3189
|
);
|
|
2702
|
-
return sql`GROUP BY ${sql.join(parts, sql`, `)}`;
|
|
2703
3190
|
}
|
|
2704
|
-
function
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
3191
|
+
function compileNodeSelectColumnsFromRecursiveRow(alias, requiredColumns, alwaysRequiredColumns) {
|
|
3192
|
+
return NODE_COLUMNS.filter(
|
|
3193
|
+
(column) => shouldProjectColumn(requiredColumns, column, alwaysRequiredColumns)
|
|
3194
|
+
).map((column) => {
|
|
3195
|
+
const projected = `${alias}_${column}`;
|
|
3196
|
+
return sql`r.${sql.raw(projected)} AS ${sql.raw(projected)}`;
|
|
3197
|
+
});
|
|
2711
3198
|
}
|
|
2712
|
-
function
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
queryEmbedding,
|
|
2720
|
-
metric
|
|
2721
|
-
);
|
|
2722
|
-
const conditions = [
|
|
2723
|
-
sql`graph_id = ${graphId}`,
|
|
2724
|
-
sql`field_path = ${fieldPath}`
|
|
2725
|
-
];
|
|
2726
|
-
if (minScore !== void 0) {
|
|
2727
|
-
conditions.push(
|
|
2728
|
-
compileVectorMinScoreCondition(distanceExpr, metric, minScore)
|
|
3199
|
+
function compileRecursiveProjection(ast, traversal, dialect) {
|
|
3200
|
+
if (ast.selectiveFields && ast.selectiveFields.length > 0) {
|
|
3201
|
+
return compileRecursiveSelectiveProjection(
|
|
3202
|
+
ast.selectiveFields,
|
|
3203
|
+
ast,
|
|
3204
|
+
traversal,
|
|
3205
|
+
dialect
|
|
2729
3206
|
);
|
|
2730
3207
|
}
|
|
2731
|
-
const
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
)
|
|
2742
|
-
|
|
2743
|
-
}
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
}
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
}
|
|
3208
|
+
const startAlias = ast.start.alias;
|
|
3209
|
+
const nodeAlias = traversal.nodeAlias;
|
|
3210
|
+
const vl = traversal.variableLength;
|
|
3211
|
+
const fields = [
|
|
3212
|
+
// Start alias fields with metadata
|
|
3213
|
+
sql`${sql.raw(startAlias)}_id`,
|
|
3214
|
+
sql`${sql.raw(startAlias)}_kind`,
|
|
3215
|
+
sql`${sql.raw(startAlias)}_props`,
|
|
3216
|
+
sql`${sql.raw(startAlias)}_version`,
|
|
3217
|
+
sql`${sql.raw(startAlias)}_valid_from`,
|
|
3218
|
+
sql`${sql.raw(startAlias)}_valid_to`,
|
|
3219
|
+
sql`${sql.raw(startAlias)}_created_at`,
|
|
3220
|
+
sql`${sql.raw(startAlias)}_updated_at`,
|
|
3221
|
+
sql`${sql.raw(startAlias)}_deleted_at`,
|
|
3222
|
+
// Node alias fields with metadata
|
|
3223
|
+
sql`${sql.raw(nodeAlias)}_id`,
|
|
3224
|
+
sql`${sql.raw(nodeAlias)}_kind`,
|
|
3225
|
+
sql`${sql.raw(nodeAlias)}_props`,
|
|
3226
|
+
sql`${sql.raw(nodeAlias)}_version`,
|
|
3227
|
+
sql`${sql.raw(nodeAlias)}_valid_from`,
|
|
3228
|
+
sql`${sql.raw(nodeAlias)}_valid_to`,
|
|
3229
|
+
sql`${sql.raw(nodeAlias)}_created_at`,
|
|
3230
|
+
sql`${sql.raw(nodeAlias)}_updated_at`,
|
|
3231
|
+
sql`${sql.raw(nodeAlias)}_deleted_at`
|
|
3232
|
+
];
|
|
3233
|
+
if (vl.depthAlias !== void 0) {
|
|
3234
|
+
fields.push(sql`depth AS ${quoteIdentifier(vl.depthAlias)}`);
|
|
3235
|
+
}
|
|
3236
|
+
if (vl.pathAlias !== void 0) {
|
|
3237
|
+
fields.push(sql`path AS ${quoteIdentifier(vl.pathAlias)}`);
|
|
2753
3238
|
}
|
|
3239
|
+
return sql.join(fields, sql`, `);
|
|
2754
3240
|
}
|
|
2755
|
-
function
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
return sql`${distanceExpr} <= ${minScore}`;
|
|
3241
|
+
function compileRecursiveSelectiveProjection(fields, ast, traversal, dialect) {
|
|
3242
|
+
const allowedAliases = /* @__PURE__ */ new Set([ast.start.alias, traversal.nodeAlias]);
|
|
3243
|
+
const columns = fields.map((field2) => {
|
|
3244
|
+
if (!allowedAliases.has(field2.alias)) {
|
|
3245
|
+
throw new UnsupportedPredicateError(
|
|
3246
|
+
`Selective projection for recursive traversals does not support alias "${field2.alias}"`
|
|
3247
|
+
);
|
|
2763
3248
|
}
|
|
2764
|
-
|
|
2765
|
-
const
|
|
2766
|
-
return sql`${
|
|
3249
|
+
if (field2.isSystemField) {
|
|
3250
|
+
const dbColumn = mapSelectiveSystemFieldToColumn(field2.field);
|
|
3251
|
+
return sql`${sql.raw(`${field2.alias}_${dbColumn}`)} AS ${quoteIdentifier(field2.outputName)}`;
|
|
2767
3252
|
}
|
|
3253
|
+
const column = sql.raw(`${field2.alias}_props`);
|
|
3254
|
+
const extracted = compileTypedJsonExtract({
|
|
3255
|
+
column,
|
|
3256
|
+
dialect,
|
|
3257
|
+
pointer: jsonPointer([field2.field]),
|
|
3258
|
+
valueType: field2.valueType
|
|
3259
|
+
});
|
|
3260
|
+
return sql`${extracted} AS ${quoteIdentifier(field2.outputName)}`;
|
|
3261
|
+
});
|
|
3262
|
+
const vl = traversal.variableLength;
|
|
3263
|
+
if (vl.depthAlias !== void 0) {
|
|
3264
|
+
columns.push(sql`depth AS ${quoteIdentifier(vl.depthAlias)}`);
|
|
2768
3265
|
}
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
const { ast, dialect } = input;
|
|
2772
|
-
const distanceOrder = sql`cte_embeddings.distance ASC`;
|
|
2773
|
-
const additionalOrders = [];
|
|
2774
|
-
if (ast.orderBy && ast.orderBy.length > 0) {
|
|
2775
|
-
for (const orderSpec of ast.orderBy) {
|
|
2776
|
-
const valueType = orderSpec.field.valueType;
|
|
2777
|
-
if (valueType === "array" || valueType === "object") {
|
|
2778
|
-
throw new UnsupportedPredicateError(
|
|
2779
|
-
"Ordering by JSON arrays or objects is not supported"
|
|
2780
|
-
);
|
|
2781
|
-
}
|
|
2782
|
-
const cteAlias = `cte_${orderSpec.field.alias}`;
|
|
2783
|
-
const field2 = compileFieldValue(
|
|
2784
|
-
orderSpec.field,
|
|
2785
|
-
dialect,
|
|
2786
|
-
valueType,
|
|
2787
|
-
cteAlias
|
|
2788
|
-
);
|
|
2789
|
-
const direction = sql.raw(orderSpec.direction.toUpperCase());
|
|
2790
|
-
const nulls = orderSpec.nulls ?? (orderSpec.direction === "asc" ? "last" : "first");
|
|
2791
|
-
const nullsDirection = sql.raw(nulls === "first" ? "DESC" : "ASC");
|
|
2792
|
-
additionalOrders.push(
|
|
2793
|
-
sql`(${field2} IS NULL) ${nullsDirection}`,
|
|
2794
|
-
sql`${field2} ${direction}`
|
|
2795
|
-
);
|
|
2796
|
-
}
|
|
3266
|
+
if (vl.pathAlias !== void 0) {
|
|
3267
|
+
columns.push(sql`path AS ${quoteIdentifier(vl.pathAlias)}`);
|
|
2797
3268
|
}
|
|
2798
|
-
|
|
2799
|
-
return sql`ORDER BY ${sql.join(allOrders, sql`, `)}`;
|
|
3269
|
+
return sql.join(columns, sql`, `);
|
|
2800
3270
|
}
|
|
2801
|
-
function
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
if (limit !== void 0) {
|
|
2805
|
-
parts.push(sql`LIMIT ${limit}`);
|
|
2806
|
-
}
|
|
2807
|
-
if (offset !== void 0) {
|
|
2808
|
-
parts.push(sql`OFFSET ${offset}`);
|
|
3271
|
+
function compileRecursiveOrderBy(ast, dialect) {
|
|
3272
|
+
if (!ast.orderBy || ast.orderBy.length === 0) {
|
|
3273
|
+
return void 0;
|
|
2809
3274
|
}
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
var NO_ALWAYS_REQUIRED_COLUMNS = /* @__PURE__ */ new Set();
|
|
2817
|
-
function runRecursiveQueryPassPipeline(ast, graphId, ctx) {
|
|
2818
|
-
let state = {
|
|
2819
|
-
ast,
|
|
2820
|
-
ctx,
|
|
2821
|
-
graphId,
|
|
2822
|
-
logicalPlan: void 0,
|
|
2823
|
-
requiredColumnsByAlias: void 0,
|
|
2824
|
-
temporalFilterPass: void 0,
|
|
2825
|
-
traversal: void 0
|
|
2826
|
-
};
|
|
2827
|
-
const recursiveTraversalPass = runCompilerPass(state, {
|
|
2828
|
-
name: "recursive_traversal",
|
|
2829
|
-
execute(currentState) {
|
|
2830
|
-
return runRecursiveTraversalSelectionPass(currentState.ast);
|
|
2831
|
-
},
|
|
2832
|
-
update(currentState, traversal) {
|
|
2833
|
-
return {
|
|
2834
|
-
...currentState,
|
|
2835
|
-
traversal
|
|
2836
|
-
};
|
|
2837
|
-
}
|
|
2838
|
-
});
|
|
2839
|
-
state = recursiveTraversalPass.state;
|
|
2840
|
-
const temporalPass = runCompilerPass(state, {
|
|
2841
|
-
name: "temporal_filters",
|
|
2842
|
-
execute(currentState) {
|
|
2843
|
-
return createTemporalFilterPass(
|
|
2844
|
-
currentState.ast,
|
|
2845
|
-
currentState.ctx.dialect.currentTimestamp()
|
|
3275
|
+
const parts = [];
|
|
3276
|
+
for (const orderSpec of ast.orderBy) {
|
|
3277
|
+
const valueType = orderSpec.field.valueType;
|
|
3278
|
+
if (valueType === "array" || valueType === "object") {
|
|
3279
|
+
throw new UnsupportedPredicateError(
|
|
3280
|
+
"Ordering by JSON arrays or objects is not supported"
|
|
2846
3281
|
);
|
|
2847
|
-
},
|
|
2848
|
-
update(currentState, temporalFilterPass) {
|
|
2849
|
-
return {
|
|
2850
|
-
...currentState,
|
|
2851
|
-
temporalFilterPass
|
|
2852
|
-
};
|
|
2853
3282
|
}
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
throw new CompilerInvariantError(
|
|
2862
|
-
"Recursive traversal pass did not select traversal"
|
|
2863
|
-
);
|
|
2864
|
-
}
|
|
2865
|
-
return collectRequiredColumnsByAlias(currentState.ast, traversal);
|
|
2866
|
-
},
|
|
2867
|
-
update(currentState, requiredColumnsByAlias) {
|
|
2868
|
-
return {
|
|
2869
|
-
...currentState,
|
|
2870
|
-
requiredColumnsByAlias
|
|
2871
|
-
};
|
|
2872
|
-
}
|
|
2873
|
-
});
|
|
2874
|
-
state = columnPruningPass.state;
|
|
2875
|
-
const logicalPlanPass = runCompilerPass(state, {
|
|
2876
|
-
name: "logical_plan",
|
|
2877
|
-
execute(currentState) {
|
|
2878
|
-
const loweringInput = {
|
|
2879
|
-
ast: currentState.ast,
|
|
2880
|
-
dialect: currentState.ctx.dialect.name,
|
|
2881
|
-
graphId: currentState.graphId,
|
|
2882
|
-
...currentState.traversal === void 0 ? {} : { traversal: currentState.traversal }
|
|
2883
|
-
};
|
|
2884
|
-
return lowerRecursiveQueryToLogicalPlan(loweringInput);
|
|
2885
|
-
},
|
|
2886
|
-
update(currentState, logicalPlan) {
|
|
2887
|
-
return {
|
|
2888
|
-
...currentState,
|
|
2889
|
-
logicalPlan
|
|
2890
|
-
};
|
|
2891
|
-
}
|
|
2892
|
-
});
|
|
2893
|
-
state = logicalPlanPass.state;
|
|
2894
|
-
return state;
|
|
2895
|
-
}
|
|
2896
|
-
function compileVariableLengthQuery(ast, graphId, ctx) {
|
|
2897
|
-
const strategy = ctx.dialect.capabilities.recursiveQueryStrategy;
|
|
2898
|
-
const handler = RECURSIVE_QUERY_STRATEGY_HANDLERS[strategy];
|
|
2899
|
-
return handler(ast, graphId, ctx);
|
|
2900
|
-
}
|
|
2901
|
-
var RECURSIVE_QUERY_STRATEGY_HANDLERS = {
|
|
2902
|
-
recursive_cte: compileVariableLengthQueryWithRecursiveCteStrategy
|
|
2903
|
-
};
|
|
2904
|
-
function compileVariableLengthQueryWithRecursiveCteStrategy(ast, graphId, ctx) {
|
|
2905
|
-
const passState = runRecursiveQueryPassPipeline(ast, graphId, ctx);
|
|
2906
|
-
const { dialect } = ctx;
|
|
2907
|
-
const {
|
|
2908
|
-
logicalPlan,
|
|
2909
|
-
requiredColumnsByAlias,
|
|
2910
|
-
temporalFilterPass,
|
|
2911
|
-
traversal: vlTraversal
|
|
2912
|
-
} = passState;
|
|
2913
|
-
if (temporalFilterPass === void 0) {
|
|
2914
|
-
throw new CompilerInvariantError(
|
|
2915
|
-
"Temporal filter pass did not initialize temporal state"
|
|
3283
|
+
const field2 = compileFieldValue(orderSpec.field, dialect, valueType);
|
|
3284
|
+
const direction = sql.raw(orderSpec.direction.toUpperCase());
|
|
3285
|
+
const nulls = orderSpec.nulls ?? (orderSpec.direction === "asc" ? "last" : "first");
|
|
3286
|
+
const nullsDirection = sql.raw(nulls === "first" ? "DESC" : "ASC");
|
|
3287
|
+
parts.push(
|
|
3288
|
+
sql`(${field2} IS NULL) ${nullsDirection}`,
|
|
3289
|
+
sql`${field2} ${direction}`
|
|
2916
3290
|
);
|
|
2917
3291
|
}
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
3292
|
+
return sql`ORDER BY ${sql.join(parts, sql`, `)}`;
|
|
3293
|
+
}
|
|
3294
|
+
function compileLimitOffset(ast) {
|
|
3295
|
+
const parts = [];
|
|
3296
|
+
if (ast.limit !== void 0) {
|
|
3297
|
+
parts.push(sql`LIMIT ${ast.limit}`);
|
|
2922
3298
|
}
|
|
2923
|
-
if (
|
|
2924
|
-
|
|
2925
|
-
"Recursive traversal pass did not select traversal"
|
|
2926
|
-
);
|
|
3299
|
+
if (ast.offset !== void 0) {
|
|
3300
|
+
parts.push(sql`OFFSET ${ast.offset}`);
|
|
2927
3301
|
}
|
|
2928
|
-
|
|
2929
|
-
ast,
|
|
2930
|
-
vlTraversal,
|
|
2931
|
-
graphId,
|
|
2932
|
-
ctx,
|
|
2933
|
-
requiredColumnsByAlias,
|
|
2934
|
-
temporalFilterPass
|
|
2935
|
-
);
|
|
2936
|
-
const projection = compileRecursiveProjection(ast, vlTraversal, dialect);
|
|
2937
|
-
const minDepth = vlTraversal.variableLength.minDepth;
|
|
2938
|
-
const depthFilter = minDepth > 0 ? sql`WHERE depth >= ${minDepth}` : sql.raw("");
|
|
2939
|
-
const orderBy = compileRecursiveOrderBy(ast, dialect);
|
|
2940
|
-
const limitOffset = compileLimitOffset(ast);
|
|
2941
|
-
return emitRecursiveQuerySql({
|
|
2942
|
-
depthFilter,
|
|
2943
|
-
...limitOffset === void 0 ? {} : { limitOffset },
|
|
2944
|
-
logicalPlan,
|
|
2945
|
-
...orderBy === void 0 ? {} : { orderBy },
|
|
2946
|
-
projection,
|
|
2947
|
-
recursiveCte
|
|
2948
|
-
});
|
|
2949
|
-
}
|
|
2950
|
-
function hasVariableLengthTraversal(ast) {
|
|
2951
|
-
return ast.traversals.some((t) => t.variableLength !== void 0);
|
|
3302
|
+
return parts.length > 0 ? sql.join(parts, sql` `) : void 0;
|
|
2952
3303
|
}
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
const direction = traversal.direction;
|
|
2964
|
-
const vl = traversal.variableLength;
|
|
2965
|
-
const shouldEnforceCycleCheck = vl.cyclePolicy !== "allow";
|
|
2966
|
-
const shouldTrackPath = shouldEnforceCycleCheck || vl.pathAlias !== void 0;
|
|
2967
|
-
const recursiveJoinRequiredColumns = /* @__PURE__ */ new Set(["id"]);
|
|
2968
|
-
if (previousNodeKinds.length > 1) {
|
|
2969
|
-
recursiveJoinRequiredColumns.add("kind");
|
|
3304
|
+
var DEFAULT_TABLE_NAMES = {
|
|
3305
|
+
nodes: "typegraph_nodes",
|
|
3306
|
+
edges: "typegraph_edges",
|
|
3307
|
+
embeddings: "typegraph_node_embeddings"
|
|
3308
|
+
};
|
|
3309
|
+
var MAX_IDENTIFIER_LENGTH = 63;
|
|
3310
|
+
var VALID_IDENTIFIER_PATTERN = /^[a-z_][a-z0-9_$]*$/i;
|
|
3311
|
+
function validateTableName(name, label) {
|
|
3312
|
+
if (!name || name.length === 0) {
|
|
3313
|
+
throw new ConfigurationError(`${label} table name cannot be empty`);
|
|
2970
3314
|
}
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
"n0",
|
|
2975
|
-
startAlias,
|
|
2976
|
-
requiredStartColumns,
|
|
2977
|
-
NO_ALWAYS_REQUIRED_COLUMNS
|
|
2978
|
-
);
|
|
2979
|
-
const startColumnsFromRecursive = compileNodeSelectColumnsFromRecursiveRow(
|
|
2980
|
-
startAlias,
|
|
2981
|
-
requiredStartColumns,
|
|
2982
|
-
NO_ALWAYS_REQUIRED_COLUMNS
|
|
2983
|
-
);
|
|
2984
|
-
const nodeColumnsFromBase = compileNodeSelectColumnsFromTable(
|
|
2985
|
-
"n0",
|
|
2986
|
-
nodeAlias,
|
|
2987
|
-
requiredNodeColumns,
|
|
2988
|
-
recursiveJoinRequiredColumns
|
|
2989
|
-
);
|
|
2990
|
-
const nodeColumnsFromRecursive = compileNodeSelectColumnsFromTable(
|
|
2991
|
-
"n",
|
|
2992
|
-
nodeAlias,
|
|
2993
|
-
requiredNodeColumns,
|
|
2994
|
-
recursiveJoinRequiredColumns
|
|
2995
|
-
);
|
|
2996
|
-
const startKindFilter = compileKindFilter2(startKinds, "n0.kind");
|
|
2997
|
-
const nodeKindFilter = compileKindFilter2(nodeKinds, "n.kind");
|
|
2998
|
-
const startTemporalFilter = temporalFilterPass.forAlias("n0");
|
|
2999
|
-
const edgeTemporalFilter = temporalFilterPass.forAlias("e");
|
|
3000
|
-
const nodeTemporalFilter = temporalFilterPass.forAlias("n");
|
|
3001
|
-
const startContext = { ...ctx, cteColumnPrefix: "" };
|
|
3002
|
-
const startPredicates = compileNodePredicates(ast, startAlias, startContext);
|
|
3003
|
-
const edgeContext = { ...ctx, cteColumnPrefix: "e" };
|
|
3004
|
-
const edgePredicates = compileEdgePredicates(
|
|
3005
|
-
ast,
|
|
3006
|
-
traversal.edgeAlias,
|
|
3007
|
-
edgeContext
|
|
3008
|
-
);
|
|
3009
|
-
const targetContext = { ...ctx, cteColumnPrefix: "n" };
|
|
3010
|
-
const targetNodePredicates = compileNodePredicates(
|
|
3011
|
-
ast,
|
|
3012
|
-
nodeAlias,
|
|
3013
|
-
targetContext
|
|
3014
|
-
);
|
|
3015
|
-
if (vl.maxDepth > MAX_EXPLICIT_RECURSIVE_DEPTH) {
|
|
3016
|
-
throw new UnsupportedPredicateError(
|
|
3017
|
-
`Recursive traversal maxHops(${vl.maxDepth}) exceeds maximum explicit depth of ${MAX_EXPLICIT_RECURSIVE_DEPTH}`
|
|
3315
|
+
if (name.length > MAX_IDENTIFIER_LENGTH) {
|
|
3316
|
+
throw new ConfigurationError(
|
|
3317
|
+
`${label} table name exceeds maximum length of ${MAX_IDENTIFIER_LENGTH} characters`
|
|
3018
3318
|
);
|
|
3019
3319
|
}
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
const pathExtension = shouldTrackPath ? dialect.extendPath(sql.raw("r.path"), sql.raw("n.id")) : void 0;
|
|
3025
|
-
const baseWhereClauses = [
|
|
3026
|
-
sql`n0.graph_id = ${graphId}`,
|
|
3027
|
-
startKindFilter,
|
|
3028
|
-
startTemporalFilter,
|
|
3029
|
-
...startPredicates
|
|
3030
|
-
];
|
|
3031
|
-
const recursiveBaseWhereClauses = [
|
|
3032
|
-
sql`e.graph_id = ${graphId}`,
|
|
3033
|
-
nodeKindFilter,
|
|
3034
|
-
edgeTemporalFilter,
|
|
3035
|
-
nodeTemporalFilter,
|
|
3036
|
-
maxDepthCondition
|
|
3037
|
-
];
|
|
3038
|
-
if (cycleCheck !== void 0) {
|
|
3039
|
-
recursiveBaseWhereClauses.push(cycleCheck);
|
|
3320
|
+
if (!VALID_IDENTIFIER_PATTERN.test(name)) {
|
|
3321
|
+
throw new ConfigurationError(
|
|
3322
|
+
`${label} table name "${name}" is not a valid SQL identifier. Table names must start with a letter or underscore and contain only letters, digits, underscores, or dollar signs.`
|
|
3323
|
+
);
|
|
3040
3324
|
}
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3325
|
+
}
|
|
3326
|
+
function quoteIdentifier2(name) {
|
|
3327
|
+
return `"${name.replaceAll('"', '""')}"`;
|
|
3328
|
+
}
|
|
3329
|
+
function createSqlSchema(names = {}) {
|
|
3330
|
+
const tables = { ...DEFAULT_TABLE_NAMES, ...names };
|
|
3331
|
+
validateTableName(tables.nodes, "nodes");
|
|
3332
|
+
validateTableName(tables.edges, "edges");
|
|
3333
|
+
validateTableName(tables.embeddings, "embeddings");
|
|
3334
|
+
return {
|
|
3335
|
+
tables,
|
|
3336
|
+
nodesTable: sql.raw(quoteIdentifier2(tables.nodes)),
|
|
3337
|
+
edgesTable: sql.raw(quoteIdentifier2(tables.edges)),
|
|
3338
|
+
embeddingsTable: sql.raw(quoteIdentifier2(tables.embeddings))
|
|
3339
|
+
};
|
|
3340
|
+
}
|
|
3341
|
+
var DEFAULT_SQL_SCHEMA = createSqlSchema();
|
|
3342
|
+
|
|
3343
|
+
// src/query/execution/value-decoder.ts
|
|
3344
|
+
function nullToUndefined(value) {
|
|
3345
|
+
return value === null ? void 0 : value;
|
|
3346
|
+
}
|
|
3347
|
+
function decodeSelectedValue(value, typeInfo) {
|
|
3348
|
+
const normalized = nullToUndefined(value);
|
|
3349
|
+
if (normalized === void 0) return void 0;
|
|
3350
|
+
if (typeInfo === void 0) {
|
|
3351
|
+
return normalized;
|
|
3352
|
+
}
|
|
3353
|
+
return decodeByValueType(normalized, typeInfo.valueType);
|
|
3354
|
+
}
|
|
3355
|
+
function decodeByValueType(value, valueType) {
|
|
3356
|
+
switch (valueType) {
|
|
3357
|
+
case "boolean": {
|
|
3358
|
+
if (typeof value === "boolean") return value;
|
|
3359
|
+
if (typeof value === "number") return value !== 0;
|
|
3360
|
+
if (typeof value === "string") {
|
|
3361
|
+
if (value === "0") return false;
|
|
3362
|
+
if (value === "1") return true;
|
|
3363
|
+
if (value.toLowerCase() === "true") return true;
|
|
3364
|
+
if (value.toLowerCase() === "false") return false;
|
|
3365
|
+
}
|
|
3366
|
+
return Boolean(value);
|
|
3051
3367
|
}
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3368
|
+
case "number": {
|
|
3369
|
+
if (typeof value === "number") return value;
|
|
3370
|
+
if (typeof value === "string") {
|
|
3371
|
+
const parsed = Number(value);
|
|
3372
|
+
return Number.isNaN(parsed) ? value : parsed;
|
|
3373
|
+
}
|
|
3374
|
+
return value;
|
|
3059
3375
|
}
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
);
|
|
3376
|
+
case "array":
|
|
3377
|
+
case "object":
|
|
3378
|
+
case "embedding": {
|
|
3379
|
+
if (typeof value !== "string") return value;
|
|
3380
|
+
const trimmed = value.trim();
|
|
3381
|
+
const looksJson = trimmed.startsWith("[") || trimmed.startsWith("{");
|
|
3382
|
+
if (!looksJson) return value;
|
|
3383
|
+
try {
|
|
3384
|
+
return JSON.parse(trimmed);
|
|
3385
|
+
} catch {
|
|
3386
|
+
return value;
|
|
3387
|
+
}
|
|
3067
3388
|
}
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
FROM recursive_cte r
|
|
3076
|
-
CROSS JOIN ${ctx.schema.edgesTable} e
|
|
3077
|
-
JOIN ${ctx.schema.nodesTable} n ON n.graph_id = e.graph_id
|
|
3078
|
-
AND n.id = e.${sql.raw(branch.targetField)}
|
|
3079
|
-
AND n.kind = e.${sql.raw(branch.targetKindField)}
|
|
3080
|
-
WHERE ${sql.join(recursiveWhereClauses, sql` AND `)}
|
|
3081
|
-
`;
|
|
3389
|
+
case "string":
|
|
3390
|
+
case "date":
|
|
3391
|
+
case "unknown": {
|
|
3392
|
+
return value;
|
|
3393
|
+
}
|
|
3394
|
+
default: {
|
|
3395
|
+
return value;
|
|
3082
3396
|
}
|
|
3083
|
-
return sql`
|
|
3084
|
-
SELECT ${sql.join(recursiveSelectColumns, sql`, `)}
|
|
3085
|
-
FROM recursive_cte r
|
|
3086
|
-
JOIN ${ctx.schema.edgesTable} e ON ${sql.join(recursiveJoinClauses, sql` AND `)}
|
|
3087
|
-
JOIN ${ctx.schema.nodesTable} n ON n.graph_id = e.graph_id
|
|
3088
|
-
AND n.id = e.${sql.raw(branch.targetField)}
|
|
3089
|
-
AND n.kind = e.${sql.raw(branch.targetKindField)}
|
|
3090
|
-
WHERE ${sql.join(recursiveFilterClauses, sql` AND `)}
|
|
3091
|
-
`;
|
|
3092
3397
|
}
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
// src/store/reserved-keys.ts
|
|
3401
|
+
var RESERVED_NODE_KEYS2 = /* @__PURE__ */ new Set([
|
|
3402
|
+
"id",
|
|
3403
|
+
"kind",
|
|
3404
|
+
"meta"
|
|
3405
|
+
]);
|
|
3406
|
+
var RESERVED_EDGE_KEYS2 = /* @__PURE__ */ new Set([
|
|
3407
|
+
"id",
|
|
3408
|
+
"kind",
|
|
3409
|
+
"meta",
|
|
3410
|
+
"fromKind",
|
|
3411
|
+
"fromId",
|
|
3412
|
+
"toKind",
|
|
3413
|
+
"toId"
|
|
3414
|
+
]);
|
|
3415
|
+
var PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set([
|
|
3416
|
+
"__proto__",
|
|
3417
|
+
"constructor",
|
|
3418
|
+
"prototype"
|
|
3419
|
+
]);
|
|
3420
|
+
function validateProjectionField(field2, entityType, kind) {
|
|
3421
|
+
const reserved = entityType === "node" ? RESERVED_NODE_KEYS2 : RESERVED_EDGE_KEYS2;
|
|
3422
|
+
if (reserved.has(field2)) {
|
|
3423
|
+
throw new ConfigurationError(
|
|
3424
|
+
`Projection field "${field2}" on ${entityType} kind "${kind}" conflicts with a reserved structural key`,
|
|
3425
|
+
{ field: field2, kind, entityType, reservedKeys: [...reserved] },
|
|
3426
|
+
{
|
|
3427
|
+
suggestion: `Remove "${field2}" from the projection. Structural fields (${[...reserved].join(", ")}) are included automatically when relevant.`
|
|
3428
|
+
}
|
|
3111
3429
|
);
|
|
3112
|
-
const duplicateGuard = overlappingKinds.length > 0 ? sql`NOT (e.from_id = e.to_id AND ${compileKindFilter2(overlappingKinds, "e.kind")})` : void 0;
|
|
3113
|
-
const inverseBranch = compileRecursiveBranch2({
|
|
3114
|
-
joinField: inverseJoinField,
|
|
3115
|
-
targetField: inverseTargetField,
|
|
3116
|
-
joinKindField: inverseJoinKindField,
|
|
3117
|
-
targetKindField: inverseTargetKindField,
|
|
3118
|
-
edgeKinds: inverseEdgeKinds,
|
|
3119
|
-
duplicateGuard
|
|
3120
|
-
});
|
|
3121
|
-
return sql`
|
|
3122
|
-
${directBranch}
|
|
3123
|
-
UNION ALL
|
|
3124
|
-
${inverseBranch}
|
|
3125
|
-
`;
|
|
3126
3430
|
}
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3431
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(field2)) {
|
|
3432
|
+
throw new ConfigurationError(
|
|
3433
|
+
`Projection field "${field2}" on ${entityType} kind "${kind}" is not allowed`,
|
|
3434
|
+
{ field: field2, kind, entityType },
|
|
3435
|
+
{
|
|
3436
|
+
suggestion: `"${field2}" cannot be used as a projection field name.`
|
|
3437
|
+
}
|
|
3438
|
+
);
|
|
3135
3439
|
}
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3440
|
+
}
|
|
3441
|
+
function filterReservedKeys(props, reservedKeys) {
|
|
3442
|
+
const filtered = {};
|
|
3443
|
+
for (const [key, value] of Object.entries(props)) {
|
|
3444
|
+
if (!reservedKeys.has(key)) {
|
|
3445
|
+
filtered[key] = value;
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
return filtered;
|
|
3449
|
+
}
|
|
3144
3450
|
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
`;
|
|
3451
|
+
// src/store/row-mappers.ts
|
|
3452
|
+
function nullToUndefined2(value) {
|
|
3453
|
+
return value === null ? void 0 : value;
|
|
3149
3454
|
}
|
|
3150
|
-
function
|
|
3151
|
-
|
|
3455
|
+
function rowToNode(row) {
|
|
3456
|
+
const rawProps = JSON.parse(row.props);
|
|
3457
|
+
const props = filterReservedKeys(rawProps, RESERVED_NODE_KEYS2);
|
|
3458
|
+
return {
|
|
3459
|
+
kind: row.kind,
|
|
3460
|
+
id: row.id,
|
|
3461
|
+
meta: rowToNodeMeta(row),
|
|
3462
|
+
...props
|
|
3463
|
+
};
|
|
3152
3464
|
}
|
|
3153
|
-
function
|
|
3154
|
-
return
|
|
3465
|
+
function rowToNodeMeta(row) {
|
|
3466
|
+
return {
|
|
3467
|
+
version: row.version,
|
|
3468
|
+
validFrom: nullToUndefined2(row.valid_from),
|
|
3469
|
+
validTo: nullToUndefined2(row.valid_to),
|
|
3470
|
+
createdAt: row.created_at,
|
|
3471
|
+
updatedAt: row.updated_at,
|
|
3472
|
+
deletedAt: nullToUndefined2(row.deleted_at)
|
|
3473
|
+
};
|
|
3155
3474
|
}
|
|
3156
|
-
function
|
|
3157
|
-
|
|
3475
|
+
function rowToEdge(row) {
|
|
3476
|
+
const rawProps = JSON.parse(row.props);
|
|
3477
|
+
const props = filterReservedKeys(rawProps, RESERVED_EDGE_KEYS2);
|
|
3478
|
+
return {
|
|
3479
|
+
id: row.id,
|
|
3480
|
+
kind: row.kind,
|
|
3481
|
+
fromKind: row.from_kind,
|
|
3482
|
+
fromId: row.from_id,
|
|
3483
|
+
toKind: row.to_kind,
|
|
3484
|
+
toId: row.to_id,
|
|
3485
|
+
meta: rowToEdgeMeta(row),
|
|
3486
|
+
...props
|
|
3487
|
+
};
|
|
3158
3488
|
}
|
|
3159
|
-
function
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3489
|
+
function rowToEdgeMeta(row) {
|
|
3490
|
+
return {
|
|
3491
|
+
validFrom: nullToUndefined2(row.valid_from),
|
|
3492
|
+
validTo: nullToUndefined2(row.valid_to),
|
|
3493
|
+
createdAt: row.created_at,
|
|
3494
|
+
updatedAt: row.updated_at,
|
|
3495
|
+
deletedAt: nullToUndefined2(row.deleted_at)
|
|
3496
|
+
};
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
// src/store/subgraph.ts
|
|
3500
|
+
var DEFAULT_SUBGRAPH_MAX_DEPTH = 10;
|
|
3501
|
+
var MAX_PG_IDENTIFIER_LENGTH = 63;
|
|
3502
|
+
function fnv1aBase36(input) {
|
|
3503
|
+
let hash = 2166136261;
|
|
3504
|
+
for (const character of input) {
|
|
3505
|
+
const codePoint = character.codePointAt(0);
|
|
3506
|
+
if (codePoint === void 0) continue;
|
|
3507
|
+
hash ^= codePoint;
|
|
3508
|
+
hash = Math.imul(hash, 16777619);
|
|
3509
|
+
}
|
|
3510
|
+
return (hash >>> 0).toString(36);
|
|
3511
|
+
}
|
|
3512
|
+
var TEXT_ENCODER = new TextEncoder();
|
|
3513
|
+
function truncateToBytes(value, maxBytes) {
|
|
3514
|
+
const encoded = TEXT_ENCODER.encode(value);
|
|
3515
|
+
if (encoded.byteLength <= maxBytes) return value;
|
|
3516
|
+
let end = maxBytes;
|
|
3517
|
+
while (end > 0 && encoded[end] >= 128 && encoded[end] < 192) {
|
|
3518
|
+
end--;
|
|
3519
|
+
}
|
|
3520
|
+
return new TextDecoder().decode(encoded.slice(0, end));
|
|
3521
|
+
}
|
|
3522
|
+
function projectionAlias(entityPrefix, kind, field2) {
|
|
3523
|
+
const prefix = entityPrefix === "node" ? "sg_n" : "sg_e";
|
|
3524
|
+
const hash = fnv1aBase36(`${kind}\0${field2}`);
|
|
3525
|
+
const fixedBytes = prefix.length + 1 + 1 + hash.length;
|
|
3526
|
+
const maxKindBytes = MAX_PG_IDENTIFIER_LENGTH - fixedBytes;
|
|
3527
|
+
const truncatedKind = truncateToBytes(kind, maxKindBytes);
|
|
3528
|
+
return `${prefix}_${truncatedKind}_${hash}`;
|
|
3529
|
+
}
|
|
3530
|
+
function normalizeProps(value) {
|
|
3531
|
+
return typeof value === "string" ? value : JSON.stringify(value ?? {});
|
|
3532
|
+
}
|
|
3533
|
+
function defineSubgraphProject(_graph) {
|
|
3534
|
+
return (project) => project;
|
|
3535
|
+
}
|
|
3536
|
+
async function executeSubgraph(params) {
|
|
3537
|
+
const { options } = params;
|
|
3538
|
+
if (options.edges.length === 0) {
|
|
3539
|
+
return { nodes: [], edges: [] };
|
|
3171
3540
|
}
|
|
3172
|
-
|
|
3173
|
-
|
|
3541
|
+
const maxDepth = Math.min(
|
|
3542
|
+
options.maxDepth ?? DEFAULT_SUBGRAPH_MAX_DEPTH,
|
|
3543
|
+
MAX_RECURSIVE_DEPTH
|
|
3544
|
+
);
|
|
3545
|
+
const ctx = {
|
|
3546
|
+
graphId: params.graphId,
|
|
3547
|
+
rootId: params.rootId,
|
|
3548
|
+
edgeKinds: options.edges,
|
|
3549
|
+
maxDepth,
|
|
3550
|
+
includeKinds: options.includeKinds,
|
|
3551
|
+
excludeRoot: options.excludeRoot ?? false,
|
|
3552
|
+
direction: options.direction ?? "out",
|
|
3553
|
+
cyclePolicy: options.cyclePolicy ?? "prevent",
|
|
3554
|
+
dialect: params.dialect,
|
|
3555
|
+
schema: params.schema ?? DEFAULT_SQL_SCHEMA,
|
|
3556
|
+
backend: params.backend
|
|
3557
|
+
};
|
|
3558
|
+
const schemaIntrospector = getSubgraphSchemaIntrospector(params.graph);
|
|
3559
|
+
const nodeProjectionPlan = buildProjectionPlan(
|
|
3560
|
+
getIncludedNodeKinds(params.graph, options.includeKinds),
|
|
3561
|
+
options.project?.nodes,
|
|
3562
|
+
(kind, field2) => schemaIntrospector.getFieldTypeInfo(kind, field2),
|
|
3563
|
+
"node"
|
|
3564
|
+
);
|
|
3565
|
+
const edgeProjectionPlan = buildProjectionPlan(
|
|
3566
|
+
dedupeStrings(options.edges),
|
|
3567
|
+
options.project?.edges,
|
|
3568
|
+
(kind, field2) => schemaIntrospector.getEdgeFieldTypeInfo(kind, field2),
|
|
3569
|
+
"edge"
|
|
3570
|
+
);
|
|
3571
|
+
const reachableCte = buildReachableCte(ctx);
|
|
3572
|
+
const includedIdsCte = buildIncludedIdsCte(ctx);
|
|
3573
|
+
const [nodeRows, edgeRows] = await Promise.all([
|
|
3574
|
+
fetchSubgraphNodes(ctx, reachableCte, includedIdsCte, nodeProjectionPlan),
|
|
3575
|
+
fetchSubgraphEdges(ctx, reachableCte, includedIdsCte, edgeProjectionPlan)
|
|
3576
|
+
]);
|
|
3577
|
+
const nodes = nodeRows.map(
|
|
3578
|
+
(row) => mapSubgraphNodeRow(row, nodeProjectionPlan)
|
|
3579
|
+
);
|
|
3580
|
+
const edges = edgeRows.map(
|
|
3581
|
+
(row) => mapSubgraphEdgeRow(row, edgeProjectionPlan)
|
|
3582
|
+
);
|
|
3583
|
+
return {
|
|
3584
|
+
nodes,
|
|
3585
|
+
edges
|
|
3586
|
+
};
|
|
3587
|
+
}
|
|
3588
|
+
var introspectorCache = /* @__PURE__ */ new WeakMap();
|
|
3589
|
+
function getSubgraphSchemaIntrospector(graph) {
|
|
3590
|
+
const cached = introspectorCache.get(graph);
|
|
3591
|
+
if (cached !== void 0) return cached;
|
|
3592
|
+
const nodeKinds = new Map(
|
|
3593
|
+
Object.entries(graph.nodes).map(([kind, definition]) => [
|
|
3594
|
+
kind,
|
|
3595
|
+
{ schema: definition.type.schema }
|
|
3596
|
+
])
|
|
3597
|
+
);
|
|
3598
|
+
const edgeKinds = new Map(
|
|
3599
|
+
Object.entries(graph.edges).map(([kind, definition]) => [
|
|
3600
|
+
kind,
|
|
3601
|
+
{ schema: definition.type.schema }
|
|
3602
|
+
])
|
|
3603
|
+
);
|
|
3604
|
+
const introspector = createSchemaIntrospector(nodeKinds, edgeKinds);
|
|
3605
|
+
introspectorCache.set(graph, introspector);
|
|
3606
|
+
return introspector;
|
|
3607
|
+
}
|
|
3608
|
+
function buildProjectionPlan(kinds, projectionMap, resolveFieldType, entityPrefix) {
|
|
3609
|
+
const projectedKinds = /* @__PURE__ */ new Map();
|
|
3610
|
+
const fullKinds = [];
|
|
3611
|
+
for (const kind of kinds) {
|
|
3612
|
+
const selection = projectionMap?.[kind];
|
|
3613
|
+
if (selection === void 0) {
|
|
3614
|
+
fullKinds.push(kind);
|
|
3615
|
+
continue;
|
|
3616
|
+
}
|
|
3617
|
+
projectedKinds.set(
|
|
3618
|
+
kind,
|
|
3619
|
+
buildKindProjectionPlan(kind, selection, resolveFieldType, entityPrefix)
|
|
3620
|
+
);
|
|
3174
3621
|
}
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3622
|
+
return { fullKinds, projectedKinds };
|
|
3623
|
+
}
|
|
3624
|
+
function buildKindProjectionPlan(kind, selection, resolveFieldType, entityPrefix) {
|
|
3625
|
+
const propertyFields = /* @__PURE__ */ new Map();
|
|
3626
|
+
let includeMeta = false;
|
|
3627
|
+
for (const field2 of selection) {
|
|
3628
|
+
if (field2 === "meta") {
|
|
3629
|
+
includeMeta = true;
|
|
3630
|
+
continue;
|
|
3631
|
+
}
|
|
3632
|
+
validateProjectionField(field2, entityPrefix, kind);
|
|
3633
|
+
if (!propertyFields.has(field2)) {
|
|
3634
|
+
propertyFields.set(field2, {
|
|
3635
|
+
field: field2,
|
|
3636
|
+
outputName: projectionAlias(entityPrefix, kind, field2),
|
|
3637
|
+
typeInfo: resolveFieldType(kind, field2)
|
|
3638
|
+
});
|
|
3178
3639
|
}
|
|
3179
3640
|
}
|
|
3180
|
-
return
|
|
3641
|
+
return {
|
|
3642
|
+
includeMeta,
|
|
3643
|
+
propertyFields: [...propertyFields.values()]
|
|
3644
|
+
};
|
|
3181
3645
|
}
|
|
3182
|
-
function
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
);
|
|
3646
|
+
function getIncludedNodeKinds(graph, includeKinds) {
|
|
3647
|
+
if (includeKinds === void 0 || includeKinds.length === 0) {
|
|
3648
|
+
return Object.keys(graph.nodes);
|
|
3649
|
+
}
|
|
3650
|
+
return dedupeStrings(includeKinds);
|
|
3188
3651
|
}
|
|
3189
|
-
function
|
|
3190
|
-
return
|
|
3191
|
-
(column) => shouldProjectColumn(requiredColumns, column, alwaysRequiredColumns)
|
|
3192
|
-
).map((column) => {
|
|
3193
|
-
const projected = `${alias}_${column}`;
|
|
3194
|
-
return sql`r.${sql.raw(projected)} AS ${sql.raw(projected)}`;
|
|
3195
|
-
});
|
|
3652
|
+
function dedupeStrings(values) {
|
|
3653
|
+
return [...new Set(values)];
|
|
3196
3654
|
}
|
|
3197
|
-
function
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3655
|
+
function buildReachableCte(ctx) {
|
|
3656
|
+
const shouldTrackPath = ctx.cyclePolicy === "prevent";
|
|
3657
|
+
const edgeKindFilter = compileKindFilter(sql.raw("e.kind"), ctx.edgeKinds);
|
|
3658
|
+
const initialPath = shouldTrackPath ? ctx.dialect.initializePath(sql.raw("n.id")) : void 0;
|
|
3659
|
+
const pathExtension = shouldTrackPath ? ctx.dialect.extendPath(sql.raw("r.path"), sql.raw("n.id")) : void 0;
|
|
3660
|
+
const cycleCheck = shouldTrackPath ? ctx.dialect.cycleCheck(sql.raw("n.id"), sql.raw("r.path")) : void 0;
|
|
3661
|
+
const baseColumns = [sql`n.id`, sql`n.kind`, sql`0 AS depth`];
|
|
3662
|
+
if (initialPath !== void 0) {
|
|
3663
|
+
baseColumns.push(sql`${initialPath} AS path`);
|
|
3205
3664
|
}
|
|
3206
|
-
const
|
|
3207
|
-
const
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
sql`${sql.raw(startAlias)}_id`,
|
|
3212
|
-
sql`${sql.raw(startAlias)}_kind`,
|
|
3213
|
-
sql`${sql.raw(startAlias)}_props`,
|
|
3214
|
-
sql`${sql.raw(startAlias)}_version`,
|
|
3215
|
-
sql`${sql.raw(startAlias)}_valid_from`,
|
|
3216
|
-
sql`${sql.raw(startAlias)}_valid_to`,
|
|
3217
|
-
sql`${sql.raw(startAlias)}_created_at`,
|
|
3218
|
-
sql`${sql.raw(startAlias)}_updated_at`,
|
|
3219
|
-
sql`${sql.raw(startAlias)}_deleted_at`,
|
|
3220
|
-
// Node alias fields with metadata
|
|
3221
|
-
sql`${sql.raw(nodeAlias)}_id`,
|
|
3222
|
-
sql`${sql.raw(nodeAlias)}_kind`,
|
|
3223
|
-
sql`${sql.raw(nodeAlias)}_props`,
|
|
3224
|
-
sql`${sql.raw(nodeAlias)}_version`,
|
|
3225
|
-
sql`${sql.raw(nodeAlias)}_valid_from`,
|
|
3226
|
-
sql`${sql.raw(nodeAlias)}_valid_to`,
|
|
3227
|
-
sql`${sql.raw(nodeAlias)}_created_at`,
|
|
3228
|
-
sql`${sql.raw(nodeAlias)}_updated_at`,
|
|
3229
|
-
sql`${sql.raw(nodeAlias)}_deleted_at`
|
|
3665
|
+
const baseCase = sql`SELECT ${sql.join(baseColumns, sql`, `)} FROM ${ctx.schema.nodesTable} n WHERE n.graph_id = ${ctx.graphId} AND n.id = ${ctx.rootId} AND n.deleted_at IS NULL`;
|
|
3666
|
+
const recursiveColumns = [
|
|
3667
|
+
sql`n.id`,
|
|
3668
|
+
sql`n.kind`,
|
|
3669
|
+
sql`r.depth + 1 AS depth`
|
|
3230
3670
|
];
|
|
3231
|
-
if (
|
|
3232
|
-
|
|
3671
|
+
if (pathExtension !== void 0) {
|
|
3672
|
+
recursiveColumns.push(sql`${pathExtension} AS path`);
|
|
3673
|
+
}
|
|
3674
|
+
const recursiveWhereClauses = [
|
|
3675
|
+
sql`e.graph_id = ${ctx.graphId}`,
|
|
3676
|
+
edgeKindFilter,
|
|
3677
|
+
sql`e.deleted_at IS NULL`,
|
|
3678
|
+
sql`n.deleted_at IS NULL`,
|
|
3679
|
+
sql`r.depth < ${ctx.maxDepth}`
|
|
3680
|
+
];
|
|
3681
|
+
if (cycleCheck !== void 0) {
|
|
3682
|
+
recursiveWhereClauses.push(cycleCheck);
|
|
3683
|
+
}
|
|
3684
|
+
const forceWorktableOuterJoinOrder = ctx.dialect.capabilities.forceRecursiveWorktableOuterJoinOrder;
|
|
3685
|
+
const recursiveCase = ctx.direction === "both" ? compileBidirectionalBranch({
|
|
3686
|
+
recursiveColumns,
|
|
3687
|
+
whereClauses: recursiveWhereClauses,
|
|
3688
|
+
forceWorktableOuterJoinOrder,
|
|
3689
|
+
schema: ctx.schema
|
|
3690
|
+
}) : compileRecursiveBranch({
|
|
3691
|
+
recursiveColumns,
|
|
3692
|
+
whereClauses: recursiveWhereClauses,
|
|
3693
|
+
joinField: "from_id",
|
|
3694
|
+
targetField: "to_id",
|
|
3695
|
+
targetKindField: "to_kind",
|
|
3696
|
+
forceWorktableOuterJoinOrder,
|
|
3697
|
+
schema: ctx.schema
|
|
3698
|
+
});
|
|
3699
|
+
return sql`WITH RECURSIVE reachable AS (${baseCase} UNION ALL ${recursiveCase})`;
|
|
3700
|
+
}
|
|
3701
|
+
function compileRecursiveBranch(params) {
|
|
3702
|
+
const columns = [...params.recursiveColumns];
|
|
3703
|
+
const selectClause = sql`SELECT ${sql.join(columns, sql`, `)}`;
|
|
3704
|
+
const nodeJoin = sql`JOIN ${params.schema.nodesTable} n ON n.graph_id = e.graph_id AND n.id = e.${sql.raw(params.targetField)} AND n.kind = e.${sql.raw(params.targetKindField)}`;
|
|
3705
|
+
if (params.forceWorktableOuterJoinOrder) {
|
|
3706
|
+
const allWhere = [
|
|
3707
|
+
...params.whereClauses,
|
|
3708
|
+
sql`e.${sql.raw(params.joinField)} = r.id`
|
|
3709
|
+
];
|
|
3710
|
+
return sql`${selectClause} FROM reachable r CROSS JOIN ${params.schema.edgesTable} e ${nodeJoin} WHERE ${sql.join(allWhere, sql` AND `)}`;
|
|
3711
|
+
}
|
|
3712
|
+
const where = [...params.whereClauses];
|
|
3713
|
+
return sql`${selectClause} FROM reachable r JOIN ${params.schema.edgesTable} e ON e.${sql.raw(params.joinField)} = r.id ${nodeJoin} WHERE ${sql.join(where, sql` AND `)}`;
|
|
3714
|
+
}
|
|
3715
|
+
function compileBidirectionalBranch(params) {
|
|
3716
|
+
const columns = [...params.recursiveColumns];
|
|
3717
|
+
const selectClause = sql`SELECT ${sql.join(columns, sql`, `)}`;
|
|
3718
|
+
const nodeJoin = sql`JOIN ${params.schema.nodesTable} n ON n.graph_id = e.graph_id AND ((e.to_id = r.id AND n.id = e.from_id AND n.kind = e.from_kind) OR (e.from_id = r.id AND n.id = e.to_id AND n.kind = e.to_kind))`;
|
|
3719
|
+
if (params.forceWorktableOuterJoinOrder) {
|
|
3720
|
+
const allWhere = [
|
|
3721
|
+
...params.whereClauses,
|
|
3722
|
+
sql`(e.from_id = r.id OR e.to_id = r.id)`
|
|
3723
|
+
];
|
|
3724
|
+
return sql`${selectClause} FROM reachable r CROSS JOIN ${params.schema.edgesTable} e ${nodeJoin} WHERE ${sql.join(allWhere, sql` AND `)}`;
|
|
3725
|
+
}
|
|
3726
|
+
return sql`${selectClause} FROM reachable r JOIN ${params.schema.edgesTable} e ON (e.from_id = r.id OR e.to_id = r.id) ${nodeJoin} WHERE ${sql.join([...params.whereClauses], sql` AND `)}`;
|
|
3727
|
+
}
|
|
3728
|
+
function buildIncludedIdsCte(ctx) {
|
|
3729
|
+
const filters = [];
|
|
3730
|
+
if (ctx.includeKinds !== void 0 && ctx.includeKinds.length > 0) {
|
|
3731
|
+
filters.push(compileKindFilter(sql.raw("kind"), ctx.includeKinds));
|
|
3233
3732
|
}
|
|
3234
|
-
if (
|
|
3235
|
-
|
|
3733
|
+
if (ctx.excludeRoot) {
|
|
3734
|
+
filters.push(sql`id != ${ctx.rootId}`);
|
|
3236
3735
|
}
|
|
3237
|
-
|
|
3736
|
+
const whereClause = filters.length > 0 ? sql` WHERE ${sql.join(filters, sql` AND `)}` : sql``;
|
|
3737
|
+
return sql`, included_ids AS (SELECT DISTINCT id FROM reachable${whereClause})`;
|
|
3238
3738
|
}
|
|
3239
|
-
function
|
|
3240
|
-
const
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3739
|
+
async function fetchSubgraphNodes(ctx, reachableCte, includedIdsCte, projectionPlan) {
|
|
3740
|
+
const columns = [
|
|
3741
|
+
sql`n.kind`,
|
|
3742
|
+
sql`n.id`,
|
|
3743
|
+
buildFullPropsColumn("n", projectionPlan),
|
|
3744
|
+
...buildMetadataColumns("n", projectionPlan, [
|
|
3745
|
+
"version",
|
|
3746
|
+
"valid_from",
|
|
3747
|
+
"valid_to",
|
|
3748
|
+
"created_at",
|
|
3749
|
+
"updated_at",
|
|
3750
|
+
"deleted_at"
|
|
3751
|
+
]),
|
|
3752
|
+
...buildProjectedPropertyColumns("n", projectionPlan, ctx.dialect)
|
|
3753
|
+
];
|
|
3754
|
+
const query = sql`${reachableCte}${includedIdsCte} SELECT ${sql.join(columns, sql`, `)} FROM ${ctx.schema.nodesTable} n WHERE n.graph_id = ${ctx.graphId} AND n.id IN (SELECT id FROM included_ids)`;
|
|
3755
|
+
return ctx.backend.execute(query);
|
|
3756
|
+
}
|
|
3757
|
+
async function fetchSubgraphEdges(ctx, reachableCte, includedIdsCte, projectionPlan) {
|
|
3758
|
+
const edgeKindFilter = compileKindFilter(sql.raw("e.kind"), ctx.edgeKinds);
|
|
3759
|
+
const columns = [
|
|
3760
|
+
sql`e.id`,
|
|
3761
|
+
sql`e.kind`,
|
|
3762
|
+
sql`e.from_kind`,
|
|
3763
|
+
sql`e.from_id`,
|
|
3764
|
+
sql`e.to_kind`,
|
|
3765
|
+
sql`e.to_id`,
|
|
3766
|
+
buildFullPropsColumn("e", projectionPlan),
|
|
3767
|
+
...buildMetadataColumns("e", projectionPlan, [
|
|
3768
|
+
"valid_from",
|
|
3769
|
+
"valid_to",
|
|
3770
|
+
"created_at",
|
|
3771
|
+
"updated_at",
|
|
3772
|
+
"deleted_at"
|
|
3773
|
+
]),
|
|
3774
|
+
...buildProjectedPropertyColumns("e", projectionPlan, ctx.dialect)
|
|
3775
|
+
];
|
|
3776
|
+
const query = sql`${reachableCte}${includedIdsCte} SELECT ${sql.join(columns, sql`, `)} FROM ${ctx.schema.edgesTable} e WHERE e.graph_id = ${ctx.graphId} AND ${edgeKindFilter} AND e.deleted_at IS NULL AND e.from_id IN (SELECT id FROM included_ids) AND e.to_id IN (SELECT id FROM included_ids)`;
|
|
3777
|
+
return ctx.backend.execute(query);
|
|
3778
|
+
}
|
|
3779
|
+
function buildMetadataColumns(alias, plan, columns) {
|
|
3780
|
+
if (plan.projectedKinds.size === 0) {
|
|
3781
|
+
return columns.map((col) => sql`${sql.raw(`${alias}.${col}`)}`);
|
|
3263
3782
|
}
|
|
3264
|
-
|
|
3265
|
-
|
|
3783
|
+
const metaKinds = [...plan.fullKinds];
|
|
3784
|
+
for (const [kind, kindPlan] of plan.projectedKinds) {
|
|
3785
|
+
if (kindPlan.includeMeta) metaKinds.push(kind);
|
|
3266
3786
|
}
|
|
3267
|
-
|
|
3268
|
-
}
|
|
3269
|
-
function compileRecursiveOrderBy(ast, dialect) {
|
|
3270
|
-
if (!ast.orderBy || ast.orderBy.length === 0) {
|
|
3271
|
-
return void 0;
|
|
3787
|
+
if (metaKinds.length === 0) {
|
|
3788
|
+
return columns.map((col) => sql`NULL AS ${sql.raw(col)}`);
|
|
3272
3789
|
}
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
const valueType = orderSpec.field.valueType;
|
|
3276
|
-
if (valueType === "array" || valueType === "object") {
|
|
3277
|
-
throw new UnsupportedPredicateError(
|
|
3278
|
-
"Ordering by JSON arrays or objects is not supported"
|
|
3279
|
-
);
|
|
3280
|
-
}
|
|
3281
|
-
const field2 = compileFieldValue(orderSpec.field, dialect, valueType);
|
|
3282
|
-
const direction = sql.raw(orderSpec.direction.toUpperCase());
|
|
3283
|
-
const nulls = orderSpec.nulls ?? (orderSpec.direction === "asc" ? "last" : "first");
|
|
3284
|
-
const nullsDirection = sql.raw(nulls === "first" ? "DESC" : "ASC");
|
|
3285
|
-
parts.push(
|
|
3286
|
-
sql`(${field2} IS NULL) ${nullsDirection}`,
|
|
3287
|
-
sql`${field2} ${direction}`
|
|
3288
|
-
);
|
|
3790
|
+
if (metaKinds.length === plan.fullKinds.length + plan.projectedKinds.size) {
|
|
3791
|
+
return columns.map((col) => sql`${sql.raw(`${alias}.${col}`)}`);
|
|
3289
3792
|
}
|
|
3290
|
-
|
|
3793
|
+
const filter = compileKindFilter(sql.raw(`${alias}.kind`), metaKinds);
|
|
3794
|
+
return columns.map(
|
|
3795
|
+
(col) => sql`CASE WHEN ${filter} THEN ${sql.raw(`${alias}.${col}`)} ELSE NULL END AS ${sql.raw(col)}`
|
|
3796
|
+
);
|
|
3291
3797
|
}
|
|
3292
|
-
function
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
parts.push(sql`LIMIT ${ast.limit}`);
|
|
3798
|
+
function buildFullPropsColumn(alias, plan) {
|
|
3799
|
+
if (plan.projectedKinds.size === 0) {
|
|
3800
|
+
return sql`${sql.raw(`${alias}.props`)} AS props`;
|
|
3296
3801
|
}
|
|
3297
|
-
if (
|
|
3298
|
-
|
|
3802
|
+
if (plan.fullKinds.length === 0) {
|
|
3803
|
+
return sql`NULL AS props`;
|
|
3299
3804
|
}
|
|
3300
|
-
|
|
3805
|
+
const filter = compileKindFilter(sql.raw(`${alias}.kind`), plan.fullKinds);
|
|
3806
|
+
return sql`CASE WHEN ${filter} THEN ${sql.raw(`${alias}.props`)} ELSE NULL END AS props`;
|
|
3301
3807
|
}
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
);
|
|
3808
|
+
function buildProjectedPropertyColumns(alias, plan, dialect) {
|
|
3809
|
+
const columns = [];
|
|
3810
|
+
for (const [kind, kindPlan] of plan.projectedKinds.entries()) {
|
|
3811
|
+
for (const fieldPlan of kindPlan.propertyFields) {
|
|
3812
|
+
const extracted = compileTypedJsonExtract({
|
|
3813
|
+
column: sql.raw(`${alias}.props`),
|
|
3814
|
+
dialect,
|
|
3815
|
+
pointer: jsonPointer([fieldPlan.field]),
|
|
3816
|
+
valueType: fieldPlan.typeInfo?.valueType
|
|
3817
|
+
});
|
|
3818
|
+
columns.push(
|
|
3819
|
+
sql`CASE WHEN ${sql.raw(alias)}.kind = ${kind} THEN ${extracted} ELSE NULL END AS ${quoteIdentifier(fieldPlan.outputName)}`
|
|
3820
|
+
);
|
|
3821
|
+
}
|
|
3317
3822
|
}
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3823
|
+
return columns;
|
|
3824
|
+
}
|
|
3825
|
+
function applyProjectedFields(target, row, kindPlan) {
|
|
3826
|
+
for (const fieldPlan of kindPlan.propertyFields) {
|
|
3827
|
+
target[fieldPlan.field] = decodeSelectedValue(
|
|
3828
|
+
row[fieldPlan.outputName],
|
|
3829
|
+
fieldPlan.typeInfo
|
|
3321
3830
|
);
|
|
3322
3831
|
}
|
|
3323
3832
|
}
|
|
3324
|
-
function
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3833
|
+
function mapSubgraphNodeRow(row, projectionPlan) {
|
|
3834
|
+
const kindPlan = projectionPlan.projectedKinds.get(row.kind);
|
|
3835
|
+
if (kindPlan === void 0) {
|
|
3836
|
+
return rowToNode({
|
|
3837
|
+
...row,
|
|
3838
|
+
props: normalizeProps(row.props)
|
|
3839
|
+
});
|
|
3840
|
+
}
|
|
3841
|
+
const projectedNode = {
|
|
3842
|
+
kind: row.kind,
|
|
3843
|
+
id: row.id
|
|
3844
|
+
};
|
|
3845
|
+
if (kindPlan.includeMeta) {
|
|
3846
|
+
projectedNode.meta = rowToNodeMeta(row);
|
|
3847
|
+
}
|
|
3848
|
+
applyProjectedFields(projectedNode, row, kindPlan);
|
|
3849
|
+
return projectedNode;
|
|
3850
|
+
}
|
|
3851
|
+
function mapSubgraphEdgeRow(row, projectionPlan) {
|
|
3852
|
+
const kindPlan = projectionPlan.projectedKinds.get(row.kind);
|
|
3853
|
+
if (kindPlan === void 0) {
|
|
3854
|
+
return rowToEdge({
|
|
3855
|
+
...row,
|
|
3856
|
+
props: normalizeProps(row.props)
|
|
3857
|
+
});
|
|
3858
|
+
}
|
|
3859
|
+
const projectedEdge = {
|
|
3860
|
+
id: row.id,
|
|
3861
|
+
kind: row.kind,
|
|
3862
|
+
fromKind: row.from_kind,
|
|
3863
|
+
fromId: row.from_id,
|
|
3864
|
+
toKind: row.to_kind,
|
|
3865
|
+
toId: row.to_id
|
|
3337
3866
|
};
|
|
3867
|
+
if (kindPlan.includeMeta) {
|
|
3868
|
+
projectedEdge.meta = rowToEdgeMeta(row);
|
|
3869
|
+
}
|
|
3870
|
+
applyProjectedFields(projectedEdge, row, kindPlan);
|
|
3871
|
+
return projectedEdge;
|
|
3338
3872
|
}
|
|
3339
|
-
var DEFAULT_SQL_SCHEMA = createSqlSchema();
|
|
3340
3873
|
var OPERATOR_MAP = {
|
|
3341
3874
|
union: "UNION",
|
|
3342
3875
|
unionAll: "UNION ALL",
|
|
@@ -4705,9 +5238,9 @@ function transformPathColumns(rows, state, _dialect) {
|
|
|
4705
5238
|
}
|
|
4706
5239
|
return changed ? result : rows;
|
|
4707
5240
|
}
|
|
4708
|
-
var
|
|
4709
|
-
var
|
|
4710
|
-
function
|
|
5241
|
+
var RESERVED_NODE_KEYS3 = /* @__PURE__ */ new Set(["id", "kind", "meta"]);
|
|
5242
|
+
var RESERVED_EDGE_KEYS3 = /* @__PURE__ */ new Set(["id", "kind", "fromId", "toId", "meta"]);
|
|
5243
|
+
function nullToUndefined3(value) {
|
|
4711
5244
|
return value === null ? void 0 : value;
|
|
4712
5245
|
}
|
|
4713
5246
|
function assignPropsExcludingReserved(target, props, reservedKeys) {
|
|
@@ -4723,13 +5256,13 @@ function buildSelectableNode(row, alias) {
|
|
|
4723
5256
|
const propsRaw = row[`${alias}_props`];
|
|
4724
5257
|
const rawProps = typeof propsRaw === "string" ? JSON.parse(propsRaw) : propsRaw ?? {};
|
|
4725
5258
|
const version = row[`${alias}_version`];
|
|
4726
|
-
const validFrom =
|
|
5259
|
+
const validFrom = nullToUndefined3(
|
|
4727
5260
|
row[`${alias}_valid_from`]
|
|
4728
5261
|
);
|
|
4729
|
-
const validTo =
|
|
5262
|
+
const validTo = nullToUndefined3(row[`${alias}_valid_to`]);
|
|
4730
5263
|
const createdAt = row[`${alias}_created_at`];
|
|
4731
5264
|
const updatedAt = row[`${alias}_updated_at`];
|
|
4732
|
-
const deletedAt =
|
|
5265
|
+
const deletedAt = nullToUndefined3(
|
|
4733
5266
|
row[`${alias}_deleted_at`]
|
|
4734
5267
|
);
|
|
4735
5268
|
const result = {
|
|
@@ -4744,7 +5277,7 @@ function buildSelectableNode(row, alias) {
|
|
|
4744
5277
|
deletedAt
|
|
4745
5278
|
}
|
|
4746
5279
|
};
|
|
4747
|
-
assignPropsExcludingReserved(result, rawProps,
|
|
5280
|
+
assignPropsExcludingReserved(result, rawProps, RESERVED_NODE_KEYS3);
|
|
4748
5281
|
return result;
|
|
4749
5282
|
}
|
|
4750
5283
|
function buildSelectableNodeOrUndefined(row, alias) {
|
|
@@ -4764,13 +5297,13 @@ function buildSelectableEdge(row, alias) {
|
|
|
4764
5297
|
const toId = row[`${alias}_to_id`];
|
|
4765
5298
|
const propsRaw = row[`${alias}_props`];
|
|
4766
5299
|
const rawProps = typeof propsRaw === "string" ? JSON.parse(propsRaw) : propsRaw ?? {};
|
|
4767
|
-
const validFrom =
|
|
5300
|
+
const validFrom = nullToUndefined3(
|
|
4768
5301
|
row[`${alias}_valid_from`]
|
|
4769
5302
|
);
|
|
4770
|
-
const validTo =
|
|
5303
|
+
const validTo = nullToUndefined3(row[`${alias}_valid_to`]);
|
|
4771
5304
|
const createdAt = row[`${alias}_created_at`];
|
|
4772
5305
|
const updatedAt = row[`${alias}_updated_at`];
|
|
4773
|
-
const deletedAt =
|
|
5306
|
+
const deletedAt = nullToUndefined3(
|
|
4774
5307
|
row[`${alias}_deleted_at`]
|
|
4775
5308
|
);
|
|
4776
5309
|
const result = {
|
|
@@ -4786,7 +5319,7 @@ function buildSelectableEdge(row, alias) {
|
|
|
4786
5319
|
deletedAt
|
|
4787
5320
|
}
|
|
4788
5321
|
};
|
|
4789
|
-
assignPropsExcludingReserved(result, rawProps,
|
|
5322
|
+
assignPropsExcludingReserved(result, rawProps, RESERVED_EDGE_KEYS3);
|
|
4790
5323
|
return result;
|
|
4791
5324
|
}
|
|
4792
5325
|
function buildSelectContext(row, startAlias, traversals) {
|
|
@@ -5172,63 +5705,6 @@ function getPlaceholderForValueType(valueType, mode) {
|
|
|
5172
5705
|
}
|
|
5173
5706
|
}
|
|
5174
5707
|
|
|
5175
|
-
// src/query/execution/value-decoder.ts
|
|
5176
|
-
function nullToUndefined2(value) {
|
|
5177
|
-
return value === null ? void 0 : value;
|
|
5178
|
-
}
|
|
5179
|
-
function decodeSelectedValue(value, typeInfo) {
|
|
5180
|
-
const normalized = nullToUndefined2(value);
|
|
5181
|
-
if (normalized === void 0) return void 0;
|
|
5182
|
-
if (typeInfo === void 0) {
|
|
5183
|
-
return normalized;
|
|
5184
|
-
}
|
|
5185
|
-
return decodeByValueType(normalized, typeInfo.valueType);
|
|
5186
|
-
}
|
|
5187
|
-
function decodeByValueType(value, valueType) {
|
|
5188
|
-
switch (valueType) {
|
|
5189
|
-
case "boolean": {
|
|
5190
|
-
if (typeof value === "boolean") return value;
|
|
5191
|
-
if (typeof value === "number") return value !== 0;
|
|
5192
|
-
if (typeof value === "string") {
|
|
5193
|
-
if (value === "0") return false;
|
|
5194
|
-
if (value === "1") return true;
|
|
5195
|
-
if (value.toLowerCase() === "true") return true;
|
|
5196
|
-
if (value.toLowerCase() === "false") return false;
|
|
5197
|
-
}
|
|
5198
|
-
return Boolean(value);
|
|
5199
|
-
}
|
|
5200
|
-
case "number": {
|
|
5201
|
-
if (typeof value === "number") return value;
|
|
5202
|
-
if (typeof value === "string") {
|
|
5203
|
-
const parsed = Number(value);
|
|
5204
|
-
return Number.isNaN(parsed) ? value : parsed;
|
|
5205
|
-
}
|
|
5206
|
-
return value;
|
|
5207
|
-
}
|
|
5208
|
-
case "array":
|
|
5209
|
-
case "object":
|
|
5210
|
-
case "embedding": {
|
|
5211
|
-
if (typeof value !== "string") return value;
|
|
5212
|
-
const trimmed = value.trim();
|
|
5213
|
-
const looksJson = trimmed.startsWith("[") || trimmed.startsWith("{");
|
|
5214
|
-
if (!looksJson) return value;
|
|
5215
|
-
try {
|
|
5216
|
-
return JSON.parse(trimmed);
|
|
5217
|
-
} catch {
|
|
5218
|
-
return value;
|
|
5219
|
-
}
|
|
5220
|
-
}
|
|
5221
|
-
case "string":
|
|
5222
|
-
case "date":
|
|
5223
|
-
case "unknown": {
|
|
5224
|
-
return value;
|
|
5225
|
-
}
|
|
5226
|
-
default: {
|
|
5227
|
-
return value;
|
|
5228
|
-
}
|
|
5229
|
-
}
|
|
5230
|
-
}
|
|
5231
|
-
|
|
5232
5708
|
// src/query/execution/selective-result-mapper.ts
|
|
5233
5709
|
var MissingSelectiveFieldError = class extends Error {
|
|
5234
5710
|
alias;
|
|
@@ -5397,12 +5873,12 @@ function buildRequiredAliasValue(row, plan) {
|
|
|
5397
5873
|
}
|
|
5398
5874
|
};
|
|
5399
5875
|
for (const field2 of plan.systemFields) {
|
|
5400
|
-
base[field2.field] =
|
|
5876
|
+
base[field2.field] = nullToUndefined(row[field2.outputName]);
|
|
5401
5877
|
}
|
|
5402
5878
|
if (plan.metaFields.length > 0) {
|
|
5403
5879
|
const meta = {};
|
|
5404
5880
|
for (const field2 of plan.metaFields) {
|
|
5405
|
-
meta[field2.metaKey] =
|
|
5881
|
+
meta[field2.metaKey] = nullToUndefined(row[field2.outputName]);
|
|
5406
5882
|
}
|
|
5407
5883
|
base.meta = createGuardedProxy(meta, `${plan.alias}.meta`);
|
|
5408
5884
|
}
|
|
@@ -6369,24 +6845,102 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6369
6845
|
if (optimizedResult !== void 0) {
|
|
6370
6846
|
return optimizedResult;
|
|
6371
6847
|
}
|
|
6372
|
-
const compiled = this.compile();
|
|
6373
|
-
const rawRows = await this.#config.backend.execute(compiled);
|
|
6374
|
-
this.#config.dialect ?? "sqlite";
|
|
6375
|
-
const rows = transformPathColumns(rawRows, this.#state);
|
|
6376
|
-
return mapResults(
|
|
6377
|
-
rows,
|
|
6378
|
-
this.#state.startAlias,
|
|
6379
|
-
this.#state.traversals,
|
|
6380
|
-
this.#selectFn
|
|
6381
|
-
);
|
|
6848
|
+
const compiled = this.compile();
|
|
6849
|
+
const rawRows = await this.#config.backend.execute(compiled);
|
|
6850
|
+
this.#config.dialect ?? "sqlite";
|
|
6851
|
+
const rows = transformPathColumns(rawRows, this.#state);
|
|
6852
|
+
return mapResults(
|
|
6853
|
+
rows,
|
|
6854
|
+
this.#state.startAlias,
|
|
6855
|
+
this.#state.traversals,
|
|
6856
|
+
this.#selectFn
|
|
6857
|
+
);
|
|
6858
|
+
}
|
|
6859
|
+
/**
|
|
6860
|
+
* Executes the query against a provided backend.
|
|
6861
|
+
*
|
|
6862
|
+
* Used by `store.batch()` to run multiple queries over a single connection
|
|
6863
|
+
* (e.g., within a transaction). The full compile → execute → transform
|
|
6864
|
+
* pipeline runs identically to `execute()`, but against the given backend.
|
|
6865
|
+
*/
|
|
6866
|
+
async executeOn(backend) {
|
|
6867
|
+
if (hasParameterReferences(this.toAst())) {
|
|
6868
|
+
throw new Error(
|
|
6869
|
+
"Query contains param() references. Use .prepare().execute({...}) instead of .execute()."
|
|
6870
|
+
);
|
|
6871
|
+
}
|
|
6872
|
+
const optimizedResult = await this.#tryOptimizedExecutionOn(backend);
|
|
6873
|
+
if (optimizedResult !== void 0) {
|
|
6874
|
+
return optimizedResult;
|
|
6875
|
+
}
|
|
6876
|
+
const compiled = this.compile();
|
|
6877
|
+
const rawRows = await backend.execute(compiled);
|
|
6878
|
+
this.#config.dialect ?? "sqlite";
|
|
6879
|
+
const rows = transformPathColumns(rawRows, this.#state);
|
|
6880
|
+
return mapResults(
|
|
6881
|
+
rows,
|
|
6882
|
+
this.#state.startAlias,
|
|
6883
|
+
this.#state.traversals,
|
|
6884
|
+
this.#selectFn
|
|
6885
|
+
);
|
|
6886
|
+
}
|
|
6887
|
+
/**
|
|
6888
|
+
* Attempts optimized execution by tracking which fields the select callback accesses.
|
|
6889
|
+
*
|
|
6890
|
+
* Returns undefined if optimization is not possible (callback uses method calls,
|
|
6891
|
+
* computations, or returns whole nodes).
|
|
6892
|
+
*/
|
|
6893
|
+
async #tryOptimizedExecution() {
|
|
6894
|
+
const selectiveFields = this.#getSelectiveFieldsForExecute();
|
|
6895
|
+
if (selectiveFields === void 0) {
|
|
6896
|
+
return void 0;
|
|
6897
|
+
}
|
|
6898
|
+
let compiled;
|
|
6899
|
+
if (this.#cachedOptimizedCompiled === NOT_COMPUTED) {
|
|
6900
|
+
const baseAst = buildQueryAst(this.#config, this.#state);
|
|
6901
|
+
const selectiveAst = {
|
|
6902
|
+
...baseAst,
|
|
6903
|
+
selectiveFields
|
|
6904
|
+
};
|
|
6905
|
+
compiled = compileQuery(
|
|
6906
|
+
selectiveAst,
|
|
6907
|
+
this.#config.graphId,
|
|
6908
|
+
this.#compileOptions()
|
|
6909
|
+
);
|
|
6910
|
+
this.#cachedOptimizedCompiled = compiled;
|
|
6911
|
+
} else {
|
|
6912
|
+
compiled = this.#cachedOptimizedCompiled;
|
|
6913
|
+
}
|
|
6914
|
+
const rawSelectiveRows = await this.#requireBackend().execute(compiled);
|
|
6915
|
+
this.#config.dialect ?? "sqlite";
|
|
6916
|
+
const rows = transformPathColumns(rawSelectiveRows, this.#state);
|
|
6917
|
+
try {
|
|
6918
|
+
return mapSelectiveResults(
|
|
6919
|
+
rows,
|
|
6920
|
+
this.#state,
|
|
6921
|
+
selectiveFields,
|
|
6922
|
+
this.#config.schemaIntrospector,
|
|
6923
|
+
this.#selectFn
|
|
6924
|
+
);
|
|
6925
|
+
} catch (error) {
|
|
6926
|
+
if (error instanceof MissingSelectiveFieldError) {
|
|
6927
|
+
this.#cachedSelectiveFieldsForExecute = void 0;
|
|
6928
|
+
this.#cachedOptimizedCompiled = NOT_COMPUTED;
|
|
6929
|
+
return void 0;
|
|
6930
|
+
}
|
|
6931
|
+
if (error instanceof UnsupportedPredicateError) {
|
|
6932
|
+
this.#cachedSelectiveFieldsForExecute = void 0;
|
|
6933
|
+
this.#cachedOptimizedCompiled = NOT_COMPUTED;
|
|
6934
|
+
return void 0;
|
|
6935
|
+
}
|
|
6936
|
+
throw error;
|
|
6937
|
+
}
|
|
6382
6938
|
}
|
|
6383
6939
|
/**
|
|
6384
|
-
* Attempts optimized execution
|
|
6385
|
-
*
|
|
6386
|
-
* Returns undefined if optimization is not possible (callback uses method calls,
|
|
6387
|
-
* computations, or returns whole nodes).
|
|
6940
|
+
* Attempts optimized execution against a provided backend.
|
|
6941
|
+
* Mirror of #tryOptimizedExecution but delegates to the given backend.
|
|
6388
6942
|
*/
|
|
6389
|
-
async #
|
|
6943
|
+
async #tryOptimizedExecutionOn(backend) {
|
|
6390
6944
|
const selectiveFields = this.#getSelectiveFieldsForExecute();
|
|
6391
6945
|
if (selectiveFields === void 0) {
|
|
6392
6946
|
return void 0;
|
|
@@ -6407,7 +6961,7 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6407
6961
|
} else {
|
|
6408
6962
|
compiled = this.#cachedOptimizedCompiled;
|
|
6409
6963
|
}
|
|
6410
|
-
const rawSelectiveRows = await
|
|
6964
|
+
const rawSelectiveRows = await backend.execute(compiled);
|
|
6411
6965
|
this.#config.dialect ?? "sqlite";
|
|
6412
6966
|
const rows = transformPathColumns(rawSelectiveRows, this.#state);
|
|
6413
6967
|
try {
|
|
@@ -6682,7 +7236,7 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6682
7236
|
throw new MissingSelectiveFieldError(alias, fieldName);
|
|
6683
7237
|
}
|
|
6684
7238
|
const aliasObject2 = this.#getOrCreateAliasObject(cursorContext, alias);
|
|
6685
|
-
aliasObject2[fieldName] =
|
|
7239
|
+
aliasObject2[fieldName] = nullToUndefined(row[outputName2]);
|
|
6686
7240
|
continue;
|
|
6687
7241
|
}
|
|
6688
7242
|
const segments = parseJsonPointer(jsonPointer2);
|
|
@@ -8065,6 +8619,29 @@ var UnionableQuery = class _UnionableQuery {
|
|
|
8065
8619
|
}
|
|
8066
8620
|
return rows;
|
|
8067
8621
|
}
|
|
8622
|
+
/**
|
|
8623
|
+
* Executes the combined query against a provided backend.
|
|
8624
|
+
*
|
|
8625
|
+
* Used by `store.batch()` to run multiple queries over a single connection.
|
|
8626
|
+
*/
|
|
8627
|
+
async executeOn(backend) {
|
|
8628
|
+
if (composableQueryHasParameterReferences(this.toAst())) {
|
|
8629
|
+
throw new Error(
|
|
8630
|
+
"Query contains param() references. Use .prepare().execute({...}) instead of .execute()."
|
|
8631
|
+
);
|
|
8632
|
+
}
|
|
8633
|
+
const compiled = this.compile();
|
|
8634
|
+
const rows = await backend.execute(compiled);
|
|
8635
|
+
if (this.#state.selectFn && this.#state.startAlias) {
|
|
8636
|
+
return mapResults(
|
|
8637
|
+
rows,
|
|
8638
|
+
this.#state.startAlias,
|
|
8639
|
+
this.#state.traversals ?? [],
|
|
8640
|
+
this.#state.selectFn
|
|
8641
|
+
);
|
|
8642
|
+
}
|
|
8643
|
+
return rows;
|
|
8644
|
+
}
|
|
8068
8645
|
};
|
|
8069
8646
|
|
|
8070
8647
|
// src/query/builder/aggregates.ts
|
|
@@ -8342,6 +8919,35 @@ function createEdgeCollection(config) {
|
|
|
8342
8919
|
executeHardDelete: executeEdgeHardDelete2,
|
|
8343
8920
|
matchesTemporalMode
|
|
8344
8921
|
} = config;
|
|
8922
|
+
const mapRows = (rows) => rows.map((row) => narrowEdge(rowToEdge2(row)));
|
|
8923
|
+
async function findEdgesFrom(from, target) {
|
|
8924
|
+
const rows = await target.findEdgesByKind({
|
|
8925
|
+
graphId,
|
|
8926
|
+
kind,
|
|
8927
|
+
fromKind: from.kind,
|
|
8928
|
+
fromId: from.id,
|
|
8929
|
+
excludeDeleted: true
|
|
8930
|
+
});
|
|
8931
|
+
return mapRows(rows);
|
|
8932
|
+
}
|
|
8933
|
+
async function findEdgesTo(to, target) {
|
|
8934
|
+
const rows = await target.findEdgesByKind({
|
|
8935
|
+
graphId,
|
|
8936
|
+
kind,
|
|
8937
|
+
toKind: to.kind,
|
|
8938
|
+
toId: to.id,
|
|
8939
|
+
excludeDeleted: true
|
|
8940
|
+
});
|
|
8941
|
+
return mapRows(rows);
|
|
8942
|
+
}
|
|
8943
|
+
function buildFindByEndpointsOptions(options) {
|
|
8944
|
+
const result = {};
|
|
8945
|
+
if (options?.matchOn !== void 0)
|
|
8946
|
+
result.matchOn = options.matchOn;
|
|
8947
|
+
if (options?.props !== void 0)
|
|
8948
|
+
result.props = options.props;
|
|
8949
|
+
return result;
|
|
8950
|
+
}
|
|
8345
8951
|
return {
|
|
8346
8952
|
async create(from, to, props, options) {
|
|
8347
8953
|
const result = await executeEdgeCreate2(
|
|
@@ -8397,24 +9003,32 @@ function createEdgeCollection(config) {
|
|
|
8397
9003
|
return narrowEdge(result);
|
|
8398
9004
|
},
|
|
8399
9005
|
async findFrom(from) {
|
|
8400
|
-
|
|
8401
|
-
graphId,
|
|
8402
|
-
kind,
|
|
8403
|
-
fromKind: from.kind,
|
|
8404
|
-
fromId: from.id,
|
|
8405
|
-
excludeDeleted: true
|
|
8406
|
-
});
|
|
8407
|
-
return rows.map((row) => narrowEdge(rowToEdge2(row)));
|
|
9006
|
+
return findEdgesFrom(from, backend);
|
|
8408
9007
|
},
|
|
8409
9008
|
async findTo(to) {
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
}
|
|
8417
|
-
|
|
9009
|
+
return findEdgesTo(to, backend);
|
|
9010
|
+
},
|
|
9011
|
+
batchFindFrom(from) {
|
|
9012
|
+
return { executeOn: (target) => findEdgesFrom(from, target) };
|
|
9013
|
+
},
|
|
9014
|
+
batchFindTo(to) {
|
|
9015
|
+
return { executeOn: (target) => findEdgesTo(to, target) };
|
|
9016
|
+
},
|
|
9017
|
+
batchFindByEndpoints(from, to, options) {
|
|
9018
|
+
return {
|
|
9019
|
+
executeOn: async (target) => {
|
|
9020
|
+
const result = await config.executeFindByEndpoints(
|
|
9021
|
+
kind,
|
|
9022
|
+
from.kind,
|
|
9023
|
+
from.id,
|
|
9024
|
+
to.kind,
|
|
9025
|
+
to.id,
|
|
9026
|
+
target,
|
|
9027
|
+
buildFindByEndpointsOptions(options)
|
|
9028
|
+
);
|
|
9029
|
+
return result === void 0 ? [] : [narrowEdge(result)];
|
|
9030
|
+
}
|
|
9031
|
+
};
|
|
8418
9032
|
},
|
|
8419
9033
|
async delete(id) {
|
|
8420
9034
|
await executeEdgeDelete2(id, backend);
|
|
@@ -8448,7 +9062,7 @@ function createEdgeCollection(config) {
|
|
|
8448
9062
|
if (options?.limit !== void 0) params.limit = options.limit;
|
|
8449
9063
|
if (options?.offset !== void 0) params.offset = options.offset;
|
|
8450
9064
|
const rows = await backend.findEdgesByKind(params);
|
|
8451
|
-
return rows
|
|
9065
|
+
return mapRows(rows);
|
|
8452
9066
|
},
|
|
8453
9067
|
async count(options) {
|
|
8454
9068
|
const mode = options?.temporalMode ?? defaultTemporalMode;
|
|
@@ -8577,11 +9191,6 @@ function createEdgeCollection(config) {
|
|
|
8577
9191
|
await deleteAll(backend);
|
|
8578
9192
|
},
|
|
8579
9193
|
async findByEndpoints(from, to, options) {
|
|
8580
|
-
const findOptions = {};
|
|
8581
|
-
if (options?.matchOn !== void 0)
|
|
8582
|
-
findOptions.matchOn = options.matchOn;
|
|
8583
|
-
if (options?.props !== void 0)
|
|
8584
|
-
findOptions.props = options.props;
|
|
8585
9194
|
const result = await config.executeFindByEndpoints(
|
|
8586
9195
|
kind,
|
|
8587
9196
|
from.kind,
|
|
@@ -8589,7 +9198,7 @@ function createEdgeCollection(config) {
|
|
|
8589
9198
|
to.kind,
|
|
8590
9199
|
to.id,
|
|
8591
9200
|
backend,
|
|
8592
|
-
|
|
9201
|
+
buildFindByEndpointsOptions(options)
|
|
8593
9202
|
);
|
|
8594
9203
|
return result === void 0 ? void 0 : narrowEdge(result);
|
|
8595
9204
|
},
|
|
@@ -9285,67 +9894,6 @@ async function checkCardinalityConstraint(ctx, edgeKind, cardinality, fromKind,
|
|
|
9285
9894
|
}
|
|
9286
9895
|
}
|
|
9287
9896
|
|
|
9288
|
-
// src/store/row-mappers.ts
|
|
9289
|
-
var RESERVED_NODE_KEYS3 = /* @__PURE__ */ new Set(["id", "kind", "meta"]);
|
|
9290
|
-
function nullToUndefined3(value) {
|
|
9291
|
-
return value === null ? void 0 : value;
|
|
9292
|
-
}
|
|
9293
|
-
function filterReservedKeys(props, reservedKeys) {
|
|
9294
|
-
const filtered = {};
|
|
9295
|
-
for (const [key, value] of Object.entries(props)) {
|
|
9296
|
-
if (!reservedKeys.has(key)) {
|
|
9297
|
-
filtered[key] = value;
|
|
9298
|
-
}
|
|
9299
|
-
}
|
|
9300
|
-
return filtered;
|
|
9301
|
-
}
|
|
9302
|
-
function rowToNode(row) {
|
|
9303
|
-
const rawProps = JSON.parse(row.props);
|
|
9304
|
-
const props = filterReservedKeys(rawProps, RESERVED_NODE_KEYS3);
|
|
9305
|
-
return {
|
|
9306
|
-
kind: row.kind,
|
|
9307
|
-
id: row.id,
|
|
9308
|
-
meta: {
|
|
9309
|
-
version: row.version,
|
|
9310
|
-
validFrom: nullToUndefined3(row.valid_from),
|
|
9311
|
-
validTo: nullToUndefined3(row.valid_to),
|
|
9312
|
-
createdAt: row.created_at,
|
|
9313
|
-
updatedAt: row.updated_at,
|
|
9314
|
-
deletedAt: nullToUndefined3(row.deleted_at)
|
|
9315
|
-
},
|
|
9316
|
-
...props
|
|
9317
|
-
};
|
|
9318
|
-
}
|
|
9319
|
-
var RESERVED_EDGE_KEYS3 = /* @__PURE__ */ new Set([
|
|
9320
|
-
"id",
|
|
9321
|
-
"kind",
|
|
9322
|
-
"meta",
|
|
9323
|
-
"fromKind",
|
|
9324
|
-
"fromId",
|
|
9325
|
-
"toKind",
|
|
9326
|
-
"toId"
|
|
9327
|
-
]);
|
|
9328
|
-
function rowToEdge(row) {
|
|
9329
|
-
const rawProps = JSON.parse(row.props);
|
|
9330
|
-
const props = filterReservedKeys(rawProps, RESERVED_EDGE_KEYS3);
|
|
9331
|
-
return {
|
|
9332
|
-
id: row.id,
|
|
9333
|
-
kind: row.kind,
|
|
9334
|
-
fromKind: row.from_kind,
|
|
9335
|
-
fromId: row.from_id,
|
|
9336
|
-
toKind: row.to_kind,
|
|
9337
|
-
toId: row.to_id,
|
|
9338
|
-
meta: {
|
|
9339
|
-
validFrom: nullToUndefined3(row.valid_from),
|
|
9340
|
-
validTo: nullToUndefined3(row.valid_to),
|
|
9341
|
-
createdAt: row.created_at,
|
|
9342
|
-
updatedAt: row.updated_at,
|
|
9343
|
-
deletedAt: nullToUndefined3(row.deleted_at)
|
|
9344
|
-
},
|
|
9345
|
-
...props
|
|
9346
|
-
};
|
|
9347
|
-
}
|
|
9348
|
-
|
|
9349
9897
|
// src/store/operations/edge-operations.ts
|
|
9350
9898
|
function getEdgeRegistration(graph, kind) {
|
|
9351
9899
|
const registration = graph.edges[kind];
|
|
@@ -11219,142 +11767,6 @@ async function executeNodeBulkGetOrCreateByConstraint(ctx, kind, constraintName,
|
|
|
11219
11767
|
}
|
|
11220
11768
|
return results;
|
|
11221
11769
|
}
|
|
11222
|
-
var DEFAULT_SUBGRAPH_MAX_DEPTH = 10;
|
|
11223
|
-
function normalizeProps(value) {
|
|
11224
|
-
return typeof value === "string" ? value : JSON.stringify(value ?? {});
|
|
11225
|
-
}
|
|
11226
|
-
async function executeSubgraph(params) {
|
|
11227
|
-
const { options } = params;
|
|
11228
|
-
if (options.edges.length === 0) {
|
|
11229
|
-
return { nodes: [], edges: [] };
|
|
11230
|
-
}
|
|
11231
|
-
const maxDepth = Math.min(
|
|
11232
|
-
options.maxDepth ?? DEFAULT_SUBGRAPH_MAX_DEPTH,
|
|
11233
|
-
MAX_RECURSIVE_DEPTH
|
|
11234
|
-
);
|
|
11235
|
-
const ctx = {
|
|
11236
|
-
graphId: params.graphId,
|
|
11237
|
-
rootId: params.rootId,
|
|
11238
|
-
edgeKinds: options.edges,
|
|
11239
|
-
maxDepth,
|
|
11240
|
-
includeKinds: options.includeKinds,
|
|
11241
|
-
excludeRoot: options.excludeRoot ?? false,
|
|
11242
|
-
direction: options.direction ?? "out",
|
|
11243
|
-
cyclePolicy: options.cyclePolicy ?? "prevent",
|
|
11244
|
-
dialect: params.dialect,
|
|
11245
|
-
schema: params.schema ?? DEFAULT_SQL_SCHEMA,
|
|
11246
|
-
backend: params.backend
|
|
11247
|
-
};
|
|
11248
|
-
const reachableCte = buildReachableCte(ctx);
|
|
11249
|
-
const includedIdsCte = buildIncludedIdsCte(ctx);
|
|
11250
|
-
const [nodeRows, edgeRows] = await Promise.all([
|
|
11251
|
-
fetchSubgraphNodes(ctx, reachableCte, includedIdsCte),
|
|
11252
|
-
fetchSubgraphEdges(ctx, reachableCte, includedIdsCte)
|
|
11253
|
-
]);
|
|
11254
|
-
const nodes = nodeRows.map(
|
|
11255
|
-
(row) => rowToNode({ ...row, props: normalizeProps(row.props) })
|
|
11256
|
-
);
|
|
11257
|
-
const edges = edgeRows.map(
|
|
11258
|
-
(row) => rowToEdge({ ...row, props: normalizeProps(row.props) })
|
|
11259
|
-
);
|
|
11260
|
-
return {
|
|
11261
|
-
nodes,
|
|
11262
|
-
edges
|
|
11263
|
-
};
|
|
11264
|
-
}
|
|
11265
|
-
function buildReachableCte(ctx) {
|
|
11266
|
-
const shouldTrackPath = ctx.cyclePolicy === "prevent";
|
|
11267
|
-
const edgeKindFilter = compileKindFilter(sql.raw("e.kind"), ctx.edgeKinds);
|
|
11268
|
-
const initialPath = shouldTrackPath ? ctx.dialect.initializePath(sql.raw("n.id")) : void 0;
|
|
11269
|
-
const pathExtension = shouldTrackPath ? ctx.dialect.extendPath(sql.raw("r.path"), sql.raw("n.id")) : void 0;
|
|
11270
|
-
const cycleCheck = shouldTrackPath ? ctx.dialect.cycleCheck(sql.raw("n.id"), sql.raw("r.path")) : void 0;
|
|
11271
|
-
const baseColumns = [sql`n.id`, sql`n.kind`, sql`0 AS depth`];
|
|
11272
|
-
if (initialPath !== void 0) {
|
|
11273
|
-
baseColumns.push(sql`${initialPath} AS path`);
|
|
11274
|
-
}
|
|
11275
|
-
const baseCase = sql`SELECT ${sql.join(baseColumns, sql`, `)} FROM ${ctx.schema.nodesTable} n WHERE n.graph_id = ${ctx.graphId} AND n.id = ${ctx.rootId} AND n.deleted_at IS NULL`;
|
|
11276
|
-
const recursiveColumns = [
|
|
11277
|
-
sql`n.id`,
|
|
11278
|
-
sql`n.kind`,
|
|
11279
|
-
sql`r.depth + 1 AS depth`
|
|
11280
|
-
];
|
|
11281
|
-
if (pathExtension !== void 0) {
|
|
11282
|
-
recursiveColumns.push(sql`${pathExtension} AS path`);
|
|
11283
|
-
}
|
|
11284
|
-
const recursiveWhereClauses = [
|
|
11285
|
-
sql`e.graph_id = ${ctx.graphId}`,
|
|
11286
|
-
edgeKindFilter,
|
|
11287
|
-
sql`e.deleted_at IS NULL`,
|
|
11288
|
-
sql`n.deleted_at IS NULL`,
|
|
11289
|
-
sql`r.depth < ${ctx.maxDepth}`
|
|
11290
|
-
];
|
|
11291
|
-
if (cycleCheck !== void 0) {
|
|
11292
|
-
recursiveWhereClauses.push(cycleCheck);
|
|
11293
|
-
}
|
|
11294
|
-
const forceWorktableOuterJoinOrder = ctx.dialect.capabilities.forceRecursiveWorktableOuterJoinOrder;
|
|
11295
|
-
const recursiveCase = ctx.direction === "both" ? compileBidirectionalBranch({
|
|
11296
|
-
recursiveColumns,
|
|
11297
|
-
whereClauses: recursiveWhereClauses,
|
|
11298
|
-
forceWorktableOuterJoinOrder,
|
|
11299
|
-
schema: ctx.schema
|
|
11300
|
-
}) : compileRecursiveBranch({
|
|
11301
|
-
recursiveColumns,
|
|
11302
|
-
whereClauses: recursiveWhereClauses,
|
|
11303
|
-
joinField: "from_id",
|
|
11304
|
-
targetField: "to_id",
|
|
11305
|
-
targetKindField: "to_kind",
|
|
11306
|
-
forceWorktableOuterJoinOrder,
|
|
11307
|
-
schema: ctx.schema
|
|
11308
|
-
});
|
|
11309
|
-
return sql`WITH RECURSIVE reachable AS (${baseCase} UNION ALL ${recursiveCase})`;
|
|
11310
|
-
}
|
|
11311
|
-
function compileRecursiveBranch(params) {
|
|
11312
|
-
const columns = [...params.recursiveColumns];
|
|
11313
|
-
const selectClause = sql`SELECT ${sql.join(columns, sql`, `)}`;
|
|
11314
|
-
const nodeJoin = sql`JOIN ${params.schema.nodesTable} n ON n.graph_id = e.graph_id AND n.id = e.${sql.raw(params.targetField)} AND n.kind = e.${sql.raw(params.targetKindField)}`;
|
|
11315
|
-
if (params.forceWorktableOuterJoinOrder) {
|
|
11316
|
-
const allWhere = [
|
|
11317
|
-
...params.whereClauses,
|
|
11318
|
-
sql`e.${sql.raw(params.joinField)} = r.id`
|
|
11319
|
-
];
|
|
11320
|
-
return sql`${selectClause} FROM reachable r CROSS JOIN ${params.schema.edgesTable} e ${nodeJoin} WHERE ${sql.join(allWhere, sql` AND `)}`;
|
|
11321
|
-
}
|
|
11322
|
-
const where = [...params.whereClauses];
|
|
11323
|
-
return sql`${selectClause} FROM reachable r JOIN ${params.schema.edgesTable} e ON e.${sql.raw(params.joinField)} = r.id ${nodeJoin} WHERE ${sql.join(where, sql` AND `)}`;
|
|
11324
|
-
}
|
|
11325
|
-
function compileBidirectionalBranch(params) {
|
|
11326
|
-
const columns = [...params.recursiveColumns];
|
|
11327
|
-
const selectClause = sql`SELECT ${sql.join(columns, sql`, `)}`;
|
|
11328
|
-
const nodeJoin = sql`JOIN ${params.schema.nodesTable} n ON n.graph_id = e.graph_id AND ((e.to_id = r.id AND n.id = e.from_id AND n.kind = e.from_kind) OR (e.from_id = r.id AND n.id = e.to_id AND n.kind = e.to_kind))`;
|
|
11329
|
-
if (params.forceWorktableOuterJoinOrder) {
|
|
11330
|
-
const allWhere = [
|
|
11331
|
-
...params.whereClauses,
|
|
11332
|
-
sql`(e.from_id = r.id OR e.to_id = r.id)`
|
|
11333
|
-
];
|
|
11334
|
-
return sql`${selectClause} FROM reachable r CROSS JOIN ${params.schema.edgesTable} e ${nodeJoin} WHERE ${sql.join(allWhere, sql` AND `)}`;
|
|
11335
|
-
}
|
|
11336
|
-
return sql`${selectClause} FROM reachable r JOIN ${params.schema.edgesTable} e ON (e.from_id = r.id OR e.to_id = r.id) ${nodeJoin} WHERE ${sql.join([...params.whereClauses], sql` AND `)}`;
|
|
11337
|
-
}
|
|
11338
|
-
function buildIncludedIdsCte(ctx) {
|
|
11339
|
-
const filters = [];
|
|
11340
|
-
if (ctx.includeKinds !== void 0 && ctx.includeKinds.length > 0) {
|
|
11341
|
-
filters.push(compileKindFilter(sql.raw("kind"), ctx.includeKinds));
|
|
11342
|
-
}
|
|
11343
|
-
if (ctx.excludeRoot) {
|
|
11344
|
-
filters.push(sql`id != ${ctx.rootId}`);
|
|
11345
|
-
}
|
|
11346
|
-
const whereClause = filters.length > 0 ? sql` WHERE ${sql.join(filters, sql` AND `)}` : sql``;
|
|
11347
|
-
return sql`, included_ids AS (SELECT DISTINCT id FROM reachable${whereClause})`;
|
|
11348
|
-
}
|
|
11349
|
-
async function fetchSubgraphNodes(ctx, reachableCte, includedIdsCte) {
|
|
11350
|
-
const query = sql`${reachableCte}${includedIdsCte} SELECT n.kind, n.id, n.props, n.version, n.valid_from, n.valid_to, n.created_at, n.updated_at, n.deleted_at FROM ${ctx.schema.nodesTable} n WHERE n.graph_id = ${ctx.graphId} AND n.id IN (SELECT id FROM included_ids)`;
|
|
11351
|
-
return ctx.backend.execute(query);
|
|
11352
|
-
}
|
|
11353
|
-
async function fetchSubgraphEdges(ctx, reachableCte, includedIdsCte) {
|
|
11354
|
-
const edgeKindFilter = compileKindFilter(sql.raw("e.kind"), ctx.edgeKinds);
|
|
11355
|
-
const query = sql`${reachableCte}${includedIdsCte} SELECT e.id, e.kind, e.from_kind, e.from_id, e.to_kind, e.to_id, e.props, e.valid_from, e.valid_to, e.created_at, e.updated_at, e.deleted_at FROM ${ctx.schema.edgesTable} e WHERE e.graph_id = ${ctx.graphId} AND ${edgeKindFilter} AND e.deleted_at IS NULL AND e.from_id IN (SELECT id FROM included_ids) AND e.to_id IN (SELECT id FROM included_ids)`;
|
|
11356
|
-
return ctx.backend.execute(query);
|
|
11357
|
-
}
|
|
11358
11770
|
|
|
11359
11771
|
// src/store/store.ts
|
|
11360
11772
|
var Store = class {
|
|
@@ -11558,6 +11970,46 @@ var Store = class {
|
|
|
11558
11970
|
query() {
|
|
11559
11971
|
return this.#createQueryForBackend(this.#backend);
|
|
11560
11972
|
}
|
|
11973
|
+
// === Batch Query Execution ===
|
|
11974
|
+
/**
|
|
11975
|
+
* Executes multiple queries over a single connection with snapshot consistency.
|
|
11976
|
+
*
|
|
11977
|
+
* Acquires one connection via an implicit transaction, executes each query
|
|
11978
|
+
* sequentially on that connection, and returns a typed tuple of results.
|
|
11979
|
+
* Each query preserves its own result type, projection, filtering,
|
|
11980
|
+
* sorting, and pagination.
|
|
11981
|
+
*
|
|
11982
|
+
* Read-only — use `bulkCreate`, `bulkInsert`, etc. for write batching.
|
|
11983
|
+
*
|
|
11984
|
+
* @example
|
|
11985
|
+
* ```typescript
|
|
11986
|
+
* const [people, companies] = await store.batch(
|
|
11987
|
+
* store.query()
|
|
11988
|
+
* .from("Person", "p")
|
|
11989
|
+
* .select((ctx) => ({ id: ctx.p.id, name: ctx.p.name })),
|
|
11990
|
+
* store.query()
|
|
11991
|
+
* .from("Company", "c")
|
|
11992
|
+
* .select((ctx) => ({ id: ctx.c.id, name: ctx.c.name }))
|
|
11993
|
+
* .orderBy("c", "name", "asc")
|
|
11994
|
+
* .limit(5),
|
|
11995
|
+
* );
|
|
11996
|
+
* // people: readonly { id: string; name: string }[]
|
|
11997
|
+
* // companies: readonly { id: string; name: string }[]
|
|
11998
|
+
* ```
|
|
11999
|
+
*
|
|
12000
|
+
* @param queries - Two or more executable queries (from `.select()` or set operations)
|
|
12001
|
+
* @returns A tuple with per-query typed results, preserving input order
|
|
12002
|
+
*/
|
|
12003
|
+
async batch(...queries) {
|
|
12004
|
+
return this.#backend.transaction(async (txBackend) => {
|
|
12005
|
+
const results = [];
|
|
12006
|
+
for (const query of queries) {
|
|
12007
|
+
const result = await query.executeOn(txBackend);
|
|
12008
|
+
results.push(result);
|
|
12009
|
+
}
|
|
12010
|
+
return results;
|
|
12011
|
+
});
|
|
12012
|
+
}
|
|
11561
12013
|
// === Subgraph Extraction ===
|
|
11562
12014
|
/**
|
|
11563
12015
|
* Extracts a typed subgraph by traversing from a root node.
|
|
@@ -11583,6 +12035,7 @@ var Store = class {
|
|
|
11583
12035
|
*/
|
|
11584
12036
|
async subgraph(rootId, options) {
|
|
11585
12037
|
return executeSubgraph({
|
|
12038
|
+
graph: this.#graph,
|
|
11586
12039
|
graphId: this.graphId,
|
|
11587
12040
|
rootId,
|
|
11588
12041
|
backend: this.#backend,
|
|
@@ -11762,6 +12215,6 @@ async function createStoreWithSchema(graph, backend, options) {
|
|
|
11762
12215
|
return [store, result];
|
|
11763
12216
|
}
|
|
11764
12217
|
|
|
11765
|
-
export { DEFAULT_SQL_SCHEMA, ExecutableAggregateQuery, ExecutableQuery, MAX_EXPLICIT_RECURSIVE_DEPTH, MAX_RECURSIVE_DEPTH, PreparedQuery, QueryBuilder, UnionableQuery, avg, broader, composeFragments, core, count, countDistinct, createExternalRef, createFragment, createQueryBuilder, createSqlSchema, createStore, createStoreWithSchema, defineEdge, defineNode, differentFrom, disjointWith, equivalentTo, exists, externalRef, field, fieldRef, generateId, getExternalRefTable, hasPart, having, havingEq, havingGt, havingGte, havingLt, havingLte, implies, inSubquery, inverseOf, isExternalRefSchema, isParameterRef, limitFragment, max, metaEdge, min, narrower, notExists, notInSubquery, offsetFragment, orderByFragment, param, partOf, relatedTo, sameAs, subClassOf, sum };
|
|
12218
|
+
export { DEFAULT_SQL_SCHEMA, ExecutableAggregateQuery, ExecutableQuery, MAX_EXPLICIT_RECURSIVE_DEPTH, MAX_RECURSIVE_DEPTH, PreparedQuery, QueryBuilder, UnionableQuery, avg, broader, composeFragments, core, count, countDistinct, createExternalRef, createFragment, createQueryBuilder, createSqlSchema, createStore, createStoreWithSchema, defineEdge, defineNode, defineSubgraphProject, differentFrom, disjointWith, equivalentTo, exists, externalRef, field, fieldRef, generateId, getExternalRefTable, hasPart, having, havingEq, havingGt, havingGte, havingLt, havingLte, implies, inSubquery, inverseOf, isExternalRefSchema, isParameterRef, limitFragment, max, metaEdge, min, narrower, notExists, notInSubquery, offsetFragment, orderByFragment, param, partOf, relatedTo, sameAs, subClassOf, sum };
|
|
11766
12219
|
//# sourceMappingURL=index.js.map
|
|
11767
12220
|
//# sourceMappingURL=index.js.map
|