@nicia-ai/typegraph 0.11.0 → 0.12.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 +1914 -1459
- 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 +1914 -1460
- 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-nQ1ATBlN.d.ts → store-BcnA11lH.d.ts} +154 -8
- package/dist/{store-DyGdpDFr.d.cts → store-NEa4EFFD.d.cts} +154 -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") {
|
|
@@ -581,6 +515,10 @@ function baseFieldBuilder(field2) {
|
|
|
581
515
|
function stringField(field2) {
|
|
582
516
|
return {
|
|
583
517
|
...baseFieldBuilder(field2),
|
|
518
|
+
gt: (value) => comparison("gt", field2, value),
|
|
519
|
+
gte: (value) => comparison("gte", field2, value),
|
|
520
|
+
lt: (value) => comparison("lt", field2, value),
|
|
521
|
+
lte: (value) => comparison("lte", field2, value),
|
|
584
522
|
contains: (pattern) => stringOp("contains", field2, pattern),
|
|
585
523
|
startsWith: (pattern) => stringOp("startsWith", field2, pattern),
|
|
586
524
|
endsWith: (pattern) => stringOp("endsWith", field2, pattern),
|
|
@@ -1461,627 +1399,310 @@ function extractVectorSimilarityPredicates(predicates) {
|
|
|
1461
1399
|
return results;
|
|
1462
1400
|
}
|
|
1463
1401
|
|
|
1464
|
-
// src/query/compiler/
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
throw new UnsupportedPredicateError(
|
|
1479
|
-
`Vector similarity predicates are not supported for dialect "${dialect.name}"`
|
|
1480
|
-
);
|
|
1481
|
-
}
|
|
1482
|
-
if (!dialect.capabilities.vectorMetrics.includes(vectorPredicate.metric)) {
|
|
1483
|
-
throw new UnsupportedPredicateError(
|
|
1484
|
-
`Vector metric "${vectorPredicate.metric}" is not supported for dialect "${dialect.name}"`
|
|
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)
|
|
1485
1416
|
);
|
|
1417
|
+
const existing = byAliasAndType.get(key);
|
|
1418
|
+
if (existing === void 0) {
|
|
1419
|
+
byAliasAndType.set(key, [predicate2]);
|
|
1420
|
+
} else {
|
|
1421
|
+
existing.push(predicate2);
|
|
1422
|
+
}
|
|
1486
1423
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
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`;
|
|
1491
1439
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
if (!Number.isFinite(minScore)) {
|
|
1495
|
-
throw new UnsupportedPredicateError(
|
|
1496
|
-
`Vector minScore must be a finite number, got ${String(minScore)}`
|
|
1497
|
-
);
|
|
1498
|
-
}
|
|
1499
|
-
if (vectorPredicate.metric === "cosine" && (minScore < -1 || minScore > 1)) {
|
|
1500
|
-
throw new UnsupportedPredicateError(
|
|
1501
|
-
`Cosine minScore must be between -1 and 1, got ${String(minScore)}`
|
|
1502
|
-
);
|
|
1503
|
-
}
|
|
1440
|
+
if (kinds.length === 1) {
|
|
1441
|
+
return sql`${column} = ${kinds[0]}`;
|
|
1504
1442
|
}
|
|
1505
|
-
return {
|
|
1443
|
+
return sql`${column} IN (${sql.join(
|
|
1444
|
+
kinds.map((kind) => sql`${kind}`),
|
|
1445
|
+
sql`, `
|
|
1446
|
+
)})`;
|
|
1506
1447
|
}
|
|
1507
|
-
function
|
|
1508
|
-
if (
|
|
1509
|
-
return
|
|
1448
|
+
function getNodeKindsForAlias(ast, alias) {
|
|
1449
|
+
if (alias === ast.start.alias) {
|
|
1450
|
+
return ast.start.kinds;
|
|
1510
1451
|
}
|
|
1511
|
-
|
|
1512
|
-
|
|
1452
|
+
for (const traversal of ast.traversals) {
|
|
1453
|
+
if (traversal.nodeAlias === alias) {
|
|
1454
|
+
return traversal.nodeKinds;
|
|
1455
|
+
}
|
|
1513
1456
|
}
|
|
1514
|
-
|
|
1457
|
+
throw new CompilerInvariantError(`Unknown traversal source alias: ${alias}`);
|
|
1515
1458
|
}
|
|
1516
1459
|
|
|
1517
|
-
// src/query/compiler/plan
|
|
1518
|
-
function
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
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;
|
|
1474
|
+
}
|
|
1475
|
+
case "set_op": {
|
|
1476
|
+
collectPlanOperations(node.left, ops);
|
|
1477
|
+
collectPlanOperations(node.right, ops);
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
case "scan": {
|
|
1481
|
+
return;
|
|
1530
1482
|
}
|
|
1531
1483
|
}
|
|
1532
|
-
return aggregates;
|
|
1533
1484
|
}
|
|
1534
|
-
function
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
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
|
+
}
|
|
1508
|
+
}
|
|
1539
1509
|
}
|
|
1540
|
-
function
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
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
|
+
);
|
|
1544
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
|
+
);
|
|
1545
1527
|
return {
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
};
|
|
1565
|
-
node = ast.having === void 0 ? aggregateNode : { ...aggregateNode, having: ast.having };
|
|
1566
|
-
}
|
|
1567
|
-
if (ast.orderBy !== void 0 && ast.orderBy.length > 0) {
|
|
1568
|
-
node = {
|
|
1569
|
-
id: nextPlanNodeId(),
|
|
1570
|
-
input: node,
|
|
1571
|
-
op: "sort",
|
|
1572
|
-
orderBy: ast.orderBy
|
|
1573
|
-
};
|
|
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
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
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
|
+
);
|
|
1574
1546
|
}
|
|
1575
|
-
if (
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
};
|
|
1581
|
-
const hasLimit = limit !== void 0;
|
|
1582
|
-
const hasOffset = ast.offset !== void 0;
|
|
1583
|
-
if (hasLimit && hasOffset) {
|
|
1584
|
-
node = {
|
|
1585
|
-
...limitOffsetNodeBase,
|
|
1586
|
-
limit,
|
|
1587
|
-
offset: ast.offset
|
|
1588
|
-
};
|
|
1589
|
-
} else if (hasLimit) {
|
|
1590
|
-
node = { ...limitOffsetNodeBase, limit };
|
|
1591
|
-
} else if (hasOffset) {
|
|
1592
|
-
node = { ...limitOffsetNodeBase, offset: ast.offset };
|
|
1593
|
-
} else {
|
|
1594
|
-
throw new CompilerInvariantError(
|
|
1595
|
-
"limit_offset node requires limit or offset to be present"
|
|
1596
|
-
);
|
|
1597
|
-
}
|
|
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
|
+
);
|
|
1598
1552
|
}
|
|
1599
|
-
|
|
1600
|
-
fields: ast.projection.fields,
|
|
1601
|
-
id: nextPlanNodeId(),
|
|
1602
|
-
input: node,
|
|
1603
|
-
op: "project"
|
|
1604
|
-
};
|
|
1605
|
-
return collapsedTraversalCteAlias === void 0 ? projectNodeBase : {
|
|
1606
|
-
...projectNodeBase,
|
|
1607
|
-
collapsedTraversalAlias: collapsedTraversalCteAlias
|
|
1608
|
-
};
|
|
1553
|
+
return shape;
|
|
1609
1554
|
}
|
|
1610
|
-
function
|
|
1611
|
-
const
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
kinds: ast.start.kinds,
|
|
1617
|
-
op: "scan",
|
|
1618
|
-
source: "nodes"
|
|
1619
|
-
};
|
|
1620
|
-
currentNode = wrapWithAliasFilterNode(
|
|
1621
|
-
currentNode,
|
|
1622
|
-
ast,
|
|
1623
|
-
ast.start.alias,
|
|
1624
|
-
"node",
|
|
1625
|
-
nextPlanNodeId
|
|
1626
|
-
);
|
|
1627
|
-
for (const traversal of ast.traversals) {
|
|
1628
|
-
currentNode = {
|
|
1629
|
-
direction: traversal.direction,
|
|
1630
|
-
edgeAlias: traversal.edgeAlias,
|
|
1631
|
-
edgeKinds: traversal.edgeKinds,
|
|
1632
|
-
id: nextPlanNodeId(),
|
|
1633
|
-
input: currentNode,
|
|
1634
|
-
inverseEdgeKinds: traversal.inverseEdgeKinds ?? [],
|
|
1635
|
-
joinFromAlias: traversal.joinFromAlias,
|
|
1636
|
-
joinType: traversal.optional ? "left" : "inner",
|
|
1637
|
-
nodeAlias: traversal.nodeAlias,
|
|
1638
|
-
nodeKinds: traversal.nodeKinds,
|
|
1639
|
-
op: "join"
|
|
1640
|
-
};
|
|
1641
|
-
currentNode = wrapWithAliasFilterNode(
|
|
1642
|
-
currentNode,
|
|
1643
|
-
ast,
|
|
1644
|
-
traversal.edgeAlias,
|
|
1645
|
-
"edge",
|
|
1646
|
-
nextPlanNodeId
|
|
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" }
|
|
1647
1561
|
);
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
"
|
|
1653
|
-
nextPlanNodeId
|
|
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" }
|
|
1654
1567
|
);
|
|
1655
1568
|
}
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1569
|
+
return shape;
|
|
1570
|
+
}
|
|
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
|
+
}
|
|
1663
1593
|
}
|
|
1664
|
-
return appendAggregateSortLimitAndProjectNodes(
|
|
1665
|
-
currentNode,
|
|
1666
|
-
ast,
|
|
1667
|
-
nextPlanNodeId,
|
|
1668
|
-
input.effectiveLimit,
|
|
1669
|
-
input.collapsedTraversalCteAlias
|
|
1670
|
-
);
|
|
1671
1594
|
}
|
|
1672
|
-
function
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
inverseEdgeKinds: traversal.inverseEdgeKinds ?? [],
|
|
1696
|
-
nodeAlias: traversal.nodeAlias,
|
|
1697
|
-
nodeKinds: traversal.nodeKinds,
|
|
1698
|
-
op: "recursive_expand",
|
|
1699
|
-
traversal: traversal.variableLength
|
|
1700
|
-
};
|
|
1701
|
-
currentNode = wrapWithAliasFilterNode(
|
|
1702
|
-
currentNode,
|
|
1703
|
-
ast,
|
|
1704
|
-
traversal.edgeAlias,
|
|
1705
|
-
"edge",
|
|
1706
|
-
nextPlanNodeId
|
|
1707
|
-
);
|
|
1708
|
-
currentNode = wrapWithAliasFilterNode(
|
|
1709
|
-
currentNode,
|
|
1710
|
-
ast,
|
|
1711
|
-
traversal.nodeAlias,
|
|
1712
|
-
"node",
|
|
1713
|
-
nextPlanNodeId
|
|
1714
|
-
);
|
|
1715
|
-
return appendAggregateSortLimitAndProjectNodes(
|
|
1716
|
-
currentNode,
|
|
1717
|
-
ast,
|
|
1718
|
-
nextPlanNodeId,
|
|
1719
|
-
ast.limit
|
|
1720
|
-
);
|
|
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
|
+
}
|
|
1721
1618
|
}
|
|
1722
|
-
function
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
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" }
|
|
1729
1626
|
);
|
|
1730
1627
|
}
|
|
1731
|
-
const
|
|
1732
|
-
|
|
1733
|
-
);
|
|
1734
|
-
if (hasVariableLengthTraversal2) {
|
|
1735
|
-
return lowerRecursiveQueryToLogicalPlanNode({
|
|
1736
|
-
ast: query,
|
|
1737
|
-
graphId,
|
|
1738
|
-
nextPlanNodeId
|
|
1739
|
-
});
|
|
1740
|
-
}
|
|
1741
|
-
const vectorPredicate = runVectorPredicatePass(
|
|
1742
|
-
query,
|
|
1743
|
-
getDialect(dialect)
|
|
1744
|
-
).vectorPredicate;
|
|
1745
|
-
const effectiveLimit = resolveVectorAwareLimit(query.limit, vectorPredicate);
|
|
1746
|
-
const loweringInput = {
|
|
1747
|
-
ast: query,
|
|
1748
|
-
graphId,
|
|
1749
|
-
nextPlanNodeId,
|
|
1750
|
-
...effectiveLimit === void 0 ? {} : { effectiveLimit },
|
|
1751
|
-
...vectorPredicate === void 0 ? {} : { vectorPredicate }
|
|
1752
|
-
};
|
|
1753
|
-
return lowerStandardQueryToLogicalPlanNode(loweringInput);
|
|
1754
|
-
}
|
|
1755
|
-
function lowerSetOperationToLogicalPlanNode(op, graphId, dialect, nextPlanNodeId) {
|
|
1756
|
-
let currentNode = {
|
|
1757
|
-
id: nextPlanNodeId(),
|
|
1758
|
-
left: lowerComposableQueryToLogicalPlanNode(
|
|
1759
|
-
op.left,
|
|
1760
|
-
dialect,
|
|
1761
|
-
graphId,
|
|
1762
|
-
nextPlanNodeId
|
|
1763
|
-
),
|
|
1764
|
-
op: "set_op",
|
|
1765
|
-
operator: op.operator,
|
|
1766
|
-
right: lowerComposableQueryToLogicalPlanNode(
|
|
1767
|
-
op.right,
|
|
1768
|
-
dialect,
|
|
1769
|
-
graphId,
|
|
1770
|
-
nextPlanNodeId
|
|
1771
|
-
)
|
|
1772
|
-
};
|
|
1773
|
-
if (op.orderBy !== void 0 && op.orderBy.length > 0) {
|
|
1774
|
-
currentNode = {
|
|
1775
|
-
id: nextPlanNodeId(),
|
|
1776
|
-
input: currentNode,
|
|
1777
|
-
op: "sort",
|
|
1778
|
-
orderBy: op.orderBy
|
|
1779
|
-
};
|
|
1780
|
-
}
|
|
1781
|
-
if (op.limit !== void 0 || op.offset !== void 0) {
|
|
1782
|
-
const limitOffsetBase = {
|
|
1783
|
-
id: nextPlanNodeId(),
|
|
1784
|
-
input: currentNode,
|
|
1785
|
-
op: "limit_offset"
|
|
1786
|
-
};
|
|
1787
|
-
if (op.limit !== void 0 && op.offset !== void 0) {
|
|
1788
|
-
currentNode = { ...limitOffsetBase, limit: op.limit, offset: op.offset };
|
|
1789
|
-
} else if (op.limit === void 0) {
|
|
1790
|
-
currentNode = { ...limitOffsetBase, offset: op.offset };
|
|
1791
|
-
} else {
|
|
1792
|
-
currentNode = { ...limitOffsetBase, limit: op.limit };
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
return currentNode;
|
|
1796
|
-
}
|
|
1797
|
-
function lowerStandardQueryToLogicalPlan(input) {
|
|
1798
|
-
const nextPlanNodeId = createPlanNodeIdFactory();
|
|
1799
|
-
return {
|
|
1800
|
-
metadata: {
|
|
1801
|
-
dialect: input.dialect,
|
|
1802
|
-
graphId: input.graphId
|
|
1803
|
-
},
|
|
1804
|
-
root: lowerStandardQueryToLogicalPlanNode({
|
|
1805
|
-
...input,
|
|
1806
|
-
nextPlanNodeId
|
|
1807
|
-
})
|
|
1808
|
-
};
|
|
1809
|
-
}
|
|
1810
|
-
function lowerRecursiveQueryToLogicalPlan(input) {
|
|
1811
|
-
const nextPlanNodeId = createPlanNodeIdFactory();
|
|
1812
|
-
return {
|
|
1813
|
-
metadata: {
|
|
1814
|
-
dialect: input.dialect,
|
|
1815
|
-
graphId: input.graphId
|
|
1816
|
-
},
|
|
1817
|
-
root: lowerRecursiveQueryToLogicalPlanNode({
|
|
1818
|
-
...input,
|
|
1819
|
-
nextPlanNodeId
|
|
1820
|
-
})
|
|
1821
|
-
};
|
|
1822
|
-
}
|
|
1823
|
-
function lowerSetOperationToLogicalPlan(input) {
|
|
1824
|
-
const nextPlanNodeId = createPlanNodeIdFactory();
|
|
1628
|
+
const limitOffsetNode = findTopLevelLimitOffsetNode(logicalPlan.root);
|
|
1629
|
+
const sortNode = findTopLevelSortNode(logicalPlan.root);
|
|
1825
1630
|
return {
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
input.op,
|
|
1832
|
-
input.graphId,
|
|
1833
|
-
input.dialect,
|
|
1834
|
-
nextPlanNodeId
|
|
1835
|
-
)
|
|
1631
|
+
hasLimitOffset: limitOffsetNode !== void 0,
|
|
1632
|
+
hasSetOperation: true,
|
|
1633
|
+
hasSort: sortNode !== void 0,
|
|
1634
|
+
limitOffsetNode,
|
|
1635
|
+
sortNode
|
|
1836
1636
|
};
|
|
1837
1637
|
}
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
case "join":
|
|
1846
|
-
case "limit_offset":
|
|
1847
|
-
case "project":
|
|
1848
|
-
case "recursive_expand":
|
|
1849
|
-
case "sort":
|
|
1850
|
-
case "vector_knn": {
|
|
1851
|
-
collectPlanOperations(node.input, ops);
|
|
1852
|
-
return;
|
|
1853
|
-
}
|
|
1854
|
-
case "set_op": {
|
|
1855
|
-
collectPlanOperations(node.left, ops);
|
|
1856
|
-
collectPlanOperations(node.right, ops);
|
|
1857
|
-
return;
|
|
1858
|
-
}
|
|
1859
|
-
case "scan": {
|
|
1860
|
-
return;
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
}
|
|
1864
|
-
function findUnaryNodeInProjectChain(rootNode, op) {
|
|
1865
|
-
let currentNode = rootNode.input;
|
|
1866
|
-
for (; ; ) {
|
|
1867
|
-
if (currentNode.op === op) {
|
|
1868
|
-
return currentNode;
|
|
1869
|
-
}
|
|
1870
|
-
switch (currentNode.op) {
|
|
1871
|
-
case "aggregate":
|
|
1872
|
-
case "filter":
|
|
1873
|
-
case "join":
|
|
1874
|
-
case "limit_offset":
|
|
1875
|
-
case "recursive_expand":
|
|
1876
|
-
case "sort":
|
|
1877
|
-
case "vector_knn": {
|
|
1878
|
-
currentNode = currentNode.input;
|
|
1879
|
-
continue;
|
|
1880
|
-
}
|
|
1881
|
-
case "project":
|
|
1882
|
-
case "scan":
|
|
1883
|
-
case "set_op": {
|
|
1884
|
-
return void 0;
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
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
|
+
);
|
|
1887
1645
|
}
|
|
1888
|
-
|
|
1889
|
-
function inspectProjectPlan(logicalPlan) {
|
|
1890
|
-
if (logicalPlan.root.op !== "project") {
|
|
1646
|
+
if (!planShape.hasSort && input.orderBy !== void 0) {
|
|
1891
1647
|
throw new CompilerInvariantError(
|
|
1892
|
-
|
|
1893
|
-
{ component: "
|
|
1648
|
+
"Recursive SQL emitter received ORDER BY clause for a plan without sort nodes",
|
|
1649
|
+
{ component: "recursive-emitter" }
|
|
1894
1650
|
);
|
|
1895
1651
|
}
|
|
1896
|
-
|
|
1897
|
-
collectPlanOperations(logicalPlan.root, operations);
|
|
1898
|
-
const limitOffsetNode = findUnaryNodeInProjectChain(
|
|
1899
|
-
logicalPlan.root,
|
|
1900
|
-
"limit_offset"
|
|
1901
|
-
);
|
|
1902
|
-
const sortNode = findUnaryNodeInProjectChain(
|
|
1903
|
-
logicalPlan.root,
|
|
1904
|
-
"sort"
|
|
1905
|
-
);
|
|
1906
|
-
return {
|
|
1907
|
-
hasAggregate: operations.has("aggregate"),
|
|
1908
|
-
hasLimitOffset: operations.has("limit_offset"),
|
|
1909
|
-
hasRecursiveExpand: operations.has("recursive_expand"),
|
|
1910
|
-
hasSetOperation: operations.has("set_op"),
|
|
1911
|
-
hasSort: operations.has("sort"),
|
|
1912
|
-
hasVectorKnn: operations.has("vector_knn"),
|
|
1913
|
-
limitOffsetNode,
|
|
1914
|
-
rootProjectNode: logicalPlan.root,
|
|
1915
|
-
sortNode
|
|
1916
|
-
};
|
|
1917
|
-
}
|
|
1918
|
-
function inspectStandardProjectPlan(logicalPlan) {
|
|
1919
|
-
const shape = inspectProjectPlan(logicalPlan);
|
|
1920
|
-
if (shape.hasSetOperation) {
|
|
1652
|
+
if (planShape.hasLimitOffset && input.limitOffset === void 0) {
|
|
1921
1653
|
throw new CompilerInvariantError(
|
|
1922
|
-
|
|
1923
|
-
{ component: "
|
|
1654
|
+
"Recursive SQL emitter expected LIMIT/OFFSET clause for plan containing a limit_offset node",
|
|
1655
|
+
{ component: "recursive-emitter" }
|
|
1924
1656
|
);
|
|
1925
1657
|
}
|
|
1926
|
-
if (
|
|
1658
|
+
if (!planShape.hasLimitOffset && input.limitOffset !== void 0) {
|
|
1927
1659
|
throw new CompilerInvariantError(
|
|
1928
|
-
|
|
1929
|
-
{ component: "
|
|
1660
|
+
"Recursive SQL emitter received LIMIT/OFFSET clause for a plan without limit_offset nodes",
|
|
1661
|
+
{ component: "recursive-emitter" }
|
|
1930
1662
|
);
|
|
1931
1663
|
}
|
|
1932
|
-
return shape;
|
|
1933
1664
|
}
|
|
1934
|
-
function
|
|
1935
|
-
|
|
1936
|
-
|
|
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) {
|
|
1937
1686
|
throw new CompilerInvariantError(
|
|
1938
|
-
|
|
1939
|
-
{ component: "
|
|
1687
|
+
"Set-operation SQL emitter received suffix clauses for a plan without top-level sort or limit_offset nodes",
|
|
1688
|
+
{ component: "set-operation-emitter" }
|
|
1940
1689
|
);
|
|
1941
1690
|
}
|
|
1942
|
-
if (
|
|
1691
|
+
if (!hasSuffixClauses) {
|
|
1692
|
+
if (shape.hasSort || shape.hasLimitOffset) {
|
|
1693
|
+
throw new CompilerInvariantError(
|
|
1694
|
+
"Set-operation SQL emitter expected suffix clauses for plan containing top-level sort or limit_offset nodes",
|
|
1695
|
+
{ component: "set-operation-emitter" }
|
|
1696
|
+
);
|
|
1697
|
+
}
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
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) {
|
|
1943
1703
|
throw new CompilerInvariantError(
|
|
1944
|
-
|
|
1945
|
-
{ component: "
|
|
1946
|
-
);
|
|
1947
|
-
}
|
|
1948
|
-
return shape;
|
|
1949
|
-
}
|
|
1950
|
-
function findTopLevelLimitOffsetNode(rootNode) {
|
|
1951
|
-
let currentNode = rootNode;
|
|
1952
|
-
for (; ; ) {
|
|
1953
|
-
if (currentNode.op === "limit_offset") {
|
|
1954
|
-
return currentNode;
|
|
1955
|
-
}
|
|
1956
|
-
switch (currentNode.op) {
|
|
1957
|
-
case "aggregate":
|
|
1958
|
-
case "filter":
|
|
1959
|
-
case "join":
|
|
1960
|
-
case "project":
|
|
1961
|
-
case "recursive_expand":
|
|
1962
|
-
case "sort":
|
|
1963
|
-
case "vector_knn": {
|
|
1964
|
-
currentNode = currentNode.input;
|
|
1965
|
-
continue;
|
|
1966
|
-
}
|
|
1967
|
-
case "scan":
|
|
1968
|
-
case "set_op": {
|
|
1969
|
-
return void 0;
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
function findTopLevelSortNode(rootNode) {
|
|
1975
|
-
let currentNode = rootNode;
|
|
1976
|
-
for (; ; ) {
|
|
1977
|
-
if (currentNode.op === "sort") {
|
|
1978
|
-
return currentNode;
|
|
1979
|
-
}
|
|
1980
|
-
switch (currentNode.op) {
|
|
1981
|
-
case "aggregate":
|
|
1982
|
-
case "filter":
|
|
1983
|
-
case "join":
|
|
1984
|
-
case "limit_offset":
|
|
1985
|
-
case "project":
|
|
1986
|
-
case "recursive_expand":
|
|
1987
|
-
case "vector_knn": {
|
|
1988
|
-
currentNode = currentNode.input;
|
|
1989
|
-
continue;
|
|
1990
|
-
}
|
|
1991
|
-
case "scan":
|
|
1992
|
-
case "set_op": {
|
|
1993
|
-
return void 0;
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
}
|
|
1998
|
-
function inspectSetOperationPlan(logicalPlan) {
|
|
1999
|
-
const operations = /* @__PURE__ */ new Set();
|
|
2000
|
-
collectPlanOperations(logicalPlan.root, operations);
|
|
2001
|
-
if (!operations.has("set_op")) {
|
|
2002
|
-
throw new CompilerInvariantError(
|
|
2003
|
-
'Set-operation SQL emitter expected logical plan to contain a "set_op" node',
|
|
2004
|
-
{ component: "plan-inspector" }
|
|
2005
|
-
);
|
|
2006
|
-
}
|
|
2007
|
-
const limitOffsetNode = findTopLevelLimitOffsetNode(logicalPlan.root);
|
|
2008
|
-
const sortNode = findTopLevelSortNode(logicalPlan.root);
|
|
2009
|
-
return {
|
|
2010
|
-
hasLimitOffset: limitOffsetNode !== void 0,
|
|
2011
|
-
hasSetOperation: true,
|
|
2012
|
-
hasSort: sortNode !== void 0,
|
|
2013
|
-
limitOffsetNode,
|
|
2014
|
-
sortNode
|
|
2015
|
-
};
|
|
2016
|
-
}
|
|
2017
|
-
function assertRecursiveEmitterClauseAlignment(logicalPlan, input) {
|
|
2018
|
-
const planShape = inspectRecursiveProjectPlan(logicalPlan);
|
|
2019
|
-
if (planShape.hasSort && input.orderBy === void 0) {
|
|
2020
|
-
throw new CompilerInvariantError(
|
|
2021
|
-
"Recursive SQL emitter expected ORDER BY clause for plan containing a sort node",
|
|
2022
|
-
{ component: "recursive-emitter" }
|
|
2023
|
-
);
|
|
2024
|
-
}
|
|
2025
|
-
if (!planShape.hasSort && input.orderBy !== void 0) {
|
|
2026
|
-
throw new CompilerInvariantError(
|
|
2027
|
-
"Recursive SQL emitter received ORDER BY clause for a plan without sort nodes",
|
|
2028
|
-
{ component: "recursive-emitter" }
|
|
2029
|
-
);
|
|
2030
|
-
}
|
|
2031
|
-
if (planShape.hasLimitOffset && input.limitOffset === void 0) {
|
|
2032
|
-
throw new CompilerInvariantError(
|
|
2033
|
-
"Recursive SQL emitter expected LIMIT/OFFSET clause for plan containing a limit_offset node",
|
|
2034
|
-
{ component: "recursive-emitter" }
|
|
2035
|
-
);
|
|
2036
|
-
}
|
|
2037
|
-
if (!planShape.hasLimitOffset && input.limitOffset !== void 0) {
|
|
2038
|
-
throw new CompilerInvariantError(
|
|
2039
|
-
"Recursive SQL emitter received LIMIT/OFFSET clause for a plan without limit_offset nodes",
|
|
2040
|
-
{ component: "recursive-emitter" }
|
|
2041
|
-
);
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
function emitRecursiveQuerySql(input) {
|
|
2045
|
-
assertRecursiveEmitterClauseAlignment(input.logicalPlan, input);
|
|
2046
|
-
const parts = [
|
|
2047
|
-
sql`WITH RECURSIVE`,
|
|
2048
|
-
input.recursiveCte,
|
|
2049
|
-
sql`SELECT ${input.projection}`,
|
|
2050
|
-
sql`FROM recursive_cte`,
|
|
2051
|
-
input.depthFilter
|
|
2052
|
-
];
|
|
2053
|
-
if (input.orderBy !== void 0) {
|
|
2054
|
-
parts.push(input.orderBy);
|
|
2055
|
-
}
|
|
2056
|
-
if (input.limitOffset !== void 0) {
|
|
2057
|
-
parts.push(input.limitOffset);
|
|
2058
|
-
}
|
|
2059
|
-
return sql.join(parts, sql` `);
|
|
2060
|
-
}
|
|
2061
|
-
function assertSetOperationEmitterClauseAlignment(logicalPlan, suffixClauses) {
|
|
2062
|
-
const shape = inspectSetOperationPlan(logicalPlan);
|
|
2063
|
-
const hasSuffixClauses = suffixClauses !== void 0 && suffixClauses.length > 0;
|
|
2064
|
-
if (!shape.hasSort && !shape.hasLimitOffset && hasSuffixClauses) {
|
|
2065
|
-
throw new CompilerInvariantError(
|
|
2066
|
-
"Set-operation SQL emitter received suffix clauses for a plan without top-level sort or limit_offset nodes",
|
|
2067
|
-
{ component: "set-operation-emitter" }
|
|
2068
|
-
);
|
|
2069
|
-
}
|
|
2070
|
-
if (!hasSuffixClauses) {
|
|
2071
|
-
if (shape.hasSort || shape.hasLimitOffset) {
|
|
2072
|
-
throw new CompilerInvariantError(
|
|
2073
|
-
"Set-operation SQL emitter expected suffix clauses for plan containing top-level sort or limit_offset nodes",
|
|
2074
|
-
{ component: "set-operation-emitter" }
|
|
2075
|
-
);
|
|
2076
|
-
}
|
|
2077
|
-
return;
|
|
2078
|
-
}
|
|
2079
|
-
const limitOffsetClauseCount = shape.limitOffsetNode === void 0 ? 0 : (shape.limitOffsetNode.limit === void 0 ? 0 : 1) + (shape.limitOffsetNode.offset === void 0 ? 0 : 1);
|
|
2080
|
-
const expectedClauseCount = (shape.sortNode === void 0 ? 0 : 1) + limitOffsetClauseCount;
|
|
2081
|
-
if (suffixClauses.length !== expectedClauseCount) {
|
|
2082
|
-
throw new CompilerInvariantError(
|
|
2083
|
-
`Set-operation SQL emitter expected ${String(expectedClauseCount)} top-level suffix clause(s) from logical plan, got ${String(suffixClauses.length)}`,
|
|
2084
|
-
{ component: "set-operation-emitter" }
|
|
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" }
|
|
2085
1706
|
);
|
|
2086
1707
|
}
|
|
2087
1708
|
}
|
|
@@ -2161,62 +1782,6 @@ function emitStandardQuerySql(input) {
|
|
|
2161
1782
|
}
|
|
2162
1783
|
return sql.join(parts, sql` `);
|
|
2163
1784
|
}
|
|
2164
|
-
var EMPTY_PREDICATES = [];
|
|
2165
|
-
function buildPredicateIndexKey(alias, targetType) {
|
|
2166
|
-
return `${alias}\0${targetType}`;
|
|
2167
|
-
}
|
|
2168
|
-
function resolvePredicateTargetType(predicate2) {
|
|
2169
|
-
return predicate2.targetType === "edge" ? "edge" : "node";
|
|
2170
|
-
}
|
|
2171
|
-
function buildPredicateIndex(ast) {
|
|
2172
|
-
const byAliasAndType = /* @__PURE__ */ new Map();
|
|
2173
|
-
for (const predicate2 of ast.predicates) {
|
|
2174
|
-
const key = buildPredicateIndexKey(
|
|
2175
|
-
predicate2.targetAlias,
|
|
2176
|
-
resolvePredicateTargetType(predicate2)
|
|
2177
|
-
);
|
|
2178
|
-
const existing = byAliasAndType.get(key);
|
|
2179
|
-
if (existing === void 0) {
|
|
2180
|
-
byAliasAndType.set(key, [predicate2]);
|
|
2181
|
-
} else {
|
|
2182
|
-
existing.push(predicate2);
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
return { byAliasAndType };
|
|
2186
|
-
}
|
|
2187
|
-
function getPredicatesForAlias(predicateIndex, alias, targetType) {
|
|
2188
|
-
return predicateIndex.byAliasAndType.get(
|
|
2189
|
-
buildPredicateIndexKey(alias, targetType)
|
|
2190
|
-
) ?? EMPTY_PREDICATES;
|
|
2191
|
-
}
|
|
2192
|
-
function compilePredicateClauses(predicates, predicateContext) {
|
|
2193
|
-
return predicates.map(
|
|
2194
|
-
(predicate2) => compilePredicateExpression(predicate2.expression, predicateContext)
|
|
2195
|
-
);
|
|
2196
|
-
}
|
|
2197
|
-
function compileKindFilter(column, kinds) {
|
|
2198
|
-
if (kinds.length === 0) {
|
|
2199
|
-
return sql`1 = 0`;
|
|
2200
|
-
}
|
|
2201
|
-
if (kinds.length === 1) {
|
|
2202
|
-
return sql`${column} = ${kinds[0]}`;
|
|
2203
|
-
}
|
|
2204
|
-
return sql`${column} IN (${sql.join(
|
|
2205
|
-
kinds.map((kind) => sql`${kind}`),
|
|
2206
|
-
sql`, `
|
|
2207
|
-
)})`;
|
|
2208
|
-
}
|
|
2209
|
-
function getNodeKindsForAlias(ast, alias) {
|
|
2210
|
-
if (alias === ast.start.alias) {
|
|
2211
|
-
return ast.start.kinds;
|
|
2212
|
-
}
|
|
2213
|
-
for (const traversal of ast.traversals) {
|
|
2214
|
-
if (traversal.nodeAlias === alias) {
|
|
2215
|
-
return traversal.nodeKinds;
|
|
2216
|
-
}
|
|
2217
|
-
}
|
|
2218
|
-
throw new CompilerInvariantError(`Unknown traversal source alias: ${alias}`);
|
|
2219
|
-
}
|
|
2220
1785
|
|
|
2221
1786
|
// src/query/compiler/typed-json-extract.ts
|
|
2222
1787
|
function compileTypedJsonExtract(input) {
|
|
@@ -2806,254 +2371,695 @@ function buildLimitOffsetClause(input) {
|
|
|
2806
2371
|
return parts.length > 0 ? sql.join(parts, sql` `) : void 0;
|
|
2807
2372
|
}
|
|
2808
2373
|
|
|
2809
|
-
// src/query/compiler/recursive.ts
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
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)
|
|
2822
2395
|
};
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
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})`;
|
|
2833
2407
|
}
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
name: "temporal_filters",
|
|
2838
|
-
execute(currentState) {
|
|
2839
|
-
return createTemporalFilterPass(
|
|
2840
|
-
currentState.ast,
|
|
2841
|
-
currentState.ctx.dialect.currentTimestamp()
|
|
2842
|
-
);
|
|
2843
|
-
},
|
|
2844
|
-
update(currentState, temporalFilterPass) {
|
|
2845
|
-
return {
|
|
2846
|
-
...currentState,
|
|
2847
|
-
temporalFilterPass
|
|
2848
|
-
};
|
|
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})`;
|
|
2849
2411
|
}
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
const columnPruningPass = runCompilerPass(state, {
|
|
2853
|
-
name: "column_pruning",
|
|
2854
|
-
execute(currentState) {
|
|
2855
|
-
const traversal = currentState.traversal;
|
|
2856
|
-
if (traversal === void 0) {
|
|
2857
|
-
throw new CompilerInvariantError(
|
|
2858
|
-
"Recursive traversal pass did not select traversal"
|
|
2859
|
-
);
|
|
2860
|
-
}
|
|
2861
|
-
return collectRequiredColumnsByAlias(currentState.ast, traversal);
|
|
2862
|
-
},
|
|
2863
|
-
update(currentState, requiredColumnsByAlias) {
|
|
2864
|
-
return {
|
|
2865
|
-
...currentState,
|
|
2866
|
-
requiredColumnsByAlias
|
|
2867
|
-
};
|
|
2412
|
+
case "includeEnded": {
|
|
2413
|
+
return sql`${deletedAt} IS NULL`;
|
|
2868
2414
|
}
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
const logicalPlanPass = runCompilerPass(state, {
|
|
2872
|
-
name: "logical_plan",
|
|
2873
|
-
execute(currentState) {
|
|
2874
|
-
const loweringInput = {
|
|
2875
|
-
ast: currentState.ast,
|
|
2876
|
-
dialect: currentState.ctx.dialect.name,
|
|
2877
|
-
graphId: currentState.graphId,
|
|
2878
|
-
...currentState.traversal === void 0 ? {} : { traversal: currentState.traversal }
|
|
2879
|
-
};
|
|
2880
|
-
return lowerRecursiveQueryToLogicalPlan(loweringInput);
|
|
2881
|
-
},
|
|
2882
|
-
update(currentState, logicalPlan) {
|
|
2883
|
-
return {
|
|
2884
|
-
...currentState,
|
|
2885
|
-
logicalPlan
|
|
2886
|
-
};
|
|
2415
|
+
case "includeTombstones": {
|
|
2416
|
+
return sql.raw("1=1");
|
|
2887
2417
|
}
|
|
2888
|
-
}
|
|
2889
|
-
state = logicalPlanPass.state;
|
|
2890
|
-
return state;
|
|
2418
|
+
}
|
|
2891
2419
|
}
|
|
2892
|
-
function
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2420
|
+
function extractTemporalOptions(ast, tableAlias) {
|
|
2421
|
+
return {
|
|
2422
|
+
mode: ast.temporalMode.mode,
|
|
2423
|
+
asOf: ast.temporalMode.asOf,
|
|
2424
|
+
tableAlias
|
|
2425
|
+
};
|
|
2896
2426
|
}
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
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"
|
|
2912
2446
|
);
|
|
2913
2447
|
}
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
);
|
|
2448
|
+
const vectorPredicate = vectorPredicates[0];
|
|
2449
|
+
if (vectorPredicate === void 0) {
|
|
2450
|
+
return { vectorPredicate: void 0 };
|
|
2918
2451
|
}
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
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}"`
|
|
2922
2456
|
);
|
|
2923
2457
|
}
|
|
2924
|
-
|
|
2925
|
-
ast,
|
|
2926
|
-
vlTraversal,
|
|
2927
|
-
graphId,
|
|
2928
|
-
ctx,
|
|
2929
|
-
requiredColumnsByAlias,
|
|
2930
|
-
temporalFilterPass
|
|
2931
|
-
);
|
|
2932
|
-
const projection = compileRecursiveProjection(ast, vlTraversal, dialect);
|
|
2933
|
-
const minDepth = vlTraversal.variableLength.minDepth;
|
|
2934
|
-
const depthFilter = minDepth > 0 ? sql`WHERE depth >= ${minDepth}` : sql.raw("");
|
|
2935
|
-
const orderBy = compileRecursiveOrderBy(ast, dialect);
|
|
2936
|
-
const limitOffset = compileLimitOffset(ast);
|
|
2937
|
-
return emitRecursiveQuerySql({
|
|
2938
|
-
depthFilter,
|
|
2939
|
-
...limitOffset === void 0 ? {} : { limitOffset },
|
|
2940
|
-
logicalPlan,
|
|
2941
|
-
...orderBy === void 0 ? {} : { orderBy },
|
|
2942
|
-
projection,
|
|
2943
|
-
recursiveCte
|
|
2944
|
-
});
|
|
2945
|
-
}
|
|
2946
|
-
function hasVariableLengthTraversal(ast) {
|
|
2947
|
-
return ast.traversals.some((t) => t.variableLength !== void 0);
|
|
2948
|
-
}
|
|
2949
|
-
function compileRecursiveCte(ast, traversal, graphId, ctx, requiredColumnsByAlias, temporalFilterPass) {
|
|
2950
|
-
const { dialect } = ctx;
|
|
2951
|
-
const startAlias = ast.start.alias;
|
|
2952
|
-
const startKinds = ast.start.kinds;
|
|
2953
|
-
const nodeAlias = traversal.nodeAlias;
|
|
2954
|
-
const directEdgeKinds = [...new Set(traversal.edgeKinds)];
|
|
2955
|
-
const inverseEdgeKinds = traversal.inverseEdgeKinds === void 0 ? [] : [...new Set(traversal.inverseEdgeKinds)];
|
|
2956
|
-
const forceWorktableOuterJoinOrder = dialect.capabilities.forceRecursiveWorktableOuterJoinOrder;
|
|
2957
|
-
const nodeKinds = traversal.nodeKinds;
|
|
2958
|
-
const previousNodeKinds = [.../* @__PURE__ */ new Set([...startKinds, ...nodeKinds])];
|
|
2959
|
-
const direction = traversal.direction;
|
|
2960
|
-
const vl = traversal.variableLength;
|
|
2961
|
-
const shouldEnforceCycleCheck = vl.cyclePolicy !== "allow";
|
|
2962
|
-
const shouldTrackPath = shouldEnforceCycleCheck || vl.pathAlias !== void 0;
|
|
2963
|
-
const recursiveJoinRequiredColumns = /* @__PURE__ */ new Set(["id"]);
|
|
2964
|
-
if (previousNodeKinds.length > 1) {
|
|
2965
|
-
recursiveJoinRequiredColumns.add("kind");
|
|
2966
|
-
}
|
|
2967
|
-
const requiredStartColumns = requiredColumnsByAlias ? requiredColumnsByAlias.get(startAlias) ?? EMPTY_REQUIRED_COLUMNS : void 0;
|
|
2968
|
-
const requiredNodeColumns = requiredColumnsByAlias ? requiredColumnsByAlias.get(nodeAlias) ?? EMPTY_REQUIRED_COLUMNS : void 0;
|
|
2969
|
-
const startColumnsFromBase = compileNodeSelectColumnsFromTable(
|
|
2970
|
-
"n0",
|
|
2971
|
-
startAlias,
|
|
2972
|
-
requiredStartColumns,
|
|
2973
|
-
NO_ALWAYS_REQUIRED_COLUMNS
|
|
2974
|
-
);
|
|
2975
|
-
const startColumnsFromRecursive = compileNodeSelectColumnsFromRecursiveRow(
|
|
2976
|
-
startAlias,
|
|
2977
|
-
requiredStartColumns,
|
|
2978
|
-
NO_ALWAYS_REQUIRED_COLUMNS
|
|
2979
|
-
);
|
|
2980
|
-
const nodeColumnsFromBase = compileNodeSelectColumnsFromTable(
|
|
2981
|
-
"n0",
|
|
2982
|
-
nodeAlias,
|
|
2983
|
-
requiredNodeColumns,
|
|
2984
|
-
recursiveJoinRequiredColumns
|
|
2985
|
-
);
|
|
2986
|
-
const nodeColumnsFromRecursive = compileNodeSelectColumnsFromTable(
|
|
2987
|
-
"n",
|
|
2988
|
-
nodeAlias,
|
|
2989
|
-
requiredNodeColumns,
|
|
2990
|
-
recursiveJoinRequiredColumns
|
|
2991
|
-
);
|
|
2992
|
-
const startKindFilter = compileKindFilter2(startKinds, "n0.kind");
|
|
2993
|
-
const nodeKindFilter = compileKindFilter2(nodeKinds, "n.kind");
|
|
2994
|
-
const startTemporalFilter = temporalFilterPass.forAlias("n0");
|
|
2995
|
-
const edgeTemporalFilter = temporalFilterPass.forAlias("e");
|
|
2996
|
-
const nodeTemporalFilter = temporalFilterPass.forAlias("n");
|
|
2997
|
-
const startContext = { ...ctx, cteColumnPrefix: "" };
|
|
2998
|
-
const startPredicates = compileNodePredicates(ast, startAlias, startContext);
|
|
2999
|
-
const edgeContext = { ...ctx, cteColumnPrefix: "e" };
|
|
3000
|
-
const edgePredicates = compileEdgePredicates(
|
|
3001
|
-
ast,
|
|
3002
|
-
traversal.edgeAlias,
|
|
3003
|
-
edgeContext
|
|
3004
|
-
);
|
|
3005
|
-
const targetContext = { ...ctx, cteColumnPrefix: "n" };
|
|
3006
|
-
const targetNodePredicates = compileNodePredicates(
|
|
3007
|
-
ast,
|
|
3008
|
-
nodeAlias,
|
|
3009
|
-
targetContext
|
|
3010
|
-
);
|
|
3011
|
-
if (vl.maxDepth > MAX_EXPLICIT_RECURSIVE_DEPTH) {
|
|
2458
|
+
if (!dialect.capabilities.vectorMetrics.includes(vectorPredicate.metric)) {
|
|
3012
2459
|
throw new UnsupportedPredicateError(
|
|
3013
|
-
`
|
|
2460
|
+
`Vector metric "${vectorPredicate.metric}" is not supported for dialect "${dialect.name}"`
|
|
3014
2461
|
);
|
|
3015
2462
|
}
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
const pathExtension = shouldTrackPath ? dialect.extendPath(sql.raw("r.path"), sql.raw("n.id")) : void 0;
|
|
3021
|
-
const baseWhereClauses = [
|
|
3022
|
-
sql`n0.graph_id = ${graphId}`,
|
|
3023
|
-
startKindFilter,
|
|
3024
|
-
startTemporalFilter,
|
|
3025
|
-
...startPredicates
|
|
3026
|
-
];
|
|
3027
|
-
const recursiveBaseWhereClauses = [
|
|
3028
|
-
sql`e.graph_id = ${graphId}`,
|
|
3029
|
-
nodeKindFilter,
|
|
3030
|
-
edgeTemporalFilter,
|
|
3031
|
-
nodeTemporalFilter,
|
|
3032
|
-
maxDepthCondition
|
|
3033
|
-
];
|
|
3034
|
-
if (cycleCheck !== void 0) {
|
|
3035
|
-
recursiveBaseWhereClauses.push(cycleCheck);
|
|
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
|
+
);
|
|
3036
2467
|
}
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
compileKindFilter2(nodeKinds, `e.${branch.targetKindField}`)
|
|
3044
|
-
];
|
|
3045
|
-
if (branch.duplicateGuard !== void 0) {
|
|
3046
|
-
recursiveFilterClauses.push(branch.duplicateGuard);
|
|
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
|
+
);
|
|
3047
2474
|
}
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
];
|
|
3053
|
-
if (pathExtension !== void 0) {
|
|
3054
|
-
recursiveSelectColumns.push(sql`${pathExtension} AS path`);
|
|
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
|
+
);
|
|
3055
2479
|
}
|
|
3056
|
-
|
|
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
|
+
}
|
|
2507
|
+
}
|
|
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;
|
|
2520
|
+
}
|
|
2521
|
+
return {
|
|
2522
|
+
alias,
|
|
2523
|
+
id: nextPlanNodeId(),
|
|
2524
|
+
input: currentNode,
|
|
2525
|
+
op: "filter",
|
|
2526
|
+
predicateTargetType,
|
|
2527
|
+
predicates: aliasPredicates.map((predicate2) => predicate2.expression)
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
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 };
|
|
2542
|
+
}
|
|
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 {
|
|
2570
|
+
throw new CompilerInvariantError(
|
|
2571
|
+
"limit_offset node requires limit or offset to be present"
|
|
2572
|
+
);
|
|
2573
|
+
}
|
|
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
|
+
};
|
|
2585
|
+
}
|
|
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
|
|
2602
|
+
);
|
|
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
|
|
2623
|
+
);
|
|
2624
|
+
currentNode = wrapWithAliasFilterNode(
|
|
2625
|
+
currentNode,
|
|
2626
|
+
ast,
|
|
2627
|
+
traversal.nodeAlias,
|
|
2628
|
+
"node",
|
|
2629
|
+
nextPlanNodeId
|
|
2630
|
+
);
|
|
2631
|
+
}
|
|
2632
|
+
if (input.vectorPredicate !== void 0) {
|
|
2633
|
+
currentNode = {
|
|
2634
|
+
id: nextPlanNodeId(),
|
|
2635
|
+
input: currentNode,
|
|
2636
|
+
op: "vector_knn",
|
|
2637
|
+
predicate: input.vectorPredicate
|
|
2638
|
+
};
|
|
2639
|
+
}
|
|
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
|
|
2705
|
+
);
|
|
2706
|
+
}
|
|
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
|
+
});
|
|
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);
|
|
2730
|
+
}
|
|
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
|
+
};
|
|
2756
|
+
}
|
|
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 };
|
|
2767
|
+
} else {
|
|
2768
|
+
currentNode = { ...limitOffsetBase, limit: op.limit };
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
return currentNode;
|
|
2772
|
+
}
|
|
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
|
+
};
|
|
2785
|
+
}
|
|
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
|
+
};
|
|
2798
|
+
}
|
|
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
|
+
};
|
|
2813
|
+
}
|
|
2814
|
+
|
|
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
|
+
};
|
|
2839
|
+
}
|
|
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
|
+
};
|
|
2855
|
+
}
|
|
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
|
+
};
|
|
2874
|
+
}
|
|
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
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
});
|
|
2895
|
+
state = logicalPlanPass.state;
|
|
2896
|
+
return state;
|
|
2897
|
+
}
|
|
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);
|
|
2902
|
+
}
|
|
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
|
+
);
|
|
2919
|
+
}
|
|
2920
|
+
if (logicalPlan === void 0) {
|
|
2921
|
+
throw new CompilerInvariantError(
|
|
2922
|
+
"Logical plan pass did not initialize plan state"
|
|
2923
|
+
);
|
|
2924
|
+
}
|
|
2925
|
+
if (vlTraversal === void 0) {
|
|
2926
|
+
throw new CompilerInvariantError(
|
|
2927
|
+
"Recursive traversal pass did not select traversal"
|
|
2928
|
+
);
|
|
2929
|
+
}
|
|
2930
|
+
const recursiveCte = compileRecursiveCte(
|
|
2931
|
+
ast,
|
|
2932
|
+
vlTraversal,
|
|
2933
|
+
graphId,
|
|
2934
|
+
ctx,
|
|
2935
|
+
requiredColumnsByAlias,
|
|
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;
|
|
2960
|
+
const directEdgeKinds = [...new Set(traversal.edgeKinds)];
|
|
2961
|
+
const inverseEdgeKinds = traversal.inverseEdgeKinds === void 0 ? [] : [...new Set(traversal.inverseEdgeKinds)];
|
|
2962
|
+
const forceWorktableOuterJoinOrder = dialect.capabilities.forceRecursiveWorktableOuterJoinOrder;
|
|
2963
|
+
const nodeKinds = traversal.nodeKinds;
|
|
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");
|
|
3001
|
+
const edgeTemporalFilter = temporalFilterPass.forAlias("e");
|
|
3002
|
+
const nodeTemporalFilter = temporalFilterPass.forAlias("n");
|
|
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
|
|
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;
|
|
3027
|
+
const baseWhereClauses = [
|
|
3028
|
+
sql`n0.graph_id = ${graphId}`,
|
|
3029
|
+
startKindFilter,
|
|
3030
|
+
startTemporalFilter,
|
|
3031
|
+
...startPredicates
|
|
3032
|
+
];
|
|
3033
|
+
const recursiveBaseWhereClauses = [
|
|
3034
|
+
sql`e.graph_id = ${graphId}`,
|
|
3035
|
+
nodeKindFilter,
|
|
3036
|
+
edgeTemporalFilter,
|
|
3037
|
+
nodeTemporalFilter,
|
|
3038
|
+
maxDepthCondition
|
|
3039
|
+
];
|
|
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}`)
|
|
3050
|
+
];
|
|
3051
|
+
if (branch.duplicateGuard !== void 0) {
|
|
3052
|
+
recursiveFilterClauses.push(branch.duplicateGuard);
|
|
3053
|
+
}
|
|
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`);
|
|
3061
|
+
}
|
|
3062
|
+
const recursiveJoinClauses = [
|
|
3057
3063
|
sql`e.${sql.raw(branch.joinField)} = r.${sql.raw(nodeAlias)}_id`
|
|
3058
3064
|
];
|
|
3059
3065
|
if (previousNodeKinds.length > 1) {
|
|
@@ -3061,278 +3067,809 @@ function compileRecursiveCte(ast, traversal, graphId, ctx, requiredColumnsByAlia
|
|
|
3061
3067
|
sql`e.${sql.raw(branch.joinKindField)} = r.${sql.raw(nodeAlias)}_kind`
|
|
3062
3068
|
);
|
|
3063
3069
|
}
|
|
3064
|
-
if (forceWorktableOuterJoinOrder) {
|
|
3065
|
-
const recursiveWhereClauses = [
|
|
3066
|
-
...recursiveJoinClauses,
|
|
3067
|
-
...recursiveFilterClauses
|
|
3068
|
-
];
|
|
3069
|
-
return sql`
|
|
3070
|
-
SELECT ${sql.join(recursiveSelectColumns, sql`, `)}
|
|
3071
|
-
FROM recursive_cte r
|
|
3072
|
-
CROSS JOIN ${ctx.schema.edgesTable} e
|
|
3073
|
-
JOIN ${ctx.schema.nodesTable} n ON n.graph_id = e.graph_id
|
|
3074
|
-
AND n.id = e.${sql.raw(branch.targetField)}
|
|
3075
|
-
AND n.kind = e.${sql.raw(branch.targetKindField)}
|
|
3076
|
-
WHERE ${sql.join(recursiveWhereClauses, sql` AND `)}
|
|
3077
|
-
`;
|
|
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
|
+
`;
|
|
3094
|
+
}
|
|
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)
|
|
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
|
+
`;
|
|
3128
|
+
}
|
|
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`);
|
|
3137
|
+
}
|
|
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
|
+
`;
|
|
3151
|
+
}
|
|
3152
|
+
function compileKindFilter2(kinds, columnExpr) {
|
|
3153
|
+
return compileKindFilter(sql.raw(columnExpr), kinds);
|
|
3154
|
+
}
|
|
3155
|
+
function compileNodePredicates(ast, alias, ctx) {
|
|
3156
|
+
return ast.predicates.filter((p) => p.targetAlias === alias && p.targetType !== "edge").map((p) => compilePredicateExpression(p.expression, ctx));
|
|
3157
|
+
}
|
|
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) {
|
|
3164
|
+
return void 0;
|
|
3165
|
+
}
|
|
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");
|
|
3173
|
+
}
|
|
3174
|
+
for (const field2 of selectiveFields) {
|
|
3175
|
+
markSelectiveFieldAsRequired(requiredColumnsByAlias, field2);
|
|
3176
|
+
}
|
|
3177
|
+
if (ast.orderBy) {
|
|
3178
|
+
for (const orderSpec of ast.orderBy) {
|
|
3179
|
+
markFieldRefAsRequired(requiredColumnsByAlias, orderSpec.field);
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
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}`)}`
|
|
3189
|
+
);
|
|
3190
|
+
}
|
|
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
|
+
});
|
|
3198
|
+
}
|
|
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
|
|
3206
|
+
);
|
|
3207
|
+
}
|
|
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)}`);
|
|
3238
|
+
}
|
|
3239
|
+
return sql.join(fields, sql`, `);
|
|
3240
|
+
}
|
|
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
|
+
);
|
|
3248
|
+
}
|
|
3249
|
+
if (field2.isSystemField) {
|
|
3250
|
+
const dbColumn = mapSelectiveSystemFieldToColumn(field2.field);
|
|
3251
|
+
return sql`${sql.raw(`${field2.alias}_${dbColumn}`)} AS ${quoteIdentifier(field2.outputName)}`;
|
|
3078
3252
|
}
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
`;
|
|
3088
|
-
}
|
|
3089
|
-
const directJoinField = direction === "out" ? "from_id" : "to_id";
|
|
3090
|
-
const directTargetField = direction === "out" ? "to_id" : "from_id";
|
|
3091
|
-
const directJoinKindField = direction === "out" ? "from_kind" : "to_kind";
|
|
3092
|
-
const directTargetKindField = direction === "out" ? "to_kind" : "from_kind";
|
|
3093
|
-
const directBranch = compileRecursiveBranch2({
|
|
3094
|
-
joinField: directJoinField,
|
|
3095
|
-
targetField: directTargetField,
|
|
3096
|
-
joinKindField: directJoinKindField,
|
|
3097
|
-
targetKindField: directTargetKindField,
|
|
3098
|
-
edgeKinds: directEdgeKinds
|
|
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)}`;
|
|
3099
3261
|
});
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3262
|
+
const vl = traversal.variableLength;
|
|
3263
|
+
if (vl.depthAlias !== void 0) {
|
|
3264
|
+
columns.push(sql`depth AS ${quoteIdentifier(vl.depthAlias)}`);
|
|
3265
|
+
}
|
|
3266
|
+
if (vl.pathAlias !== void 0) {
|
|
3267
|
+
columns.push(sql`path AS ${quoteIdentifier(vl.pathAlias)}`);
|
|
3268
|
+
}
|
|
3269
|
+
return sql.join(columns, sql`, `);
|
|
3270
|
+
}
|
|
3271
|
+
function compileRecursiveOrderBy(ast, dialect) {
|
|
3272
|
+
if (!ast.orderBy || ast.orderBy.length === 0) {
|
|
3273
|
+
return void 0;
|
|
3274
|
+
}
|
|
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"
|
|
3281
|
+
);
|
|
3282
|
+
}
|
|
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}`
|
|
3290
|
+
);
|
|
3291
|
+
}
|
|
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}`);
|
|
3298
|
+
}
|
|
3299
|
+
if (ast.offset !== void 0) {
|
|
3300
|
+
parts.push(sql`OFFSET ${ast.offset}`);
|
|
3301
|
+
}
|
|
3302
|
+
return parts.length > 0 ? sql.join(parts, sql` `) : void 0;
|
|
3303
|
+
}
|
|
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`);
|
|
3314
|
+
}
|
|
3315
|
+
if (name.length > MAX_IDENTIFIER_LENGTH) {
|
|
3316
|
+
throw new ConfigurationError(
|
|
3317
|
+
`${label} table name exceeds maximum length of ${MAX_IDENTIFIER_LENGTH} characters`
|
|
3318
|
+
);
|
|
3319
|
+
}
|
|
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
|
+
);
|
|
3324
|
+
}
|
|
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);
|
|
3367
|
+
}
|
|
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;
|
|
3375
|
+
}
|
|
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
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
case "string":
|
|
3390
|
+
case "date":
|
|
3391
|
+
case "unknown": {
|
|
3392
|
+
return value;
|
|
3393
|
+
}
|
|
3394
|
+
default: {
|
|
3395
|
+
return value;
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
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
|
+
}
|
|
3429
|
+
);
|
|
3430
|
+
}
|
|
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
|
+
}
|
|
3107
3438
|
);
|
|
3108
|
-
const duplicateGuard = overlappingKinds.length > 0 ? sql`NOT (e.from_id = e.to_id AND ${compileKindFilter2(overlappingKinds, "e.kind")})` : void 0;
|
|
3109
|
-
const inverseBranch = compileRecursiveBranch2({
|
|
3110
|
-
joinField: inverseJoinField,
|
|
3111
|
-
targetField: inverseTargetField,
|
|
3112
|
-
joinKindField: inverseJoinKindField,
|
|
3113
|
-
targetKindField: inverseTargetKindField,
|
|
3114
|
-
edgeKinds: inverseEdgeKinds,
|
|
3115
|
-
duplicateGuard
|
|
3116
|
-
});
|
|
3117
|
-
return sql`
|
|
3118
|
-
${directBranch}
|
|
3119
|
-
UNION ALL
|
|
3120
|
-
${inverseBranch}
|
|
3121
|
-
`;
|
|
3122
3439
|
}
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
baseSelectColumns.push(sql`${initialPath} AS path`);
|
|
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
|
+
}
|
|
3131
3447
|
}
|
|
3132
|
-
return
|
|
3133
|
-
|
|
3134
|
-
-- Base case: starting nodes
|
|
3135
|
-
SELECT ${sql.join(baseSelectColumns, sql`, `)}
|
|
3136
|
-
FROM ${ctx.schema.nodesTable} n0
|
|
3137
|
-
WHERE ${sql.join(baseWhereClauses, sql` AND `)}
|
|
3138
|
-
|
|
3139
|
-
UNION ALL
|
|
3448
|
+
return filtered;
|
|
3449
|
+
}
|
|
3140
3450
|
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
`;
|
|
3451
|
+
// src/store/row-mappers.ts
|
|
3452
|
+
function nullToUndefined2(value) {
|
|
3453
|
+
return value === null ? void 0 : value;
|
|
3145
3454
|
}
|
|
3146
|
-
function
|
|
3147
|
-
|
|
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
|
+
};
|
|
3148
3464
|
}
|
|
3149
|
-
function
|
|
3150
|
-
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
|
+
};
|
|
3151
3474
|
}
|
|
3152
|
-
function
|
|
3153
|
-
|
|
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
|
+
};
|
|
3154
3488
|
}
|
|
3155
|
-
function
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
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: [] };
|
|
3167
3540
|
}
|
|
3168
|
-
|
|
3169
|
-
|
|
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
|
+
);
|
|
3170
3621
|
}
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
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
|
+
});
|
|
3174
3639
|
}
|
|
3175
3640
|
}
|
|
3176
|
-
return
|
|
3641
|
+
return {
|
|
3642
|
+
includeMeta,
|
|
3643
|
+
propertyFields: [...propertyFields.values()]
|
|
3644
|
+
};
|
|
3177
3645
|
}
|
|
3178
|
-
function
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
);
|
|
3646
|
+
function getIncludedNodeKinds(graph, includeKinds) {
|
|
3647
|
+
if (includeKinds === void 0 || includeKinds.length === 0) {
|
|
3648
|
+
return Object.keys(graph.nodes);
|
|
3649
|
+
}
|
|
3650
|
+
return dedupeStrings(includeKinds);
|
|
3184
3651
|
}
|
|
3185
|
-
function
|
|
3186
|
-
return
|
|
3187
|
-
(column) => shouldProjectColumn(requiredColumns, column, alwaysRequiredColumns)
|
|
3188
|
-
).map((column) => {
|
|
3189
|
-
const projected = `${alias}_${column}`;
|
|
3190
|
-
return sql`r.${sql.raw(projected)} AS ${sql.raw(projected)}`;
|
|
3191
|
-
});
|
|
3652
|
+
function dedupeStrings(values) {
|
|
3653
|
+
return [...new Set(values)];
|
|
3192
3654
|
}
|
|
3193
|
-
function
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
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`);
|
|
3201
3664
|
}
|
|
3202
|
-
const
|
|
3203
|
-
const
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
sql`${sql.raw(startAlias)}_id`,
|
|
3208
|
-
sql`${sql.raw(startAlias)}_kind`,
|
|
3209
|
-
sql`${sql.raw(startAlias)}_props`,
|
|
3210
|
-
sql`${sql.raw(startAlias)}_version`,
|
|
3211
|
-
sql`${sql.raw(startAlias)}_valid_from`,
|
|
3212
|
-
sql`${sql.raw(startAlias)}_valid_to`,
|
|
3213
|
-
sql`${sql.raw(startAlias)}_created_at`,
|
|
3214
|
-
sql`${sql.raw(startAlias)}_updated_at`,
|
|
3215
|
-
sql`${sql.raw(startAlias)}_deleted_at`,
|
|
3216
|
-
// Node alias fields with metadata
|
|
3217
|
-
sql`${sql.raw(nodeAlias)}_id`,
|
|
3218
|
-
sql`${sql.raw(nodeAlias)}_kind`,
|
|
3219
|
-
sql`${sql.raw(nodeAlias)}_props`,
|
|
3220
|
-
sql`${sql.raw(nodeAlias)}_version`,
|
|
3221
|
-
sql`${sql.raw(nodeAlias)}_valid_from`,
|
|
3222
|
-
sql`${sql.raw(nodeAlias)}_valid_to`,
|
|
3223
|
-
sql`${sql.raw(nodeAlias)}_created_at`,
|
|
3224
|
-
sql`${sql.raw(nodeAlias)}_updated_at`,
|
|
3225
|
-
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`
|
|
3226
3670
|
];
|
|
3227
|
-
if (
|
|
3228
|
-
|
|
3671
|
+
if (pathExtension !== void 0) {
|
|
3672
|
+
recursiveColumns.push(sql`${pathExtension} AS path`);
|
|
3229
3673
|
}
|
|
3230
|
-
|
|
3231
|
-
|
|
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 `)}`;
|
|
3232
3725
|
}
|
|
3233
|
-
return sql.join(
|
|
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 `)}`;
|
|
3234
3727
|
}
|
|
3235
|
-
function
|
|
3236
|
-
const
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
throw new UnsupportedPredicateError(
|
|
3240
|
-
`Selective projection for recursive traversals does not support alias "${field2.alias}"`
|
|
3241
|
-
);
|
|
3242
|
-
}
|
|
3243
|
-
if (field2.isSystemField) {
|
|
3244
|
-
const dbColumn = mapSelectiveSystemFieldToColumn(field2.field);
|
|
3245
|
-
return sql`${sql.raw(`${field2.alias}_${dbColumn}`)} AS ${quoteIdentifier(field2.outputName)}`;
|
|
3246
|
-
}
|
|
3247
|
-
const column = sql.raw(`${field2.alias}_props`);
|
|
3248
|
-
const extracted = compileTypedJsonExtract({
|
|
3249
|
-
column,
|
|
3250
|
-
dialect,
|
|
3251
|
-
pointer: jsonPointer([field2.field]),
|
|
3252
|
-
valueType: field2.valueType
|
|
3253
|
-
});
|
|
3254
|
-
return sql`${extracted} AS ${quoteIdentifier(field2.outputName)}`;
|
|
3255
|
-
});
|
|
3256
|
-
const vl = traversal.variableLength;
|
|
3257
|
-
if (vl.depthAlias !== void 0) {
|
|
3258
|
-
columns.push(sql`depth AS ${quoteIdentifier(vl.depthAlias)}`);
|
|
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));
|
|
3259
3732
|
}
|
|
3260
|
-
if (
|
|
3261
|
-
|
|
3733
|
+
if (ctx.excludeRoot) {
|
|
3734
|
+
filters.push(sql`id != ${ctx.rootId}`);
|
|
3262
3735
|
}
|
|
3263
|
-
|
|
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})`;
|
|
3264
3738
|
}
|
|
3265
|
-
function
|
|
3266
|
-
|
|
3267
|
-
|
|
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}`)}`);
|
|
3268
3782
|
}
|
|
3269
|
-
const
|
|
3270
|
-
for (const
|
|
3271
|
-
|
|
3272
|
-
if (valueType === "array" || valueType === "object") {
|
|
3273
|
-
throw new UnsupportedPredicateError(
|
|
3274
|
-
"Ordering by JSON arrays or objects is not supported"
|
|
3275
|
-
);
|
|
3276
|
-
}
|
|
3277
|
-
const field2 = compileFieldValue(orderSpec.field, dialect, valueType);
|
|
3278
|
-
const direction = sql.raw(orderSpec.direction.toUpperCase());
|
|
3279
|
-
const nulls = orderSpec.nulls ?? (orderSpec.direction === "asc" ? "last" : "first");
|
|
3280
|
-
const nullsDirection = sql.raw(nulls === "first" ? "DESC" : "ASC");
|
|
3281
|
-
parts.push(
|
|
3282
|
-
sql`(${field2} IS NULL) ${nullsDirection}`,
|
|
3283
|
-
sql`${field2} ${direction}`
|
|
3284
|
-
);
|
|
3783
|
+
const metaKinds = [...plan.fullKinds];
|
|
3784
|
+
for (const [kind, kindPlan] of plan.projectedKinds) {
|
|
3785
|
+
if (kindPlan.includeMeta) metaKinds.push(kind);
|
|
3285
3786
|
}
|
|
3286
|
-
|
|
3287
|
-
}
|
|
3288
|
-
function compileLimitOffset(ast) {
|
|
3289
|
-
const parts = [];
|
|
3290
|
-
if (ast.limit !== void 0) {
|
|
3291
|
-
parts.push(sql`LIMIT ${ast.limit}`);
|
|
3787
|
+
if (metaKinds.length === 0) {
|
|
3788
|
+
return columns.map((col) => sql`NULL AS ${sql.raw(col)}`);
|
|
3292
3789
|
}
|
|
3293
|
-
if (
|
|
3294
|
-
|
|
3790
|
+
if (metaKinds.length === plan.fullKinds.length + plan.projectedKinds.size) {
|
|
3791
|
+
return columns.map((col) => sql`${sql.raw(`${alias}.${col}`)}`);
|
|
3295
3792
|
}
|
|
3296
|
-
|
|
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
|
+
);
|
|
3297
3797
|
}
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
embeddings: "typegraph_node_embeddings"
|
|
3302
|
-
};
|
|
3303
|
-
var MAX_IDENTIFIER_LENGTH = 63;
|
|
3304
|
-
var VALID_IDENTIFIER_PATTERN = /^[a-z_][a-z0-9_$]*$/i;
|
|
3305
|
-
function validateTableName(name, label) {
|
|
3306
|
-
if (!name || name.length === 0) {
|
|
3307
|
-
throw new ConfigurationError(`${label} table name cannot be empty`);
|
|
3798
|
+
function buildFullPropsColumn(alias, plan) {
|
|
3799
|
+
if (plan.projectedKinds.size === 0) {
|
|
3800
|
+
return sql`${sql.raw(`${alias}.props`)} AS props`;
|
|
3308
3801
|
}
|
|
3309
|
-
if (
|
|
3310
|
-
|
|
3311
|
-
`${label} table name exceeds maximum length of ${MAX_IDENTIFIER_LENGTH} characters`
|
|
3312
|
-
);
|
|
3802
|
+
if (plan.fullKinds.length === 0) {
|
|
3803
|
+
return sql`NULL AS props`;
|
|
3313
3804
|
}
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
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`;
|
|
3807
|
+
}
|
|
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
|
+
}
|
|
3318
3822
|
}
|
|
3823
|
+
return columns;
|
|
3319
3824
|
}
|
|
3320
|
-
function
|
|
3321
|
-
|
|
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
|
|
3830
|
+
);
|
|
3831
|
+
}
|
|
3322
3832
|
}
|
|
3323
|
-
function
|
|
3324
|
-
const
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
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
|
|
3333
3866
|
};
|
|
3867
|
+
if (kindPlan.includeMeta) {
|
|
3868
|
+
projectedEdge.meta = rowToEdgeMeta(row);
|
|
3869
|
+
}
|
|
3870
|
+
applyProjectedFields(projectedEdge, row, kindPlan);
|
|
3871
|
+
return projectedEdge;
|
|
3334
3872
|
}
|
|
3335
|
-
var DEFAULT_SQL_SCHEMA = createSqlSchema();
|
|
3336
3873
|
var OPERATOR_MAP = {
|
|
3337
3874
|
union: "UNION",
|
|
3338
3875
|
unionAll: "UNION ALL",
|
|
@@ -4701,9 +5238,9 @@ function transformPathColumns(rows, state, _dialect) {
|
|
|
4701
5238
|
}
|
|
4702
5239
|
return changed ? result : rows;
|
|
4703
5240
|
}
|
|
4704
|
-
var
|
|
4705
|
-
var
|
|
4706
|
-
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) {
|
|
4707
5244
|
return value === null ? void 0 : value;
|
|
4708
5245
|
}
|
|
4709
5246
|
function assignPropsExcludingReserved(target, props, reservedKeys) {
|
|
@@ -4719,13 +5256,13 @@ function buildSelectableNode(row, alias) {
|
|
|
4719
5256
|
const propsRaw = row[`${alias}_props`];
|
|
4720
5257
|
const rawProps = typeof propsRaw === "string" ? JSON.parse(propsRaw) : propsRaw ?? {};
|
|
4721
5258
|
const version = row[`${alias}_version`];
|
|
4722
|
-
const validFrom =
|
|
5259
|
+
const validFrom = nullToUndefined3(
|
|
4723
5260
|
row[`${alias}_valid_from`]
|
|
4724
5261
|
);
|
|
4725
|
-
const validTo =
|
|
5262
|
+
const validTo = nullToUndefined3(row[`${alias}_valid_to`]);
|
|
4726
5263
|
const createdAt = row[`${alias}_created_at`];
|
|
4727
5264
|
const updatedAt = row[`${alias}_updated_at`];
|
|
4728
|
-
const deletedAt =
|
|
5265
|
+
const deletedAt = nullToUndefined3(
|
|
4729
5266
|
row[`${alias}_deleted_at`]
|
|
4730
5267
|
);
|
|
4731
5268
|
const result = {
|
|
@@ -4740,7 +5277,7 @@ function buildSelectableNode(row, alias) {
|
|
|
4740
5277
|
deletedAt
|
|
4741
5278
|
}
|
|
4742
5279
|
};
|
|
4743
|
-
assignPropsExcludingReserved(result, rawProps,
|
|
5280
|
+
assignPropsExcludingReserved(result, rawProps, RESERVED_NODE_KEYS3);
|
|
4744
5281
|
return result;
|
|
4745
5282
|
}
|
|
4746
5283
|
function buildSelectableNodeOrUndefined(row, alias) {
|
|
@@ -4760,13 +5297,13 @@ function buildSelectableEdge(row, alias) {
|
|
|
4760
5297
|
const toId = row[`${alias}_to_id`];
|
|
4761
5298
|
const propsRaw = row[`${alias}_props`];
|
|
4762
5299
|
const rawProps = typeof propsRaw === "string" ? JSON.parse(propsRaw) : propsRaw ?? {};
|
|
4763
|
-
const validFrom =
|
|
5300
|
+
const validFrom = nullToUndefined3(
|
|
4764
5301
|
row[`${alias}_valid_from`]
|
|
4765
5302
|
);
|
|
4766
|
-
const validTo =
|
|
5303
|
+
const validTo = nullToUndefined3(row[`${alias}_valid_to`]);
|
|
4767
5304
|
const createdAt = row[`${alias}_created_at`];
|
|
4768
5305
|
const updatedAt = row[`${alias}_updated_at`];
|
|
4769
|
-
const deletedAt =
|
|
5306
|
+
const deletedAt = nullToUndefined3(
|
|
4770
5307
|
row[`${alias}_deleted_at`]
|
|
4771
5308
|
);
|
|
4772
5309
|
const result = {
|
|
@@ -4782,7 +5319,7 @@ function buildSelectableEdge(row, alias) {
|
|
|
4782
5319
|
deletedAt
|
|
4783
5320
|
}
|
|
4784
5321
|
};
|
|
4785
|
-
assignPropsExcludingReserved(result, rawProps,
|
|
5322
|
+
assignPropsExcludingReserved(result, rawProps, RESERVED_EDGE_KEYS3);
|
|
4786
5323
|
return result;
|
|
4787
5324
|
}
|
|
4788
5325
|
function buildSelectContext(row, startAlias, traversals) {
|
|
@@ -4938,6 +5475,10 @@ var FieldAccessTracker = class {
|
|
|
4938
5475
|
#fields = /* @__PURE__ */ new Map();
|
|
4939
5476
|
record(alias, field2, isSystemField) {
|
|
4940
5477
|
const key = `${alias}\0${field2}`;
|
|
5478
|
+
const existing = this.#fields.get(key);
|
|
5479
|
+
if (existing !== void 0 && existing.isSystemField && !isSystemField) {
|
|
5480
|
+
return;
|
|
5481
|
+
}
|
|
4941
5482
|
this.#fields.set(key, { alias, field: field2, isSystemField });
|
|
4942
5483
|
}
|
|
4943
5484
|
getAccessedFields() {
|
|
@@ -5164,63 +5705,6 @@ function getPlaceholderForValueType(valueType, mode) {
|
|
|
5164
5705
|
}
|
|
5165
5706
|
}
|
|
5166
5707
|
|
|
5167
|
-
// src/query/execution/value-decoder.ts
|
|
5168
|
-
function nullToUndefined2(value) {
|
|
5169
|
-
return value === null ? void 0 : value;
|
|
5170
|
-
}
|
|
5171
|
-
function decodeSelectedValue(value, typeInfo) {
|
|
5172
|
-
const normalized = nullToUndefined2(value);
|
|
5173
|
-
if (normalized === void 0) return void 0;
|
|
5174
|
-
if (typeInfo === void 0) {
|
|
5175
|
-
return normalized;
|
|
5176
|
-
}
|
|
5177
|
-
return decodeByValueType(normalized, typeInfo.valueType);
|
|
5178
|
-
}
|
|
5179
|
-
function decodeByValueType(value, valueType) {
|
|
5180
|
-
switch (valueType) {
|
|
5181
|
-
case "boolean": {
|
|
5182
|
-
if (typeof value === "boolean") return value;
|
|
5183
|
-
if (typeof value === "number") return value !== 0;
|
|
5184
|
-
if (typeof value === "string") {
|
|
5185
|
-
if (value === "0") return false;
|
|
5186
|
-
if (value === "1") return true;
|
|
5187
|
-
if (value.toLowerCase() === "true") return true;
|
|
5188
|
-
if (value.toLowerCase() === "false") return false;
|
|
5189
|
-
}
|
|
5190
|
-
return Boolean(value);
|
|
5191
|
-
}
|
|
5192
|
-
case "number": {
|
|
5193
|
-
if (typeof value === "number") return value;
|
|
5194
|
-
if (typeof value === "string") {
|
|
5195
|
-
const parsed = Number(value);
|
|
5196
|
-
return Number.isNaN(parsed) ? value : parsed;
|
|
5197
|
-
}
|
|
5198
|
-
return value;
|
|
5199
|
-
}
|
|
5200
|
-
case "array":
|
|
5201
|
-
case "object":
|
|
5202
|
-
case "embedding": {
|
|
5203
|
-
if (typeof value !== "string") return value;
|
|
5204
|
-
const trimmed = value.trim();
|
|
5205
|
-
const looksJson = trimmed.startsWith("[") || trimmed.startsWith("{");
|
|
5206
|
-
if (!looksJson) return value;
|
|
5207
|
-
try {
|
|
5208
|
-
return JSON.parse(trimmed);
|
|
5209
|
-
} catch {
|
|
5210
|
-
return value;
|
|
5211
|
-
}
|
|
5212
|
-
}
|
|
5213
|
-
case "string":
|
|
5214
|
-
case "date":
|
|
5215
|
-
case "unknown": {
|
|
5216
|
-
return value;
|
|
5217
|
-
}
|
|
5218
|
-
default: {
|
|
5219
|
-
return value;
|
|
5220
|
-
}
|
|
5221
|
-
}
|
|
5222
|
-
}
|
|
5223
|
-
|
|
5224
5708
|
// src/query/execution/selective-result-mapper.ts
|
|
5225
5709
|
var MissingSelectiveFieldError = class extends Error {
|
|
5226
5710
|
alias;
|
|
@@ -5389,12 +5873,12 @@ function buildRequiredAliasValue(row, plan) {
|
|
|
5389
5873
|
}
|
|
5390
5874
|
};
|
|
5391
5875
|
for (const field2 of plan.systemFields) {
|
|
5392
|
-
base[field2.field] =
|
|
5876
|
+
base[field2.field] = nullToUndefined(row[field2.outputName]);
|
|
5393
5877
|
}
|
|
5394
5878
|
if (plan.metaFields.length > 0) {
|
|
5395
5879
|
const meta = {};
|
|
5396
5880
|
for (const field2 of plan.metaFields) {
|
|
5397
|
-
meta[field2.metaKey] =
|
|
5881
|
+
meta[field2.metaKey] = nullToUndefined(row[field2.outputName]);
|
|
5398
5882
|
}
|
|
5399
5883
|
base.meta = createGuardedProxy(meta, `${plan.alias}.meta`);
|
|
5400
5884
|
}
|
|
@@ -6361,24 +6845,102 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6361
6845
|
if (optimizedResult !== void 0) {
|
|
6362
6846
|
return optimizedResult;
|
|
6363
6847
|
}
|
|
6364
|
-
const compiled = this.compile();
|
|
6365
|
-
const rawRows = await this.#config.backend.execute(compiled);
|
|
6366
|
-
this.#config.dialect ?? "sqlite";
|
|
6367
|
-
const rows = transformPathColumns(rawRows, this.#state);
|
|
6368
|
-
return mapResults(
|
|
6369
|
-
rows,
|
|
6370
|
-
this.#state.startAlias,
|
|
6371
|
-
this.#state.traversals,
|
|
6372
|
-
this.#selectFn
|
|
6373
|
-
);
|
|
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
|
+
}
|
|
6374
6938
|
}
|
|
6375
6939
|
/**
|
|
6376
|
-
* Attempts optimized execution
|
|
6377
|
-
*
|
|
6378
|
-
* Returns undefined if optimization is not possible (callback uses method calls,
|
|
6379
|
-
* computations, or returns whole nodes).
|
|
6940
|
+
* Attempts optimized execution against a provided backend.
|
|
6941
|
+
* Mirror of #tryOptimizedExecution but delegates to the given backend.
|
|
6380
6942
|
*/
|
|
6381
|
-
async #
|
|
6943
|
+
async #tryOptimizedExecutionOn(backend) {
|
|
6382
6944
|
const selectiveFields = this.#getSelectiveFieldsForExecute();
|
|
6383
6945
|
if (selectiveFields === void 0) {
|
|
6384
6946
|
return void 0;
|
|
@@ -6399,7 +6961,7 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6399
6961
|
} else {
|
|
6400
6962
|
compiled = this.#cachedOptimizedCompiled;
|
|
6401
6963
|
}
|
|
6402
|
-
const rawSelectiveRows = await
|
|
6964
|
+
const rawSelectiveRows = await backend.execute(compiled);
|
|
6403
6965
|
this.#config.dialect ?? "sqlite";
|
|
6404
6966
|
const rows = transformPathColumns(rawSelectiveRows, this.#state);
|
|
6405
6967
|
try {
|
|
@@ -6619,6 +7181,10 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6619
7181
|
#recordOrderByFieldsForPagination(tracker) {
|
|
6620
7182
|
for (const spec of this.#state.orderBy) {
|
|
6621
7183
|
const field2 = spec.field;
|
|
7184
|
+
if (field2.path.length === 1 && field2.path[0] !== "props" && field2.jsonPointer === void 0) {
|
|
7185
|
+
tracker.record(field2.alias, field2.path[0], true);
|
|
7186
|
+
continue;
|
|
7187
|
+
}
|
|
6622
7188
|
if (field2.path.length !== 1 || field2.path[0] !== "props") {
|
|
6623
7189
|
return false;
|
|
6624
7190
|
}
|
|
@@ -6659,7 +7225,19 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6659
7225
|
const alias = spec.field.alias;
|
|
6660
7226
|
const jsonPointer2 = spec.field.jsonPointer;
|
|
6661
7227
|
if (jsonPointer2 === void 0) {
|
|
6662
|
-
|
|
7228
|
+
if (spec.field.path.length !== 1) {
|
|
7229
|
+
throw new MissingSelectiveFieldError(alias, "orderBy");
|
|
7230
|
+
}
|
|
7231
|
+
const fieldName = spec.field.path[0];
|
|
7232
|
+
const outputName2 = outputNameByAliasField.get(
|
|
7233
|
+
`${alias}\0${fieldName}`
|
|
7234
|
+
);
|
|
7235
|
+
if (outputName2 === void 0) {
|
|
7236
|
+
throw new MissingSelectiveFieldError(alias, fieldName);
|
|
7237
|
+
}
|
|
7238
|
+
const aliasObject2 = this.#getOrCreateAliasObject(cursorContext, alias);
|
|
7239
|
+
aliasObject2[fieldName] = nullToUndefined(row[outputName2]);
|
|
7240
|
+
continue;
|
|
6663
7241
|
}
|
|
6664
7242
|
const segments = parseJsonPointer(jsonPointer2);
|
|
6665
7243
|
if (segments.length === 0) {
|
|
@@ -6682,14 +7260,7 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6682
7260
|
continue;
|
|
6683
7261
|
}
|
|
6684
7262
|
}
|
|
6685
|
-
|
|
6686
|
-
const existing = cursorContext[alias];
|
|
6687
|
-
if (typeof existing === "object" && existing !== null) {
|
|
6688
|
-
aliasObject = existing;
|
|
6689
|
-
} else {
|
|
6690
|
-
aliasObject = {};
|
|
6691
|
-
cursorContext[alias] = aliasObject;
|
|
6692
|
-
}
|
|
7263
|
+
const aliasObject = this.#getOrCreateAliasObject(cursorContext, alias);
|
|
6693
7264
|
const kindNames = this.#getNodeKindNamesForAlias(alias);
|
|
6694
7265
|
const typeInfo = kindNames ? this.#config.schemaIntrospector.getSharedFieldTypeInfo(
|
|
6695
7266
|
kindNames,
|
|
@@ -6716,6 +7287,15 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6716
7287
|
}
|
|
6717
7288
|
return cursorContext;
|
|
6718
7289
|
}
|
|
7290
|
+
#getOrCreateAliasObject(cursorContext, alias) {
|
|
7291
|
+
const existing = cursorContext[alias];
|
|
7292
|
+
if (typeof existing === "object" && existing !== null) {
|
|
7293
|
+
return existing;
|
|
7294
|
+
}
|
|
7295
|
+
const created = {};
|
|
7296
|
+
cursorContext[alias] = created;
|
|
7297
|
+
return created;
|
|
7298
|
+
}
|
|
6719
7299
|
#getNodeKindNamesForAlias(alias) {
|
|
6720
7300
|
if (alias === this.#state.startAlias) {
|
|
6721
7301
|
return this.#state.startKinds;
|
|
@@ -7543,9 +8123,12 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
7543
8123
|
* Orders results.
|
|
7544
8124
|
*/
|
|
7545
8125
|
orderBy(alias, field2, direction = "asc") {
|
|
7546
|
-
const
|
|
7547
|
-
const typeInfo =
|
|
7548
|
-
const orderSpec = {
|
|
8126
|
+
const isSystem = field2 === "id" || field2 === "kind";
|
|
8127
|
+
const typeInfo = isSystem ? void 0 : this.#getOrderByTypeInfo(alias, field2);
|
|
8128
|
+
const orderSpec = isSystem ? {
|
|
8129
|
+
field: fieldRef(alias, [field2], { valueType: "string" }),
|
|
8130
|
+
direction
|
|
8131
|
+
} : {
|
|
7549
8132
|
field: fieldRef(alias, ["props"], {
|
|
7550
8133
|
jsonPointer: jsonPointer([field2]),
|
|
7551
8134
|
valueType: typeInfo?.valueType,
|
|
@@ -7559,6 +8142,10 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
7559
8142
|
};
|
|
7560
8143
|
return new _QueryBuilder(this.#config, newState);
|
|
7561
8144
|
}
|
|
8145
|
+
#getOrderByTypeInfo(alias, field2) {
|
|
8146
|
+
const kindNames = this.#getKindNamesForAlias(alias);
|
|
8147
|
+
return kindNames ? this.#config.schemaIntrospector.getSharedFieldTypeInfo(kindNames, field2) : void 0;
|
|
8148
|
+
}
|
|
7562
8149
|
/**
|
|
7563
8150
|
* Limits the number of results.
|
|
7564
8151
|
*/
|
|
@@ -8032,6 +8619,29 @@ var UnionableQuery = class _UnionableQuery {
|
|
|
8032
8619
|
}
|
|
8033
8620
|
return rows;
|
|
8034
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
|
+
}
|
|
8035
8645
|
};
|
|
8036
8646
|
|
|
8037
8647
|
// src/query/builder/aggregates.ts
|
|
@@ -9252,67 +9862,6 @@ async function checkCardinalityConstraint(ctx, edgeKind, cardinality, fromKind,
|
|
|
9252
9862
|
}
|
|
9253
9863
|
}
|
|
9254
9864
|
|
|
9255
|
-
// src/store/row-mappers.ts
|
|
9256
|
-
var RESERVED_NODE_KEYS3 = /* @__PURE__ */ new Set(["id", "kind", "meta"]);
|
|
9257
|
-
function nullToUndefined3(value) {
|
|
9258
|
-
return value === null ? void 0 : value;
|
|
9259
|
-
}
|
|
9260
|
-
function filterReservedKeys(props, reservedKeys) {
|
|
9261
|
-
const filtered = {};
|
|
9262
|
-
for (const [key, value] of Object.entries(props)) {
|
|
9263
|
-
if (!reservedKeys.has(key)) {
|
|
9264
|
-
filtered[key] = value;
|
|
9265
|
-
}
|
|
9266
|
-
}
|
|
9267
|
-
return filtered;
|
|
9268
|
-
}
|
|
9269
|
-
function rowToNode(row) {
|
|
9270
|
-
const rawProps = JSON.parse(row.props);
|
|
9271
|
-
const props = filterReservedKeys(rawProps, RESERVED_NODE_KEYS3);
|
|
9272
|
-
return {
|
|
9273
|
-
kind: row.kind,
|
|
9274
|
-
id: row.id,
|
|
9275
|
-
meta: {
|
|
9276
|
-
version: row.version,
|
|
9277
|
-
validFrom: nullToUndefined3(row.valid_from),
|
|
9278
|
-
validTo: nullToUndefined3(row.valid_to),
|
|
9279
|
-
createdAt: row.created_at,
|
|
9280
|
-
updatedAt: row.updated_at,
|
|
9281
|
-
deletedAt: nullToUndefined3(row.deleted_at)
|
|
9282
|
-
},
|
|
9283
|
-
...props
|
|
9284
|
-
};
|
|
9285
|
-
}
|
|
9286
|
-
var RESERVED_EDGE_KEYS3 = /* @__PURE__ */ new Set([
|
|
9287
|
-
"id",
|
|
9288
|
-
"kind",
|
|
9289
|
-
"meta",
|
|
9290
|
-
"fromKind",
|
|
9291
|
-
"fromId",
|
|
9292
|
-
"toKind",
|
|
9293
|
-
"toId"
|
|
9294
|
-
]);
|
|
9295
|
-
function rowToEdge(row) {
|
|
9296
|
-
const rawProps = JSON.parse(row.props);
|
|
9297
|
-
const props = filterReservedKeys(rawProps, RESERVED_EDGE_KEYS3);
|
|
9298
|
-
return {
|
|
9299
|
-
id: row.id,
|
|
9300
|
-
kind: row.kind,
|
|
9301
|
-
fromKind: row.from_kind,
|
|
9302
|
-
fromId: row.from_id,
|
|
9303
|
-
toKind: row.to_kind,
|
|
9304
|
-
toId: row.to_id,
|
|
9305
|
-
meta: {
|
|
9306
|
-
validFrom: nullToUndefined3(row.valid_from),
|
|
9307
|
-
validTo: nullToUndefined3(row.valid_to),
|
|
9308
|
-
createdAt: row.created_at,
|
|
9309
|
-
updatedAt: row.updated_at,
|
|
9310
|
-
deletedAt: nullToUndefined3(row.deleted_at)
|
|
9311
|
-
},
|
|
9312
|
-
...props
|
|
9313
|
-
};
|
|
9314
|
-
}
|
|
9315
|
-
|
|
9316
9865
|
// src/store/operations/edge-operations.ts
|
|
9317
9866
|
function getEdgeRegistration(graph, kind) {
|
|
9318
9867
|
const registration = graph.edges[kind];
|
|
@@ -11186,142 +11735,6 @@ async function executeNodeBulkGetOrCreateByConstraint(ctx, kind, constraintName,
|
|
|
11186
11735
|
}
|
|
11187
11736
|
return results;
|
|
11188
11737
|
}
|
|
11189
|
-
var DEFAULT_SUBGRAPH_MAX_DEPTH = 10;
|
|
11190
|
-
function normalizeProps(value) {
|
|
11191
|
-
return typeof value === "string" ? value : JSON.stringify(value ?? {});
|
|
11192
|
-
}
|
|
11193
|
-
async function executeSubgraph(params) {
|
|
11194
|
-
const { options } = params;
|
|
11195
|
-
if (options.edges.length === 0) {
|
|
11196
|
-
return { nodes: [], edges: [] };
|
|
11197
|
-
}
|
|
11198
|
-
const maxDepth = Math.min(
|
|
11199
|
-
options.maxDepth ?? DEFAULT_SUBGRAPH_MAX_DEPTH,
|
|
11200
|
-
MAX_RECURSIVE_DEPTH
|
|
11201
|
-
);
|
|
11202
|
-
const ctx = {
|
|
11203
|
-
graphId: params.graphId,
|
|
11204
|
-
rootId: params.rootId,
|
|
11205
|
-
edgeKinds: options.edges,
|
|
11206
|
-
maxDepth,
|
|
11207
|
-
includeKinds: options.includeKinds,
|
|
11208
|
-
excludeRoot: options.excludeRoot ?? false,
|
|
11209
|
-
direction: options.direction ?? "out",
|
|
11210
|
-
cyclePolicy: options.cyclePolicy ?? "prevent",
|
|
11211
|
-
dialect: params.dialect,
|
|
11212
|
-
schema: params.schema ?? DEFAULT_SQL_SCHEMA,
|
|
11213
|
-
backend: params.backend
|
|
11214
|
-
};
|
|
11215
|
-
const reachableCte = buildReachableCte(ctx);
|
|
11216
|
-
const includedIdsCte = buildIncludedIdsCte(ctx);
|
|
11217
|
-
const [nodeRows, edgeRows] = await Promise.all([
|
|
11218
|
-
fetchSubgraphNodes(ctx, reachableCte, includedIdsCte),
|
|
11219
|
-
fetchSubgraphEdges(ctx, reachableCte, includedIdsCte)
|
|
11220
|
-
]);
|
|
11221
|
-
const nodes = nodeRows.map(
|
|
11222
|
-
(row) => rowToNode({ ...row, props: normalizeProps(row.props) })
|
|
11223
|
-
);
|
|
11224
|
-
const edges = edgeRows.map(
|
|
11225
|
-
(row) => rowToEdge({ ...row, props: normalizeProps(row.props) })
|
|
11226
|
-
);
|
|
11227
|
-
return {
|
|
11228
|
-
nodes,
|
|
11229
|
-
edges
|
|
11230
|
-
};
|
|
11231
|
-
}
|
|
11232
|
-
function buildReachableCte(ctx) {
|
|
11233
|
-
const shouldTrackPath = ctx.cyclePolicy === "prevent";
|
|
11234
|
-
const edgeKindFilter = compileKindFilter(sql.raw("e.kind"), ctx.edgeKinds);
|
|
11235
|
-
const initialPath = shouldTrackPath ? ctx.dialect.initializePath(sql.raw("n.id")) : void 0;
|
|
11236
|
-
const pathExtension = shouldTrackPath ? ctx.dialect.extendPath(sql.raw("r.path"), sql.raw("n.id")) : void 0;
|
|
11237
|
-
const cycleCheck = shouldTrackPath ? ctx.dialect.cycleCheck(sql.raw("n.id"), sql.raw("r.path")) : void 0;
|
|
11238
|
-
const baseColumns = [sql`n.id`, sql`n.kind`, sql`0 AS depth`];
|
|
11239
|
-
if (initialPath !== void 0) {
|
|
11240
|
-
baseColumns.push(sql`${initialPath} AS path`);
|
|
11241
|
-
}
|
|
11242
|
-
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`;
|
|
11243
|
-
const recursiveColumns = [
|
|
11244
|
-
sql`n.id`,
|
|
11245
|
-
sql`n.kind`,
|
|
11246
|
-
sql`r.depth + 1 AS depth`
|
|
11247
|
-
];
|
|
11248
|
-
if (pathExtension !== void 0) {
|
|
11249
|
-
recursiveColumns.push(sql`${pathExtension} AS path`);
|
|
11250
|
-
}
|
|
11251
|
-
const recursiveWhereClauses = [
|
|
11252
|
-
sql`e.graph_id = ${ctx.graphId}`,
|
|
11253
|
-
edgeKindFilter,
|
|
11254
|
-
sql`e.deleted_at IS NULL`,
|
|
11255
|
-
sql`n.deleted_at IS NULL`,
|
|
11256
|
-
sql`r.depth < ${ctx.maxDepth}`
|
|
11257
|
-
];
|
|
11258
|
-
if (cycleCheck !== void 0) {
|
|
11259
|
-
recursiveWhereClauses.push(cycleCheck);
|
|
11260
|
-
}
|
|
11261
|
-
const forceWorktableOuterJoinOrder = ctx.dialect.capabilities.forceRecursiveWorktableOuterJoinOrder;
|
|
11262
|
-
const recursiveCase = ctx.direction === "both" ? compileBidirectionalBranch({
|
|
11263
|
-
recursiveColumns,
|
|
11264
|
-
whereClauses: recursiveWhereClauses,
|
|
11265
|
-
forceWorktableOuterJoinOrder,
|
|
11266
|
-
schema: ctx.schema
|
|
11267
|
-
}) : compileRecursiveBranch({
|
|
11268
|
-
recursiveColumns,
|
|
11269
|
-
whereClauses: recursiveWhereClauses,
|
|
11270
|
-
joinField: "from_id",
|
|
11271
|
-
targetField: "to_id",
|
|
11272
|
-
targetKindField: "to_kind",
|
|
11273
|
-
forceWorktableOuterJoinOrder,
|
|
11274
|
-
schema: ctx.schema
|
|
11275
|
-
});
|
|
11276
|
-
return sql`WITH RECURSIVE reachable AS (${baseCase} UNION ALL ${recursiveCase})`;
|
|
11277
|
-
}
|
|
11278
|
-
function compileRecursiveBranch(params) {
|
|
11279
|
-
const columns = [...params.recursiveColumns];
|
|
11280
|
-
const selectClause = sql`SELECT ${sql.join(columns, sql`, `)}`;
|
|
11281
|
-
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)}`;
|
|
11282
|
-
if (params.forceWorktableOuterJoinOrder) {
|
|
11283
|
-
const allWhere = [
|
|
11284
|
-
...params.whereClauses,
|
|
11285
|
-
sql`e.${sql.raw(params.joinField)} = r.id`
|
|
11286
|
-
];
|
|
11287
|
-
return sql`${selectClause} FROM reachable r CROSS JOIN ${params.schema.edgesTable} e ${nodeJoin} WHERE ${sql.join(allWhere, sql` AND `)}`;
|
|
11288
|
-
}
|
|
11289
|
-
const where = [...params.whereClauses];
|
|
11290
|
-
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 `)}`;
|
|
11291
|
-
}
|
|
11292
|
-
function compileBidirectionalBranch(params) {
|
|
11293
|
-
const columns = [...params.recursiveColumns];
|
|
11294
|
-
const selectClause = sql`SELECT ${sql.join(columns, sql`, `)}`;
|
|
11295
|
-
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))`;
|
|
11296
|
-
if (params.forceWorktableOuterJoinOrder) {
|
|
11297
|
-
const allWhere = [
|
|
11298
|
-
...params.whereClauses,
|
|
11299
|
-
sql`(e.from_id = r.id OR e.to_id = r.id)`
|
|
11300
|
-
];
|
|
11301
|
-
return sql`${selectClause} FROM reachable r CROSS JOIN ${params.schema.edgesTable} e ${nodeJoin} WHERE ${sql.join(allWhere, sql` AND `)}`;
|
|
11302
|
-
}
|
|
11303
|
-
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 `)}`;
|
|
11304
|
-
}
|
|
11305
|
-
function buildIncludedIdsCte(ctx) {
|
|
11306
|
-
const filters = [];
|
|
11307
|
-
if (ctx.includeKinds !== void 0 && ctx.includeKinds.length > 0) {
|
|
11308
|
-
filters.push(compileKindFilter(sql.raw("kind"), ctx.includeKinds));
|
|
11309
|
-
}
|
|
11310
|
-
if (ctx.excludeRoot) {
|
|
11311
|
-
filters.push(sql`id != ${ctx.rootId}`);
|
|
11312
|
-
}
|
|
11313
|
-
const whereClause = filters.length > 0 ? sql` WHERE ${sql.join(filters, sql` AND `)}` : sql``;
|
|
11314
|
-
return sql`, included_ids AS (SELECT DISTINCT id FROM reachable${whereClause})`;
|
|
11315
|
-
}
|
|
11316
|
-
async function fetchSubgraphNodes(ctx, reachableCte, includedIdsCte) {
|
|
11317
|
-
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)`;
|
|
11318
|
-
return ctx.backend.execute(query);
|
|
11319
|
-
}
|
|
11320
|
-
async function fetchSubgraphEdges(ctx, reachableCte, includedIdsCte) {
|
|
11321
|
-
const edgeKindFilter = compileKindFilter(sql.raw("e.kind"), ctx.edgeKinds);
|
|
11322
|
-
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)`;
|
|
11323
|
-
return ctx.backend.execute(query);
|
|
11324
|
-
}
|
|
11325
11738
|
|
|
11326
11739
|
// src/store/store.ts
|
|
11327
11740
|
var Store = class {
|
|
@@ -11525,6 +11938,46 @@ var Store = class {
|
|
|
11525
11938
|
query() {
|
|
11526
11939
|
return this.#createQueryForBackend(this.#backend);
|
|
11527
11940
|
}
|
|
11941
|
+
// === Batch Query Execution ===
|
|
11942
|
+
/**
|
|
11943
|
+
* Executes multiple queries over a single connection with snapshot consistency.
|
|
11944
|
+
*
|
|
11945
|
+
* Acquires one connection via an implicit transaction, executes each query
|
|
11946
|
+
* sequentially on that connection, and returns a typed tuple of results.
|
|
11947
|
+
* Each query preserves its own result type, projection, filtering,
|
|
11948
|
+
* sorting, and pagination.
|
|
11949
|
+
*
|
|
11950
|
+
* Read-only — use `bulkCreate`, `bulkInsert`, etc. for write batching.
|
|
11951
|
+
*
|
|
11952
|
+
* @example
|
|
11953
|
+
* ```typescript
|
|
11954
|
+
* const [people, companies] = await store.batch(
|
|
11955
|
+
* store.query()
|
|
11956
|
+
* .from("Person", "p")
|
|
11957
|
+
* .select((ctx) => ({ id: ctx.p.id, name: ctx.p.name })),
|
|
11958
|
+
* store.query()
|
|
11959
|
+
* .from("Company", "c")
|
|
11960
|
+
* .select((ctx) => ({ id: ctx.c.id, name: ctx.c.name }))
|
|
11961
|
+
* .orderBy("c", "name", "asc")
|
|
11962
|
+
* .limit(5),
|
|
11963
|
+
* );
|
|
11964
|
+
* // people: readonly { id: string; name: string }[]
|
|
11965
|
+
* // companies: readonly { id: string; name: string }[]
|
|
11966
|
+
* ```
|
|
11967
|
+
*
|
|
11968
|
+
* @param queries - Two or more executable queries (from `.select()` or set operations)
|
|
11969
|
+
* @returns A tuple with per-query typed results, preserving input order
|
|
11970
|
+
*/
|
|
11971
|
+
async batch(...queries) {
|
|
11972
|
+
return this.#backend.transaction(async (txBackend) => {
|
|
11973
|
+
const results = [];
|
|
11974
|
+
for (const query of queries) {
|
|
11975
|
+
const result = await query.executeOn(txBackend);
|
|
11976
|
+
results.push(result);
|
|
11977
|
+
}
|
|
11978
|
+
return results;
|
|
11979
|
+
});
|
|
11980
|
+
}
|
|
11528
11981
|
// === Subgraph Extraction ===
|
|
11529
11982
|
/**
|
|
11530
11983
|
* Extracts a typed subgraph by traversing from a root node.
|
|
@@ -11550,6 +12003,7 @@ var Store = class {
|
|
|
11550
12003
|
*/
|
|
11551
12004
|
async subgraph(rootId, options) {
|
|
11552
12005
|
return executeSubgraph({
|
|
12006
|
+
graph: this.#graph,
|
|
11553
12007
|
graphId: this.graphId,
|
|
11554
12008
|
rootId,
|
|
11555
12009
|
backend: this.#backend,
|
|
@@ -11729,6 +12183,6 @@ async function createStoreWithSchema(graph, backend, options) {
|
|
|
11729
12183
|
return [store, result];
|
|
11730
12184
|
}
|
|
11731
12185
|
|
|
11732
|
-
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 };
|
|
12186
|
+
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 };
|
|
11733
12187
|
//# sourceMappingURL=index.js.map
|
|
11734
12188
|
//# sourceMappingURL=index.js.map
|